Skip to content

Commit d944b70

Browse files
authored
Merge branch 'main' into claude/commandlayer-full-stack-work-Mvrou
2 parents ae925c6 + 30f088c commit d944b70

File tree

16 files changed

+1609
-903
lines changed

16 files changed

+1609
-903
lines changed

.eslintrc.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"parser": "@typescript-eslint/parser",
3+
"plugins": ["@typescript-eslint"],
4+
"extends": [
5+
"eslint:recommended",
6+
"plugin:@typescript-eslint/recommended"
7+
],
8+
"env": {
9+
"node": true,
10+
"es2022": true
11+
},
12+
"rules": {
13+
"@typescript-eslint/no-explicit-any": "warn",
14+
"@typescript-eslint/explicit-module-boundary-types": "off",
15+
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
16+
"no-console": "off"
17+
}
18+
}

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
node_modules/
22
dist/
3+
*.js.map
4+
*.d.ts.map
35
.env
4-
.DS_Store
6+
.env.local
7+
.env.*.local
58
*.log
69
run-logs/
10+
coverage/
11+
.nyc_output/

package.json

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,70 @@
11
{
22
"name": "@commandlayer/router",
3-
"version": "0.1.0",
4-
"private": false,
5-
"description": "Deterministic CommandLayer Router (Commons: 10 universal verbs) — explicit plans + scored routing, schema-shaped requests, signed receipts.",
6-
"main": "dist/src/index.js",
7-
"types": "dist/src/index.d.ts",
8-
"files": ["dist", "docs", "README.md", "LICENSE"],
9-
"engines": { "node": ">=20" },
3+
"version": "1.0.0",
4+
"description": "Deterministic CommandLayer router runtime orchestrating the 10 Commons verbs",
5+
"main": "dist/index.js",
6+
"types": "dist/index.d.ts",
7+
"bin": {
8+
"cl-router": "dist/cli/cl-router.js"
9+
},
1010
"scripts": {
11-
"dev": "node --watch --enable-source-maps dist/src/server.js",
12-
"build": "tsc -p tsconfig.json",
13-
"start": "node dist/src/server.js",
14-
"test": "node --test dist/test/scoring.test.js dist/test/prereqs.test.js dist/test/requestBuilders.test.js dist/test/idempotency.test.js dist/test/engine.test.js",
15-
"lint": "tsc --noEmit"
11+
"build": "tsc",
12+
"build:watch": "tsc --watch",
13+
"start": "node dist/index.js",
14+
"dev": "ts-node src/index.ts",
15+
"test": "jest --runInBand",
16+
"test:watch": "jest --watch",
17+
"lint": "eslint src test --ext .ts",
18+
"lint:fix": "eslint src test --ext .ts --fix",
19+
"typecheck": "tsc --noEmit",
20+
"clean": "rm -rf dist",
21+
"prepublishOnly": "npm run clean && npm run build"
1622
},
23+
"keywords": [
24+
"commandlayer",
25+
"router",
26+
"commons",
27+
"deterministic",
28+
"receipts",
29+
"ed25519"
30+
],
31+
"author": "CommandLayer",
32+
"license": "MIT",
1733
"dependencies": {
18-
"ajv": "^8.17.1",
19-
"ajv-formats": "^3.0.1",
20-
"express": "^4.21.2"
34+
"ajv": "^8.12.0",
35+
"ajv-formats": "^2.1.1",
36+
"commander": "^11.1.0",
37+
"express": "^4.18.2",
38+
"node-fetch": "^3.3.2",
39+
"uuid": "^9.0.0"
2140
},
2241
"devDependencies": {
2342
"@types/express": "^4.17.21",
24-
"@types/node": "^20.11.30",
25-
"typescript": "^5.6.3"
43+
"@types/jest": "^29.5.11",
44+
"@types/node": "^20.11.0",
45+
"@types/uuid": "^9.0.7",
46+
"@typescript-eslint/eslint-plugin": "^6.18.0",
47+
"@typescript-eslint/parser": "^6.18.0",
48+
"eslint": "^8.56.0",
49+
"jest": "^29.7.0",
50+
"supertest": "^6.3.4",
51+
"@types/supertest": "^6.0.2",
52+
"ts-jest": "^29.1.1",
53+
"ts-node": "^10.9.2",
54+
"typescript": "^5.3.3"
55+
},
56+
"jest": {
57+
"preset": "ts-jest",
58+
"testEnvironment": "node",
59+
"testMatch": [
60+
"**/test/**/*.test.ts"
61+
],
62+
"collectCoverageFrom": [
63+
"src/**/*.ts",
64+
"!src/cli/**"
65+
]
66+
},
67+
"engines": {
68+
"node": ">=20.0.0"
2669
}
2770
}

