cool-workflow 0.1.82 → 0.1.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +4 -4
- package/README.md +128 -120
- package/apps/architecture-review/app.json +1 -1
- package/apps/architecture-review-fast/app.json +1 -1
- package/apps/end-to-end-golden-path/app.json +1 -1
- package/apps/pr-review-fix-ci/app.json +1 -1
- package/apps/release-cut/app.json +1 -1
- package/apps/research-synthesis/app.json +1 -1
- package/dist/capability-core.js +16 -8
- package/dist/capability-registry.js +270 -0
- package/dist/cli/command-surface.js +1320 -0
- package/dist/cli.js +2 -1307
- package/dist/commit.js +5 -1
- package/dist/doctor.js +153 -0
- package/dist/mcp-server.js +15 -1451
- package/dist/mcp-surface.js +1441 -0
- package/dist/orchestrator.js +13 -0
- package/dist/reclamation/hash.js +72 -0
- package/dist/reclamation.js +25 -78
- package/dist/run-registry/queue.js +6 -7
- package/dist/run-registry.js +35 -24
- package/dist/scheduler.js +78 -53
- package/dist/version.js +1 -1
- package/dist/worker-accept/acceptance.js +114 -0
- package/dist/worker-accept/blackboard-fanout.js +80 -0
- package/dist/worker-accept/blackboard-linkage.js +19 -0
- package/dist/worker-accept/context.js +2 -0
- package/dist/worker-accept/telemetry-ledger.js +116 -0
- package/dist/worker-accept/validation.js +77 -0
- package/dist/worker-accept/verifier-completion.js +73 -0
- package/dist/worker-isolation.js +41 -446
- package/docs/agent-delegation-drive.7.md +94 -86
- package/docs/agent-framework.md +33 -32
- package/docs/candidate-scoring.7.md +26 -24
- package/docs/canonical-workflow-apps.7.md +40 -40
- package/docs/capability-topology-registry.7.md +24 -24
- package/docs/cli-mcp-parity.7.md +230 -154
- package/docs/contract-migration-tooling.7.md +52 -41
- package/docs/control-plane-scheduling.7.md +49 -41
- package/docs/coordinator-blackboard.7.md +30 -30
- package/docs/dogfood-one-real-repo.7.md +44 -44
- package/docs/durable-state-and-locking.7.md +38 -30
- package/docs/end-to-end-golden-path.7.md +29 -29
- package/docs/error-feedback.7.md +27 -27
- package/docs/evidence-adoption-reasoning-chain.7.md +66 -58
- package/docs/execution-backends.7.md +88 -80
- package/docs/getting-started.md +35 -18
- package/docs/index.md +3 -3
- package/docs/mcp-app-surface.7.md +64 -64
- package/docs/multi-agent-cli-mcp-surface.7.md +86 -77
- package/docs/multi-agent-eval-replay-harness.7.md +63 -55
- package/docs/multi-agent-operator-ux.7.md +73 -65
- package/docs/multi-agent-runtime-core.7.md +39 -39
- package/docs/multi-agent-topologies.7.md +24 -24
- package/docs/multi-agent-trust-policy-audit.7.md +38 -38
- package/docs/node-snapshot-diff-replay.7.md +30 -22
- package/docs/observability-cost-accounting.7.md +53 -45
- package/docs/operator-ux.7.md +30 -30
- package/docs/pipeline-runner.7.md +31 -31
- package/docs/project-index.md +16 -5
- package/docs/real-execution-backends.7.md +51 -43
- package/docs/release-and-migration.7.md +46 -38
- package/docs/release-tooling.7.md +67 -50
- package/docs/routines.md +16 -16
- package/docs/run-registry-control-plane.7.md +124 -116
- package/docs/run-retention-reclamation.7.md +49 -41
- package/docs/sandbox-profiles.7.md +32 -32
- package/docs/scheduled-tasks.md +14 -14
- package/docs/security-trust-hardening.7.md +29 -29
- package/docs/source-context-profiles.7.md +28 -28
- package/docs/state-explosion-management.7.md +67 -59
- package/docs/state-node.7.md +8 -8
- package/docs/team-collaboration.7.md +66 -58
- package/docs/trust-model.md +126 -126
- package/docs/unix-principles.md +80 -80
- package/docs/vendor-manifest-loadability.7.md +20 -20
- package/docs/verifier-gated-commit.7.md +16 -16
- package/docs/web-desktop-workbench.7.md +73 -65
- package/docs/worker-isolation.7.md +34 -37
- package/docs/workflow-app-framework.7.md +38 -38
- package/manifest/plugin.manifest.json +4 -4
- package/package.json +3 -2
- package/scripts/bump-version.js +9 -1
- package/scripts/canonical-apps.js +4 -4
- package/scripts/dogfood-release.js +1 -1
- package/scripts/gen-parity-doc.js +106 -0
- package/scripts/golden-path.js +4 -4
- package/scripts/parity-check.js +27 -57
- package/scripts/release-flow.js +7 -6
- package/scripts/sync-project-index.js +1 -1
- package/dist/verifier-registry.js +0 -46
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cool-workflow",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.84",
|
|
4
4
|
"bin": {
|
|
5
5
|
"cool-workflow": "scripts/cw.js",
|
|
6
6
|
"cw": "scripts/cw.js"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"bugs": {
|
|
15
15
|
"url": "https://github.com/coo1white/cool-workflow/issues"
|
|
16
16
|
},
|
|
17
|
-
"description": "
|
|
17
|
+
"description": "A workflow control plane and run-time you are able to check: it sends out jobs in TypeScript, makes certain of work against facts before it goes through, puts state into fixed records, orders jobs by time, runs jobs again and again, gets a group of agents to do their parts together, and talks MCP. It gives the doing of the work to outside agents — it never runs the models itself.",
|
|
18
18
|
"type": "commonjs",
|
|
19
19
|
"license": "BSD-2-Clause",
|
|
20
20
|
"author": {
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"forward-ref": "node scripts/forward-ref-docs.js",
|
|
52
52
|
"verify:container": "node scripts/verify-container-selfref.js",
|
|
53
53
|
"gen:manifests": "node scripts/gen-manifests.js",
|
|
54
|
+
"gen:parity": "node scripts/gen-parity-doc.js",
|
|
54
55
|
"manifest:load-check": "node test/vendor-manifest-load-smoke.js",
|
|
55
56
|
"parity:check": "node scripts/parity-check.js --check",
|
|
56
57
|
"version:sync": "node scripts/version-sync-check.js",
|
package/scripts/bump-version.js
CHANGED
|
@@ -59,7 +59,15 @@ function main() {
|
|
|
59
59
|
if (!SEMVER.test(next)) fail(`"${next}" is not a x.y.z version`);
|
|
60
60
|
|
|
61
61
|
const current = JSON.parse(fs.readFileSync(path.join(pluginRoot, "package.json"), "utf8")).version;
|
|
62
|
-
if (next === current)
|
|
62
|
+
if (next === current) {
|
|
63
|
+
// Idempotent: re-running the bump for the same version is a no-op (exit 0)
|
|
64
|
+
// rather than a hard fail. Required by `release-flow --cut --version X` when
|
|
65
|
+
// the bump was already landed in a prior PR (e.g. a release-prep PR that
|
|
66
|
+
// committed the version surfaces, then a follow-up fix on top — cut should
|
|
67
|
+
// still be able to commit the verdict and tag without re-bumping).
|
|
68
|
+
process.stdout.write(`bump:version: already at ${next}, no surfaces to update\n`);
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
63
71
|
|
|
64
72
|
const touched = [];
|
|
65
73
|
const note = (rel) => touched.push(rel);
|
|
@@ -83,7 +83,7 @@ const canonicalApps = [
|
|
|
83
83
|
"--source",
|
|
84
84
|
"plugins/cool-workflow/docs/workflow-app-framework.7.md",
|
|
85
85
|
"--scope",
|
|
86
|
-
"Cool Workflow v0.1.
|
|
86
|
+
"Cool Workflow v0.1.84",
|
|
87
87
|
"--freshness",
|
|
88
88
|
"as of release preparation"
|
|
89
89
|
]
|
|
@@ -117,14 +117,14 @@ function main() {
|
|
|
117
117
|
assert.ok(summary, `${app.id} must appear in app list`);
|
|
118
118
|
assert.equal(summary.sourceKind, "app-directory");
|
|
119
119
|
assert.equal(summary.legacy, false);
|
|
120
|
-
assert.equal(summary.version, "0.1.
|
|
120
|
+
assert.equal(summary.version, "0.1.84");
|
|
121
121
|
|
|
122
122
|
const validation = runJson(["app", "validate", manifestPath]);
|
|
123
123
|
assert.equal(validation.valid, true, `${app.id} manifest must validate`);
|
|
124
124
|
|
|
125
125
|
const shown = runJson(["app", "show", app.id]);
|
|
126
126
|
assert.equal(shown.app.id, app.id);
|
|
127
|
-
assert.equal(shown.app.version, "0.1.
|
|
127
|
+
assert.equal(shown.app.version, "0.1.84");
|
|
128
128
|
assert.ok(shown.app.metadata.canonical, `${app.id} must be marked canonical`);
|
|
129
129
|
assert.ok(shown.app.sandboxProfiles.length > 0, `${app.id} must declare sandbox profiles`);
|
|
130
130
|
assertTaskIdsUnique(shown);
|
|
@@ -135,7 +135,7 @@ function main() {
|
|
|
135
135
|
const plan = runJson(["plan", app.id, ...app.args(workspace)]);
|
|
136
136
|
const state = JSON.parse(fs.readFileSync(plan.statePath, "utf8"));
|
|
137
137
|
assert.equal(state.workflow.app.id, app.id);
|
|
138
|
-
assert.equal(state.workflow.app.version, "0.1.
|
|
138
|
+
assert.equal(state.workflow.app.version, "0.1.84");
|
|
139
139
|
assert.equal(state.workflow.app.metadata.canonical, true);
|
|
140
140
|
assert.ok(state.tasks.some((task) => task.requiresEvidence), `${app.id} plan must include evidence gates`);
|
|
141
141
|
assert.ok(state.tasks.every((task) => task.sandboxProfileId), `${app.id} plan must include sandbox hints`);
|
|
@@ -5,7 +5,7 @@ const { spawnSync } = require("node:child_process");
|
|
|
5
5
|
const fs = require("node:fs");
|
|
6
6
|
const path = require("node:path");
|
|
7
7
|
|
|
8
|
-
const TARGET_VERSION = "0.1.
|
|
8
|
+
const TARGET_VERSION = "0.1.84";
|
|
9
9
|
const PREVIOUS_VERSION = "0.1.31";
|
|
10
10
|
const pluginRoot = path.resolve(__dirname, "..");
|
|
11
11
|
const repoRoot = path.resolve(pluginRoot, "..", "..");
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// gen-parity-doc.js — generate the CLI<->MCP parity matrix, counts, and the
|
|
5
|
+
// surface-specific (cli-only / projected) enumerations in
|
|
6
|
+
// docs/cli-mcp-parity.7.md DIRECTLY from the capability registry (the single
|
|
7
|
+
// source of truth). The matrix declares itself "machine-complete by design", so
|
|
8
|
+
// it must BE machine-generated — a hand-maintained table drifts (it did: 132
|
|
9
|
+
// rows vs a 190-capability registry). Mechanism, not policy.
|
|
10
|
+
//
|
|
11
|
+
// node scripts/gen-parity-doc.js # rewrite the generated regions
|
|
12
|
+
// node scripts/gen-parity-doc.js --check # fail closed if the doc drifted
|
|
13
|
+
//
|
|
14
|
+
// Idempotent: re-running with no registry change is a no-op. Only the four
|
|
15
|
+
// marker-delimited regions are touched; all surrounding prose (incl. the Basic
|
|
16
|
+
// English narrative and version trailers) is preserved byte-for-byte.
|
|
17
|
+
|
|
18
|
+
const fs = require("node:fs");
|
|
19
|
+
const path = require("node:path");
|
|
20
|
+
|
|
21
|
+
const pluginRoot = path.resolve(__dirname, "..");
|
|
22
|
+
const DOC = path.join(pluginRoot, "docs", "cli-mcp-parity.7.md");
|
|
23
|
+
const CHECK = process.argv.includes("--check");
|
|
24
|
+
|
|
25
|
+
const { CAPABILITY_REGISTRY } = require(path.join(pluginRoot, "dist", "capability-registry.js"));
|
|
26
|
+
|
|
27
|
+
const NUM_WORDS = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve"];
|
|
28
|
+
const numWord = (n) => NUM_WORDS[n] || String(n);
|
|
29
|
+
|
|
30
|
+
function cliCommand(cap) {
|
|
31
|
+
return cap.cli ? `cw ${cap.cli.path.join(" ")}` : "—";
|
|
32
|
+
}
|
|
33
|
+
function cliDisplay(cap) {
|
|
34
|
+
// Human form used in the prose bullets (e.g. "schedule daemon", "demo tamper").
|
|
35
|
+
return cap.cli ? cap.cli.path.join(" ") : cap.capability;
|
|
36
|
+
}
|
|
37
|
+
function mcpTool(cap) {
|
|
38
|
+
return cap.mcp ? cap.mcp.tool : "—";
|
|
39
|
+
}
|
|
40
|
+
function payload(cap) {
|
|
41
|
+
if (cap.surface !== "both") return cap.surface;
|
|
42
|
+
return cap.payloadIdentical === false ? "projected" : "identical";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function buildCount() {
|
|
46
|
+
const caps = CAPABILITY_REGISTRY.length;
|
|
47
|
+
const tools = CAPABILITY_REGISTRY.filter((c) => c.mcp && c.mcp.tool).length;
|
|
48
|
+
return `machine-complete by design: ${caps} capabilities, ${tools} MCP tools.`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function buildTable() {
|
|
52
|
+
const lines = [
|
|
53
|
+
"| Capability | CLI command | MCP tool | Core entry | Surface | Payload |",
|
|
54
|
+
"| --- | --- | --- | --- | --- | --- |"
|
|
55
|
+
];
|
|
56
|
+
for (const cap of CAPABILITY_REGISTRY) {
|
|
57
|
+
lines.push(`| \`${cap.capability}\` | \`${cliCommand(cap)}\` | \`${mcpTool(cap)}\` | \`${cap.entry}\` | ${payload(cap) === "cli-only" || cap.surface !== "both" ? cap.surface : "both"} | ${payload(cap)} |`);
|
|
58
|
+
}
|
|
59
|
+
return lines.join("\n");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildCliOnly() {
|
|
63
|
+
const caps = CAPABILITY_REGISTRY.filter((c) => c.surface === "cli-only");
|
|
64
|
+
const head = `${numWord(caps.length)} ${caps.length === 1 ? "capability is" : "capabilities are"} CLI-only:`;
|
|
65
|
+
const bullets = caps.map((c) => `- \`${cliDisplay(c)}\` — ${c.reason || "(reason recorded in the registry)"}`);
|
|
66
|
+
return [head, "", ...bullets].join("\n");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function buildProjected() {
|
|
70
|
+
const caps = CAPABILITY_REGISTRY.filter((c) => c.surface === "both" && c.payloadIdentical === false);
|
|
71
|
+
const head = `${numWord(caps.length)} ${caps.length === 1 ? "capability is" : "capabilities are"} payload-divergent on purpose (\`projected\`):`;
|
|
72
|
+
const bullets = caps.map((c) => `- \`${c.capability}\` — ${c.reason || "(reason recorded in the registry)"}`);
|
|
73
|
+
return [head, "", ...bullets].join("\n");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const REGIONS = {
|
|
77
|
+
count: buildCount(),
|
|
78
|
+
table: buildTable(),
|
|
79
|
+
cliOnly: buildCliOnly(),
|
|
80
|
+
projected: buildProjected()
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
function replaceRegion(text, name, body) {
|
|
84
|
+
const begin = `<!-- gen:parity:${name} -->`;
|
|
85
|
+
const end = `<!-- /gen:parity:${name} -->`;
|
|
86
|
+
const re = new RegExp(`${begin}[\\s\\S]*?${end}`);
|
|
87
|
+
if (!re.test(text)) {
|
|
88
|
+
throw new Error(`marker ${begin} … ${end} not found in cli-mcp-parity.7.md — add the markers once, then re-run.`);
|
|
89
|
+
}
|
|
90
|
+
return text.replace(re, `${begin}\n${body}\n${end}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const original = fs.readFileSync(DOC, "utf8");
|
|
94
|
+
let next = original;
|
|
95
|
+
for (const [name, body] of Object.entries(REGIONS)) next = replaceRegion(next, name, body);
|
|
96
|
+
|
|
97
|
+
if (CHECK) {
|
|
98
|
+
if (next !== original) {
|
|
99
|
+
process.stderr.write("gen-parity-doc: docs/cli-mcp-parity.7.md is OUT OF SYNC with the capability registry. Run `node scripts/gen-parity-doc.js` and commit.\n");
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
process.stdout.write("gen-parity-doc: cli-mcp-parity.7.md in sync with the registry.\n");
|
|
103
|
+
} else {
|
|
104
|
+
if (next !== original) fs.writeFileSync(DOC, next);
|
|
105
|
+
process.stdout.write(`gen-parity-doc: ${next !== original ? "updated" : "unchanged"} — ${CAPABILITY_REGISTRY.length} capabilities, ${CAPABILITY_REGISTRY.filter((c) => c.mcp).length} MCP tools.\n`);
|
|
106
|
+
}
|
package/scripts/golden-path.js
CHANGED
|
@@ -33,7 +33,7 @@ function main() {
|
|
|
33
33
|
const appValidation = runJson(["app", "validate", "end-to-end-golden-path"], pluginRoot);
|
|
34
34
|
assert.equal(appValidation.valid, true);
|
|
35
35
|
assert.equal(appValidation.summary.id, "end-to-end-golden-path");
|
|
36
|
-
assert.equal(appValidation.summary.version, "0.1.
|
|
36
|
+
assert.equal(appValidation.summary.version, "0.1.84");
|
|
37
37
|
|
|
38
38
|
const plan = runJson(
|
|
39
39
|
[
|
|
@@ -42,7 +42,7 @@ function main() {
|
|
|
42
42
|
"--repo",
|
|
43
43
|
tmp,
|
|
44
44
|
"--question",
|
|
45
|
-
"Prove the deterministic v0.1.
|
|
45
|
+
"Prove the deterministic v0.1.84 end-to-end golden path."
|
|
46
46
|
],
|
|
47
47
|
pluginRoot
|
|
48
48
|
);
|
|
@@ -52,7 +52,7 @@ function main() {
|
|
|
52
52
|
|
|
53
53
|
let state = readJson(plan.statePath);
|
|
54
54
|
assert.equal(state.workflow.app.id, "end-to-end-golden-path");
|
|
55
|
-
assert.equal(state.workflow.app.version, "0.1.
|
|
55
|
+
assert.equal(state.workflow.app.version, "0.1.84");
|
|
56
56
|
assert.equal(state.loopStage, "interpret");
|
|
57
57
|
|
|
58
58
|
const dispatch = runJson(["dispatch", plan.runId, "--limit", "1", "--sandbox", "readonly"], tmp);
|
|
@@ -195,7 +195,7 @@ function main() {
|
|
|
195
195
|
assert.equal(reportPath, plan.reportPath);
|
|
196
196
|
assert.ok(fs.existsSync(reportPath));
|
|
197
197
|
const report = fs.readFileSync(reportPath, "utf8");
|
|
198
|
-
assert.match(report, /Workflow App: end-to-end-golden-path@0\.1\.
|
|
198
|
+
assert.match(report, /Workflow App: end-to-end-golden-path@0\.1\.84/);
|
|
199
199
|
assert.match(report, /## Candidates/);
|
|
200
200
|
assert.match(report, /## Trust Audit/);
|
|
201
201
|
assert.match(report, /## Acceptance Rationale/);
|
package/scripts/parity-check.js
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
// FAIL CLOSED ON DRIFT (BSD discipline, same shape as gen-manifests --check):
|
|
10
10
|
// 1. STATIC parity — the declared capability registry must exactly match the
|
|
11
11
|
// live MCP tool list (tools/list) and the CLI dispatch tokens parsed from
|
|
12
|
-
//
|
|
13
|
-
// declared in src/capability-registry.ts, is release-blocking.
|
|
12
|
+
// the built CLI dispatch surface. A tool or command on one surface but not
|
|
13
|
+
// the other, or not declared in src/capability-registry.ts, is release-blocking.
|
|
14
14
|
// 2. PAYLOAD parity — for every capability declared `payloadIdentical`, the
|
|
15
15
|
// `cw <cmd> --json` payload must equal the `cw_<tool>` MCP result on a real
|
|
16
16
|
// bootstrap run (whitespace + generation-moment ISO timestamps aside).
|
|
@@ -31,50 +31,16 @@ const cli = path.join(pluginRoot, "dist", "cli.js");
|
|
|
31
31
|
const mcpServer = path.join(pluginRoot, "dist", "mcp-server.js");
|
|
32
32
|
const registry = require(path.join(pluginRoot, "dist", "capability-registry.js"));
|
|
33
33
|
|
|
34
|
-
// Read capabilities that are safe to probe on a freshly planned run with only a
|
|
35
|
-
// runId (or no args). Each: [capability, cliArgvAfterRunId, mcpTool].
|
|
36
|
-
// runId-less reads use [] for cli args and {} mcp args.
|
|
37
|
-
const RUN_PROBES = [
|
|
38
|
-
["status", ["--json"], "cw_status"],
|
|
39
|
-
["operator.status", ["--json"], "cw_operator_status"],
|
|
40
|
-
["operator.report", ["--json"], "cw_operator_report"],
|
|
41
|
-
["graph", ["--json"], "cw_operator_graph"],
|
|
42
|
-
["report", ["--json"], "cw_report"],
|
|
43
|
-
["next", [], "cw_next"],
|
|
44
|
-
["state.check", [], "cw_state_check"],
|
|
45
|
-
["contract.show", [], "cw_contract_show"],
|
|
46
|
-
["node.list", [], "cw_node_list"],
|
|
47
|
-
["node.graph", ["--json"], "cw_node_graph"],
|
|
48
|
-
["worker.summary", ["--json"], "cw_worker_summary"],
|
|
49
|
-
["candidate.summary", ["--json"], "cw_candidate_summary"],
|
|
50
|
-
["feedback.summary", ["--json"], "cw_feedback_summary"],
|
|
51
|
-
["commit.summary", ["--json"], "cw_commit_summary"],
|
|
52
|
-
["audit.summary", [], "cw_audit_summary"],
|
|
53
|
-
["multi-agent.summary", ["--json"], "cw_multi_agent_summary"],
|
|
54
|
-
["workbench.view", ["--json"], "cw_workbench_view"],
|
|
55
|
-
["metrics.show", ["--json"], "cw_metrics_show"],
|
|
56
|
-
["review.status", ["--json"], "cw_review_status"],
|
|
57
|
-
["comment.list", ["--json"], "cw_comment_list"],
|
|
58
|
-
["run.drive", ["--json"], "cw_run_drive"],
|
|
59
|
-
["gc.plan", ["--json"], "cw_gc_plan"],
|
|
60
|
-
["gc.verify", ["--json"], "cw_gc_verify"]
|
|
61
|
-
];
|
|
62
|
-
const GLOBAL_PROBES = [
|
|
63
|
-
["list", "cw_list"],
|
|
64
|
-
["app.list", "cw_app_list"],
|
|
65
|
-
["topology.list", "cw_topology_list"],
|
|
66
|
-
["sandbox.list", "cw_sandbox_list"],
|
|
67
|
-
["backend.list", "cw_backend_list"],
|
|
68
|
-
["backend.agent.config.show", "cw_backend_agent_config_show"],
|
|
69
|
-
["metrics.summary", "cw_metrics_summary"]
|
|
70
|
-
];
|
|
71
|
-
|
|
72
34
|
function capById(id) {
|
|
73
35
|
const cap = registry.CAPABILITY_REGISTRY.find((entry) => entry.capability === id);
|
|
74
36
|
assert.ok(cap, `probe references unknown capability ${id}`);
|
|
75
37
|
return cap;
|
|
76
38
|
}
|
|
77
39
|
|
|
40
|
+
function jsonFlag(cap) {
|
|
41
|
+
return cap.cli.jsonMode === "flag" ? ["--json"] : [];
|
|
42
|
+
}
|
|
43
|
+
|
|
78
44
|
// ---- 1. static surface parity ---------------------------------------------
|
|
79
45
|
function liveMcpTools() {
|
|
80
46
|
const result = execFileSync(node, [mcpServer], {
|
|
@@ -91,10 +57,14 @@ function liveMcpTools() {
|
|
|
91
57
|
}
|
|
92
58
|
|
|
93
59
|
function cliDispatchTokens() {
|
|
94
|
-
const source = fs.readFileSync(
|
|
60
|
+
const source = cliDispatchSources().map((file) => fs.readFileSync(file, "utf8")).join("\n");
|
|
95
61
|
return [...new Set([...source.matchAll(/case\s+"([^"]+)":/g)].map((match) => match[1]))];
|
|
96
62
|
}
|
|
97
63
|
|
|
64
|
+
function cliDispatchSources() {
|
|
65
|
+
return [cli, path.join(pluginRoot, "dist", "cli", "command-surface.js")].filter((file) => fs.existsSync(file));
|
|
66
|
+
}
|
|
67
|
+
|
|
98
68
|
// ---- 2. payload identity ---------------------------------------------------
|
|
99
69
|
// Generation-moment ISO timestamps are presentation metadata, not capability
|
|
100
70
|
// data: the same field carries the wall-clock instant of the render. Neutralize
|
|
@@ -144,31 +114,28 @@ async function payloadParity() {
|
|
|
144
114
|
const runId = plan.runId;
|
|
145
115
|
const mismatches = [];
|
|
146
116
|
const checked = [];
|
|
117
|
+
const probePlan = registry.payloadProbePlan();
|
|
118
|
+
assert.deepEqual(probePlan.unclassified, [], "payload probe classification has unclassified capabilities");
|
|
119
|
+
assert.deepEqual(probePlan.duplicateClassifications, [], "payload probe classification has duplicates");
|
|
120
|
+
assert.deepEqual(probePlan.invalidClassifications, [], "payload probe classification names non-payload capabilities");
|
|
147
121
|
const mcp = openMcp();
|
|
148
122
|
try {
|
|
149
123
|
await mcp.rpc("initialize", {});
|
|
150
|
-
for (const
|
|
151
|
-
const cap = capById(capability);
|
|
152
|
-
assert.equal(cap.mcp.tool, mcpTool, `probe/registry MCP tool mismatch for ${capability}`);
|
|
124
|
+
for (const target of registry.payloadProbeTargets()) {
|
|
125
|
+
const cap = capById(target.capability);
|
|
153
126
|
// jsonMode is the single source for the CLI's --json policy; this probe only
|
|
154
127
|
// appends --json for "flag" verbs and JSON.parse-es the result. The human
|
|
155
128
|
// rendering and "default"-verb no-flag JSON are pinned to cap.cli.jsonMode by
|
|
156
129
|
// the companion test/cli-jsonmode-parity-smoke.js, so cli.ts can't silently
|
|
157
130
|
// re-encode that policy by hand and drift from this registry data.
|
|
158
|
-
const cliArgv =
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
checked.push(capability);
|
|
162
|
-
if (canonical(cliOut) !== canonical(mcpOut)) mismatches.push(capability);
|
|
163
|
-
}
|
|
164
|
-
for (const [capability, extraArgv, mcpTool] of RUN_PROBES) {
|
|
165
|
-
const cap = capById(capability);
|
|
166
|
-
assert.equal(cap.mcp.tool, mcpTool, `probe/registry MCP tool mismatch for ${capability}`);
|
|
167
|
-
const cliArgv = [...cap.cli.path, runId, ...extraArgv];
|
|
131
|
+
const cliArgv = target.kind === "run"
|
|
132
|
+
? [...cap.cli.path, runId, ...jsonFlag(cap)]
|
|
133
|
+
: [...cap.cli.path, ...jsonFlag(cap)];
|
|
168
134
|
const cliOut = JSON.parse(execFileSync(node, [cli, ...cliArgv], { cwd: workspace, encoding: "utf8" }));
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
135
|
+
const mcpArgs = target.kind === "run" ? { cwd: workspace, runId } : { cwd: workspace };
|
|
136
|
+
const mcpOut = await mcp.tool(cap.mcp.tool, mcpArgs);
|
|
137
|
+
checked.push(target.capability);
|
|
138
|
+
if (canonical(cliOut) !== canonical(mcpOut)) mismatches.push(target.capability);
|
|
172
139
|
}
|
|
173
140
|
} finally {
|
|
174
141
|
mcp.server.kill();
|
|
@@ -204,6 +171,9 @@ async function main() {
|
|
|
204
171
|
if (report.missingCliTokens.length) lines.push(` - registry declares CLI tokens absent from dist/cli.js: ${report.missingCliTokens.join(", ")}`);
|
|
205
172
|
if (report.undeclaredCliTokens.length) lines.push(` - dist/cli.js dispatches tokens not declared in the registry: ${report.undeclaredCliTokens.join(", ")}`);
|
|
206
173
|
if (report.reasonlessExceptions.length) lines.push(` - surface-specific / payload-divergent capabilities missing a recorded reason: ${report.reasonlessExceptions.join(", ")}`);
|
|
174
|
+
if (report.payloadProbeUnclassified.length) lines.push(` - payload-identical capabilities neither probed nor deferred: ${report.payloadProbeUnclassified.join(", ")}`);
|
|
175
|
+
if (report.payloadProbeDuplicateClassifications.length) lines.push(` - payload probe duplicate classifications: ${report.payloadProbeDuplicateClassifications.join(", ")}`);
|
|
176
|
+
if (report.payloadProbeInvalidClassifications.length) lines.push(` - payload probe classifications for invalid capabilities: ${report.payloadProbeInvalidClassifications.join(", ")}`);
|
|
207
177
|
if (report.registryLint.length) lines.push(` - registry lint: ${report.registryLint.join("; ")}`);
|
|
208
178
|
if (payload.mismatches.length) lines.push(` - cw --json != cw_<tool> payload for: ${payload.mismatches.join(", ")}`);
|
|
209
179
|
lines.push("Reconcile src/capability-registry.ts, cli.ts, and mcp-server.ts so both surfaces render one data source.\n");
|
package/scripts/release-flow.js
CHANGED
|
@@ -124,6 +124,7 @@ function reviewerPromptBody() {
|
|
|
124
124
|
|
|
125
125
|
function buildReviewerInput(resultPath) {
|
|
126
126
|
const action = MODE_CUT ? `tag v${cutVersion || "<next>"}` : "check (no tag)";
|
|
127
|
+
const resultDisplay = path.relative(repoRoot, resultPath);
|
|
127
128
|
return [
|
|
128
129
|
reviewerPromptBody(),
|
|
129
130
|
"",
|
|
@@ -133,11 +134,11 @@ function buildReviewerInput(resultPath) {
|
|
|
133
134
|
`- Previous tag: ${PREV_TAG || "(none)"}`,
|
|
134
135
|
`- Diff range: ${PREV_TAG ? `${PREV_TAG}..HEAD` : "(no previous tag)"}`,
|
|
135
136
|
`- Proposed action: ${action}`,
|
|
136
|
-
|
|
137
|
+
"- Repo root (run git here): .",
|
|
137
138
|
"",
|
|
138
139
|
"## Required output — write ONLY this file, nothing else",
|
|
139
140
|
`Write your verdict to this exact path:`,
|
|
140
|
-
` ${
|
|
141
|
+
` ${resultDisplay}`,
|
|
141
142
|
"First line MUST be exactly one of:",
|
|
142
143
|
` APPROVED ${HEAD}`,
|
|
143
144
|
" <one concrete sentence: the user-visible capability this ships>",
|
|
@@ -174,10 +175,10 @@ function delegateReview(resultPath, inputPath) {
|
|
|
174
175
|
}
|
|
175
176
|
|
|
176
177
|
const subMap = {
|
|
177
|
-
input: inputPath,
|
|
178
|
-
manifest: inputPath,
|
|
179
|
-
result: resultPath,
|
|
180
|
-
workerDir:
|
|
178
|
+
input: path.relative(repoRoot, inputPath),
|
|
179
|
+
manifest: path.relative(repoRoot, inputPath),
|
|
180
|
+
result: path.relative(repoRoot, resultPath),
|
|
181
|
+
workerDir: ".",
|
|
181
182
|
model: cfg.model || "",
|
|
182
183
|
prompt: fs.readFileSync(inputPath, "utf8")
|
|
183
184
|
};
|
|
@@ -311,7 +311,7 @@ function ensureLine(file, line, afterHeading) {
|
|
|
311
311
|
}
|
|
312
312
|
|
|
313
313
|
function detectObsidianVault() {
|
|
314
|
-
const candidate = path.join(process.env.HOME || "", "Documents", "
|
|
314
|
+
const candidate = process.env.CW_OBSIDIAN_VAULT || path.join(process.env.HOME || "", "Documents", "Cool Workflow");
|
|
315
315
|
return fs.existsSync(path.join(candidate, ".obsidian")) ? candidate : "";
|
|
316
316
|
}
|
|
317
317
|
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// Verifier Registry — open pluggability for commit-gate verifiers.
|
|
3
|
-
//
|
|
4
|
-
// BSD discipline (mechanism separate from policy):
|
|
5
|
-
// - MECHANISM: a Map<string, Verifier> with register + resolve.
|
|
6
|
-
// - POLICY: which verifiers exist is declared via registerVerifier() at load time.
|
|
7
|
-
// - FAIL CLOSED: unknown verifier id → named refusal.
|
|
8
|
-
// - COMPOSABLE: multiple verifiers can be chained; each runs in registration order.
|
|
9
|
-
//
|
|
10
|
-
// From v0.1.58: the built-in verifier (validateResultEnvelope, validateRunGates,
|
|
11
|
-
// hasGroundedEvidence) remains the default. Registered verifiers run BEFORE the
|
|
12
|
-
// default — they can block or add evidence but cannot override the default gate.
|
|
13
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.registerVerifier = registerVerifier;
|
|
15
|
-
exports.getVerifier = getVerifier;
|
|
16
|
-
exports.listVerifiers = listVerifiers;
|
|
17
|
-
exports.runAllVerifiers = runAllVerifiers;
|
|
18
|
-
const _verifierRegistry = new Map();
|
|
19
|
-
function registerVerifier(verifier) {
|
|
20
|
-
_verifierRegistry.set(verifier.id, verifier);
|
|
21
|
-
}
|
|
22
|
-
function getVerifier(id) {
|
|
23
|
-
return _verifierRegistry.get(id);
|
|
24
|
-
}
|
|
25
|
-
function listVerifiers() {
|
|
26
|
-
return [..._verifierRegistry.values()];
|
|
27
|
-
}
|
|
28
|
-
/** Run all registered verifiers against a run. Returns the aggregated verdict:
|
|
29
|
-
* "pass" if all pass, "block" if any block (first block reason wins). */
|
|
30
|
-
async function runAllVerifiers(input) {
|
|
31
|
-
const reasons = [];
|
|
32
|
-
const evidence = [];
|
|
33
|
-
for (const verifier of _verifierRegistry.values()) {
|
|
34
|
-
const result = await verifier.verify(input);
|
|
35
|
-
if (result.evidence)
|
|
36
|
-
evidence.push(...result.evidence);
|
|
37
|
-
if (result.verdict === "block") {
|
|
38
|
-
reasons.push(`[${verifier.id}] ${result.reason || "blocked by verifier"}`);
|
|
39
|
-
}
|
|
40
|
-
else if (result.verdict === "warn") {
|
|
41
|
-
reasons.push(`[${verifier.id}] warn: ${result.reason || "warning"}`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
const blocked = reasons.some((r) => !r.includes("warn:"));
|
|
45
|
-
return { verdict: blocked ? "block" : "pass", reasons, evidence };
|
|
46
|
-
}
|