src/router/applyReceipt.ts

Lines changed: 89 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,114 @@
1-
import type { RunState } from "../types/run";
2-
import { addArtifact } from "./state";
3-
41
/**
5-
* Apply a successful step receipt to the run state by appending an artifact
6-
* of the appropriate type.
7-
*
8-
* This is the canonical mapping from verb → artifact type.
9-
* Only applies if receipt.status === "success".
10-
* Never overwrites or removes existing artifacts (append-only).
2+
* CommandLayer Router — Apply receipt to state
3+
* Extracts output from a verb receipt and appends the appropriate artifact.
114
*/
12-
export function applyReceiptToState(
13-
state: RunState,
14-
verb: string,
15-
receipt: Record<string, unknown>
16-
): void {
17-
if (!receipt || receipt["status"] !== "success") return;
185

19-
const r = receipt["result"];
20-
if (r === undefined || r === null) return;
6+
import type { CommonsVerb, RunState, VerbReceipt } from "../types/commons.js";
7+
import { VERB_ARTIFACT_TYPE } from "./commonsVerbs.js";
8+
import { addArtifact } from "./state.js";
9+
10+
/**
11+
* Extract the content artifact from a receipt's output.
12+
* Each verb may return output in a different shape.
13+
*/
14+
function extractArtifactData(verb: CommonsVerb, output: unknown): unknown {
15+
if (!output || typeof output !== "object") return output;
16+
const out = output as Record<string, unknown>;
2117

2218
switch (verb) {
2319
case "fetch":
24-
// fetch may return items[] or a direct result
25-
{
26-
const items = (r as Record<string, unknown>)["items"];
27-
const data =
28-
Array.isArray(items) && items.length > 0 ? items[0] : r;
29-
addArtifact(state, "fetched", data);
30-
}
31-
break;
20+
// Return { content, status, headers, url } or raw content
21+
return out;
3222

3323
case "clean":
34-
addArtifact(state, "cleaned", r);
35-
break;
24+
// Return { content } or raw string
25+
if (typeof out["content"] === "string") return { content: out["content"] };
26+
return out;
3627

3728
case "summarize":
38-
addArtifact(state, "summary", r);
39-
break;
29+
// Return { summary } or raw string
30+
if (typeof out["summary"] === "string") return { summary: out["summary"] };
31+
if (typeof out["content"] === "string") return { summary: out["content"] };
32+
return out;
4033

4134
case "classify":
42-
addArtifact(state, "classification", r);
43-
break;
44-
45-
case "analyze":
46-
addArtifact(state, "analysis", r);
47-
break;
35+
// Return { label, confidence, categories } etc
36+
return out;
4837

4938
case "parse":
50-
addArtifact(state, "parsed", r);
51-
break;
39+
// Return structured data
40+
return out;
5241

53-
case "convert":
54-
addArtifact(state, "converted", r);
55-
break;
42+
case "analyze":
43+
// Return { analysis } or structured data
44+
return out;
5645

5746
case "describe":
58-
addArtifact(state, "description", r);
59-
break;
47+
// Return { description } or string
48+
return out;
6049

6150
case "explain":
62-
addArtifact(state, "explanation", r);
63-
break;
51+
// Return { explanation } or string
52+
return out;
53+
54+
case "convert":
55+
// Return { content, format } or raw
56+
return out;
6457

6558
case "format":
66-
// format produces the final artifact
67-
addArtifact(state, "final", r);
68-
break;
59+
// Return { content, format } or raw
60+
return out;
6961

7062
default:
71-
// unknown verb: store with verb name as type
72-
addArtifact(state, verb, r);
73-
break;
63+
return out;
7464
}
7565
}
66+
67+
/**
68+
* Apply a successful step receipt to state by adding the appropriate artifact.
69+
*/
70+
export function applyReceiptToState(
71+
verb: CommonsVerb,
72+
receipt: VerbReceipt,
73+
state: RunState
74+
): void {
75+
if (!receipt.ok) return; // Only apply successful receipts
76+
77+
const artifactType = VERB_ARTIFACT_TYPE[verb];
78+
if (!artifactType) return;
79+
80+
const data = extractArtifactData(verb, receipt.output);
81+
addArtifact(state, artifactType, data);
82+
}
83+
84+
/**
85+
* Extract "final" artifact from state after all steps.
86+
* Returns the most meaningful artifact available.
87+
*/
88+
export function extractFinal(state: RunState): unknown {
89+
// Priority: formatted > converted > summary > analysis > description > explanation > classification > parsed > cleaned > fetched
90+
const priority = [
91+
"formatted",
92+
"converted",
93+
"summary",
94+
"analysis",
95+
"description",
96+
"explanation",
97+
"classification",
98+
"parsed",
99+
"cleaned",
100+
"fetched",
101+
];
102+
103+
for (const type of priority) {
104+
const artifact = state.artifacts.findLast?.((a) => a.type === type);
105+
if (artifact) return artifact.data;
106+
}
107+
108+
// Return last artifact if any
109+
if (state.artifacts.length > 0) {
110+
return state.artifacts[state.artifacts.length - 1].data;
111+
}
112+
113+
return null;
114+
}

src/router/choose.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* CommandLayer Router — Verb chooser
3+
* Wraps scoring + prereq filtering for the scored-mode engine.
4+
*/
5+
6+
import type { CommonsVerb, RunState, Policy } from "../types/commons.js";
7+
import type { ScoredCandidate } from "../types/run.js";
8+
import { effectiveVerbs } from "./policy.js";
9+
import { prereqsMet } from "./prereqs.js";
10+
import { chooseBestVerb, scoreCandidates } from "./scoring.js";
11+
12+
export interface ChoiceResult {
13+
chosen: ScoredCandidate | null;
14+
all_candidates: ScoredCandidate[];
15+
stop_reason?: string;
16+
}
17+
18+
/**
19+
* Choose the best next verb to run in scored mode.
20+
* Filters by:
21+
* 1. Policy (allow/deny lists)
22+
* 2. Prerequisites met (state-need graph)
23+
* 3. Score > 0 (stop condition)
24+
*/
25+
export function chooseNextVerb(
26+
goal: string,
27+
state: RunState,
28+
policy?: Policy
29+
): ChoiceResult {
30+
// 1. Get policy-allowed verbs
31+
const policyVerbs = effectiveVerbs(policy);
32+
33+
if (policyVerbs.length === 0) {
34+
return {
35+
chosen: null,
36+
all_candidates: [],
37+
stop_reason: "no_verbs_in_policy",
38+
};
39+
}
40+
41+
// 2. Filter by prerequisites
42+
const eligible = policyVerbs.filter((v) => prereqsMet(v, state, goal));
43+
44+
if (eligible.length === 0) {
45+
return {
46+
chosen: null,
47+
all_candidates: [],
48+
stop_reason: "no_eligible_verbs_after_prereq_filter",
49+
};
50+
}
51+
52+
// 3. Score and pick best
53+
const all_candidates = scoreCandidates(eligible, goal, state, policy);
54+
const chosen = chooseBestVerb(eligible, goal, state, policy);
55+
56+
return {
57+
chosen,
58+
all_candidates,
59+
stop_reason: chosen ? undefined : "best_score_lte_zero",
60+
};
61+
}

0 commit comments

Comments
 (0)