cool-workflow 0.2.0 → 0.2.2

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.
Files changed (141) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +7 -5
  4. package/apps/architecture-review/app.json +2 -2
  5. package/apps/architecture-review/workflow.js +12 -12
  6. package/apps/architecture-review-fast/app.json +1 -1
  7. package/apps/end-to-end-golden-path/app.json +1 -1
  8. package/apps/pr-review-fix-ci/app.json +1 -1
  9. package/apps/release-cut/app.json +1 -1
  10. package/apps/research-synthesis/app.json +1 -1
  11. package/dist/cli/dispatch.js +20 -88
  12. package/dist/cli/parseargv.js +6 -3
  13. package/dist/core/capability-data.js +270 -0
  14. package/dist/core/capability-table.js +15 -3519
  15. package/dist/core/format/help.js +30 -7
  16. package/dist/core/hash.js +1 -1
  17. package/dist/core/multi-agent/candidate-scoring.js +1 -1
  18. package/dist/core/multi-agent/collaboration.js +3 -2
  19. package/dist/core/multi-agent/coordinator.js +4 -3
  20. package/dist/core/multi-agent/eval-replay.js +1 -1
  21. package/dist/core/multi-agent/runtime.js +2 -2
  22. package/dist/core/multi-agent/trust-policy.js +1 -1
  23. package/dist/core/pipeline/commit-gate.js +2 -1
  24. package/dist/core/pipeline/contract.js +1 -1
  25. package/dist/core/pipeline/drive-decide.js +1 -1
  26. package/dist/core/state/contract-migration.js +1 -1
  27. package/dist/core/state/migrations.js +2 -2
  28. package/dist/core/state/node-snapshot.js +1 -1
  29. package/dist/core/state/state-explosion/digest.js +3 -2
  30. package/dist/core/state/state-explosion/graph.js +13 -12
  31. package/dist/core/state/state-explosion/helpers.js +5 -4
  32. package/dist/core/state/state-explosion/report.js +1 -1
  33. package/dist/core/state/state-explosion/size.js +1 -1
  34. package/dist/core/state/state-node.js +2 -2
  35. package/dist/core/state/types.js +1 -1
  36. package/dist/core/trust/ledger.js +3 -2
  37. package/dist/core/trust/telemetry-attestation.js +3 -3
  38. package/dist/core/trust/telemetry-ledger.js +2 -2
  39. package/dist/core/types/execution-backend.js +18 -0
  40. package/dist/core/types/observability.js +7 -0
  41. package/dist/core/util/collate.js +23 -0
  42. package/dist/core/version.js +1 -1
  43. package/dist/core/workflow-apps/app-schema.js +1 -1
  44. package/dist/mcp/dispatch.js +1 -1
  45. package/dist/mcp/server.js +1 -1
  46. package/dist/shell/agent-config.js +1 -1
  47. package/dist/shell/audit-cli.js +46 -2
  48. package/dist/shell/commit-summary.js +2 -1
  49. package/dist/shell/doctor.js +17 -4
  50. package/dist/shell/drive.js +47 -19
  51. package/dist/shell/evidence-reasoning.js +4 -3
  52. package/dist/shell/execution-backend/agent.js +42 -10
  53. package/dist/shell/execution-backend/ci.js +1 -1
  54. package/dist/shell/execution-backend/container.js +1 -1
  55. package/dist/shell/execution-backend/envelopes.js +1 -1
  56. package/dist/shell/execution-backend/local.js +1 -1
  57. package/dist/shell/execution-backend/probes.js +1 -1
  58. package/dist/shell/execution-backend/registry.js +3 -2
  59. package/dist/shell/execution-backend/remote.js +1 -1
  60. package/dist/shell/execution-backend/types.js +0 -9
  61. package/dist/shell/fs-atomic.js +15 -2
  62. package/dist/shell/ledger-io.js +1 -1
  63. package/dist/shell/metrics-cli.js +4 -2
  64. package/dist/shell/multi-agent-host.js +1 -1
  65. package/dist/shell/multi-agent-operator-ux.js +4 -3
  66. package/dist/shell/observability.js +12 -11
  67. package/dist/shell/onramp.js +17 -1
  68. package/dist/shell/operator-ux-text.js +2 -1
  69. package/dist/shell/operator-ux.js +7 -6
  70. package/dist/shell/pipeline-cli.js +82 -72
  71. package/dist/shell/reclamation-io.js +3 -2
  72. package/dist/shell/registry-cli.js +15 -0
  73. package/dist/shell/report.js +2 -1
  74. package/dist/shell/run-registry-io.js +1 -1
  75. package/dist/shell/run-store.js +17 -0
  76. package/dist/shell/sandbox-profile.js +1 -1
  77. package/dist/shell/scheduler-io.js +46 -37
  78. package/dist/shell/scheduling-io.js +32 -22
  79. package/dist/shell/state-explosion-cli.js +2 -1
  80. package/dist/shell/telemetry-ledger-io.js +1 -1
  81. package/dist/shell/term.js +1 -1
  82. package/dist/shell/topology-io.js +2 -1
  83. package/dist/shell/trust-audit.js +45 -5
  84. package/dist/shell/workbench-host.js +9 -1
  85. package/dist/shell/workbench.js +6 -11
  86. package/dist/shell/worker-cli.js +9 -1
  87. package/dist/shell/worker-isolation.js +34 -9
  88. package/dist/shell/workflow-app-loader.js +3 -2
  89. package/dist/wiring/capability-table/basics.js +66 -0
  90. package/dist/wiring/capability-table/exec-backend.js +162 -0
  91. package/dist/wiring/capability-table/index.js +42 -0
  92. package/dist/wiring/capability-table/multi-agent.js +623 -0
  93. package/dist/wiring/capability-table/parity.js +466 -0
  94. package/dist/wiring/capability-table/pipeline.js +280 -0
  95. package/dist/wiring/capability-table/registry-core.js +189 -0
  96. package/dist/wiring/capability-table/reporting.js +394 -0
  97. package/dist/wiring/capability-table/scheduling-registry.js +558 -0
  98. package/dist/wiring/capability-table/state.js +173 -0
  99. package/dist/wiring/capability-table/trust-ledger.js +134 -0
  100. package/dist/wiring/capability-table/workflow-apps.js +366 -0
  101. package/docs/agent-delegation-drive.7.md +4 -0
  102. package/docs/cli-mcp-parity.7.md +9 -2
  103. package/docs/contract-migration-tooling.7.md +4 -0
  104. package/docs/control-plane-scheduling.7.md +4 -0
  105. package/docs/durable-state-and-locking.7.md +24 -0
  106. package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
  107. package/docs/execution-backends.7.md +4 -0
  108. package/docs/index.md +1 -0
  109. package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
  110. package/docs/multi-agent-eval-replay-harness.7.md +4 -0
  111. package/docs/multi-agent-operator-ux.7.md +4 -0
  112. package/docs/node-snapshot-diff-replay.7.md +4 -0
  113. package/docs/observability-cost-accounting.7.md +6 -1
  114. package/docs/project-index.md +30 -5
  115. package/docs/real-execution-backends.7.md +4 -0
  116. package/docs/release-and-migration.7.md +4 -0
  117. package/docs/release-tooling.7.md +4 -0
  118. package/docs/remote-source-review.7.md +4 -4
  119. package/docs/report-verifiable-bundle.7.md +1 -1
  120. package/docs/run-registry-control-plane.7.md +4 -0
  121. package/docs/run-retention-reclamation.7.md +4 -0
  122. package/docs/security-trust-hardening.7.md +33 -1
  123. package/docs/state-explosion-management.7.md +4 -0
  124. package/docs/team-collaboration.7.md +4 -0
  125. package/docs/trust-audit-anchor.7.md +69 -0
  126. package/docs/web-desktop-workbench.7.md +15 -1
  127. package/manifest/plugin.manifest.json +1 -1
  128. package/package.json +3 -1
  129. package/scripts/agents/agent-adapter-core.js +22 -2
  130. package/scripts/agents/claude-p-agent.js +8 -3
  131. package/scripts/agents/gemini-agent.js +5 -1
  132. package/scripts/agents/opencode-agent.js +5 -1
  133. package/scripts/bump-version.js +24 -2
  134. package/scripts/canonical-apps.js +4 -4
  135. package/scripts/dogfood-release.js +1 -1
  136. package/scripts/golden-path.js +4 -4
  137. package/scripts/purity-baseline.json +68 -0
  138. package/scripts/purity-gate.js +239 -0
  139. package/scripts/release-check.js +8 -1
  140. package/scripts/version-sync-check.js +33 -12
  141. package/workflows/README.md +19 -0
@@ -0,0 +1,68 @@
1
+ {
2
+ "builtinViolations": {
3
+ "core/state/run-paths.ts": [
4
+ "node:fs"
5
+ ],
6
+ "core/trust/telemetry-attestation.ts": [
7
+ "node:fs"
8
+ ]
9
+ },
10
+ "clockEnvCounts": {
11
+ "core/multi-agent/candidate-scoring.ts": {
12
+ "new Date()": 1
13
+ },
14
+ "core/pipeline/runner.ts": {
15
+ "new Date()": 1
16
+ },
17
+ "core/state/migrations.ts": {
18
+ "process.cwd()": 1
19
+ },
20
+ "core/state/state-explosion/digest.ts": {
21
+ "new Date()": 1
22
+ },
23
+ "core/state/state-explosion/graph.ts": {
24
+ "new Date()": 1
25
+ },
26
+ "core/state/state-explosion/report.ts": {
27
+ "new Date()": 1
28
+ },
29
+ "core/state/state-node.ts": {
30
+ "new Date()": 5
31
+ },
32
+ "core/trust/evidence-grounding.ts": {
33
+ "process.env": 3
34
+ }
35
+ },
36
+ "layerViolations": {
37
+ "core/capability-table.ts": [
38
+ "../wiring/capability-table"
39
+ ],
40
+ "wiring/capability-table/exec-backend.ts": [
41
+ "../../cli/io"
42
+ ],
43
+ "wiring/capability-table/multi-agent.ts": [
44
+ "../../cli/io"
45
+ ],
46
+ "wiring/capability-table/pipeline.ts": [
47
+ "../../cli/io"
48
+ ],
49
+ "wiring/capability-table/registry-core.ts": [
50
+ "../../cli/io"
51
+ ],
52
+ "wiring/capability-table/reporting.ts": [
53
+ "../../cli/io"
54
+ ],
55
+ "wiring/capability-table/scheduling-registry.ts": [
56
+ "../../cli/io"
57
+ ],
58
+ "wiring/capability-table/state.ts": [
59
+ "../../cli/io"
60
+ ],
61
+ "wiring/capability-table/trust-ledger.ts": [
62
+ "../../cli/io"
63
+ ],
64
+ "wiring/capability-table/workflow-apps.ts": [
65
+ "../../cli/io"
66
+ ]
67
+ }
68
+ }
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // purity-gate — fail closed if src/ crosses a layer boundary the rebuild
5
+ // docs claim is enforced, or if src/core/** reads an impure primitive
6
+ // (node:fs/child_process/net/http, process.env, process.cwd(), Date.now(),
7
+ // new Date(), Math.random()), beyond a committed, itemized baseline.
8
+ //
9
+ // Why this exists: docs/rebuild/PLAN.md and AGENTS.md both describe core/
10
+ // as pure (no IO) and shell/ as the only impure layer, and say this is
11
+ // "enforced by a lint rule". No such lint exists anywhere in scripts/ or
12
+ // package.json — only `tsc --noEmit`, which does not check import
13
+ // direction. The one place the rule is already broken is the hub itself:
14
+ // core/capability-table.ts imports ~30 things from ../shell.
15
+ //
16
+ // This is a RATCHET, not a clean-slate rule (matching dist-drift-check.js's
17
+ // and version-sync-check.js's style: node + git only, no new dependency).
18
+ // scripts/purity-baseline.json lists every violation that exists TODAY.
19
+ // The gate fails on:
20
+ // - a violation NOT in the baseline (a NEW break), and
21
+ // - a baseline entry that no longer matches reality (STALE — either the
22
+ // violation was fixed, in which case delete the entry, or the count
23
+ // changed, in which case update it consciously).
24
+ // Fail-closed both directions, same as dist-drift-check.js's added/changed/
25
+ // removed three-way diff.
26
+ //
27
+ // Layers (by top-level directory under src/):
28
+ // core/** -> may only import core/** (+ node:path, node:crypto)
29
+ // shell/** -> may import core/** or shell/**
30
+ // wiring/** -> may import core/**, shell/**, or wiring/** (not yet used;
31
+ // reserved for the capability-table split)
32
+ // cli/** -> may import core/**, shell/**, or cli/** (never mcp/**)
33
+ // mcp/** -> may import core/**, shell/**, or mcp/** (never cli/**)
34
+ //
35
+ // Approach: a plain-text specifier scan (import/export-from/require), not a
36
+ // real TS parser — this codebase uses only those three forms, verified
37
+ // against every current violation this baseline lists.
38
+
39
+ const fs = require("node:fs");
40
+ const path = require("node:path");
41
+
42
+ const packageDir = path.resolve(__dirname, "..");
43
+ const srcDir = path.join(packageDir, "src");
44
+ const baselinePath = path.join(__dirname, "purity-baseline.json");
45
+
46
+ const CORE_ALLOWED_BUILTINS = new Set(["node:path", "node:crypto"]);
47
+ const IMPURE_PATTERNS = ["process.env", "process.cwd()", "Date.now()", "new Date()", "Math.random()"];
48
+
49
+ const ALLOWED_TARGET_LAYERS = {
50
+ core: new Set(["core"]),
51
+ shell: new Set(["core", "shell"]),
52
+ wiring: new Set(["core", "shell", "wiring"]),
53
+ cli: new Set(["core", "shell", "cli"]),
54
+ mcp: new Set(["core", "shell", "mcp"]),
55
+ other: new Set(["core", "shell", "wiring", "cli", "mcp", "other"]),
56
+ };
57
+
58
+ function layerOf(relPath) {
59
+ if (relPath.startsWith("core/")) return "core";
60
+ if (relPath.startsWith("shell/")) return "shell";
61
+ if (relPath.startsWith("wiring/")) return "wiring";
62
+ if (relPath.startsWith("cli/") || relPath === "cli") return "cli";
63
+ if (relPath.startsWith("mcp/") || relPath === "mcp-server") return "mcp";
64
+ return "other";
65
+ }
66
+
67
+ function listTsFiles(dir) {
68
+ const out = [];
69
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
70
+ const full = path.join(dir, entry.name);
71
+ if (entry.isDirectory()) out.push(...listTsFiles(full));
72
+ else if (entry.name.endsWith(".ts")) out.push(full);
73
+ }
74
+ return out;
75
+ }
76
+
77
+ // This codebase's file-header comments routinely quote the EXACT patterns
78
+ // this gate looks for, in prose explaining why a file is pure (e.g. "never
79
+ // a top-level `require(\"node:fs\")`", "no fs, no process.env, no clock").
80
+ // A naive text scan reads those quotes as violations. Strip `//` and
81
+ // `/* */` comments before scanning (string/template literal contents are
82
+ // left untouched, so a real `from "..."` clause is never damaged); newlines
83
+ // are preserved so extractSpecifiers' `(?:^|\n)` anchor still lines up.
84
+ function stripComments(text) {
85
+ let out = "";
86
+ let mode = "code"; // "code" | "line" | "block" | quote character
87
+ for (let i = 0; i < text.length; i++) {
88
+ const c = text[i];
89
+ const c2 = text[i + 1];
90
+ if (mode === "line") {
91
+ if (c === "\n") { mode = "code"; out += c; }
92
+ continue;
93
+ }
94
+ if (mode === "block") {
95
+ if (c === "*" && c2 === "/") { mode = "code"; i++; continue; }
96
+ if (c === "\n") out += c;
97
+ continue;
98
+ }
99
+ if (mode === '"' || mode === "'" || mode === "`") {
100
+ out += c;
101
+ if (c === "\\") { out += c2 ?? ""; i++; continue; }
102
+ if (c === mode) mode = "code";
103
+ continue;
104
+ }
105
+ if (c === "/" && c2 === "/") { mode = "line"; i++; continue; }
106
+ if (c === "/" && c2 === "*") { mode = "block"; i++; continue; }
107
+ if (c === '"' || c === "'" || c === "`") { mode = c; out += c; continue; }
108
+ out += c;
109
+ }
110
+ return out;
111
+ }
112
+
113
+ // Every `from "spec"` (import or export-from, type or value — a type-only
114
+ // import still names a module, so it counts the same way a real dependency
115
+ // scan would) plus every `require("spec")`. Non-greedy across `from` finds
116
+ // each import's own clause without needing a real parser.
117
+ function extractSpecifiers(codeText) {
118
+ const specs = [];
119
+ const fromRe = /(?:^|\n)\s*(?:import|export)\s[\s\S]*?from\s+["']([^"']+)["']/g;
120
+ let m;
121
+ while ((m = fromRe.exec(codeText))) specs.push(m[1]);
122
+ const requireRe = /require\(\s*["']([^"']+)["']\s*\)/g;
123
+ while ((m = requireRe.exec(codeText))) specs.push(m[1]);
124
+ return specs;
125
+ }
126
+
127
+ function countOccurrences(text, needle) {
128
+ let count = 0;
129
+ let index = 0;
130
+ while ((index = text.indexOf(needle, index)) !== -1) {
131
+ count += 1;
132
+ index += needle.length;
133
+ }
134
+ return count;
135
+ }
136
+
137
+ function relSrcPath(absPath) {
138
+ return path.relative(srcDir, absPath).split(path.sep).join("/");
139
+ }
140
+
141
+ function scan() {
142
+ const layerViolations = {}; // relFile -> string[] (specs that cross a disallowed layer)
143
+ const builtinViolations = {}; // relFile -> string[] (banned node builtins imported from core)
144
+ const clockEnvCounts = {}; // relFile -> { pattern: count } (core only, count > 0)
145
+
146
+ for (const absFile of listTsFiles(srcDir)) {
147
+ const relFile = relSrcPath(absFile);
148
+ const fileLayer = layerOf(relFile.replace(/\.ts$/, ""));
149
+ const text = stripComments(fs.readFileSync(absFile, "utf8"));
150
+
151
+ for (const spec of extractSpecifiers(text)) {
152
+ if (spec.startsWith(".")) {
153
+ const targetAbs = path.resolve(path.dirname(absFile), spec);
154
+ const targetRel = relSrcPath(targetAbs);
155
+ const targetLayer = layerOf(targetRel);
156
+ if (!ALLOWED_TARGET_LAYERS[fileLayer].has(targetLayer)) {
157
+ (layerViolations[relFile] = layerViolations[relFile] || []).push(spec);
158
+ }
159
+ } else if (spec.startsWith("node:")) {
160
+ if (fileLayer === "core" && !CORE_ALLOWED_BUILTINS.has(spec)) {
161
+ (builtinViolations[relFile] = builtinViolations[relFile] || []).push(spec);
162
+ }
163
+ }
164
+ // Bare package specifiers (no "." or "node:" prefix): this package
165
+ // has zero runtime dependencies, so there are none to check.
166
+ }
167
+
168
+ if (fileLayer === "core") {
169
+ const counts = {};
170
+ for (const pattern of IMPURE_PATTERNS) {
171
+ const n = countOccurrences(text, pattern);
172
+ if (n > 0) counts[pattern] = n;
173
+ }
174
+ if (Object.keys(counts).length > 0) clockEnvCounts[relFile] = counts;
175
+ }
176
+ }
177
+
178
+ for (const bucket of [layerViolations, builtinViolations]) {
179
+ for (const file of Object.keys(bucket)) bucket[file] = [...new Set(bucket[file])].sort();
180
+ }
181
+ return { layerViolations, builtinViolations, clockEnvCounts };
182
+ }
183
+
184
+ function sortedKeys(obj) {
185
+ return Object.keys(obj).sort();
186
+ }
187
+
188
+ function diffListBuckets(actual, baseline, label, problems) {
189
+ const files = new Set([...sortedKeys(actual), ...sortedKeys(baseline)]);
190
+ for (const file of [...files].sort()) {
191
+ const actualList = actual[file] || [];
192
+ const baseList = baseline[file] || [];
193
+ const actualSet = new Set(actualList);
194
+ const baseSet = new Set(baseList);
195
+ const added = actualList.filter((s) => !baseSet.has(s));
196
+ const removed = baseList.filter((s) => !actualSet.has(s));
197
+ for (const spec of added) problems.push(`NEW ${label}: ${file} imports ${spec} (not in purity-baseline.json)`);
198
+ for (const spec of removed) problems.push(`STALE ${label} baseline entry: ${file} no longer imports ${spec} — delete it from purity-baseline.json`);
199
+ }
200
+ }
201
+
202
+ function diffCountBuckets(actual, baseline, problems) {
203
+ const files = new Set([...sortedKeys(actual), ...sortedKeys(baseline)]);
204
+ for (const file of [...files].sort()) {
205
+ const actualCounts = actual[file] || {};
206
+ const baseCounts = baseline[file] || {};
207
+ const patterns = new Set([...Object.keys(actualCounts), ...Object.keys(baseCounts)]);
208
+ for (const pattern of [...patterns].sort()) {
209
+ const a = actualCounts[pattern] || 0;
210
+ const b = baseCounts[pattern] || 0;
211
+ if (a !== b) {
212
+ problems.push(`clock/env count drift: ${file} has ${a} occurrence(s) of "${pattern}", purity-baseline.json expects ${b} — update the baseline consciously (this catches new AND removed occurrences)`);
213
+ }
214
+ }
215
+ }
216
+ }
217
+
218
+ function main() {
219
+ if (!fs.existsSync(baselinePath)) {
220
+ process.stderr.write(`purity gate: missing ${path.relative(packageDir, baselinePath)}\n`);
221
+ process.exit(1);
222
+ }
223
+ const baseline = JSON.parse(fs.readFileSync(baselinePath, "utf8"));
224
+ const actual = scan();
225
+
226
+ const problems = [];
227
+ diffListBuckets(actual.layerViolations, baseline.layerViolations || {}, "layer violation", problems);
228
+ diffListBuckets(actual.builtinViolations, baseline.builtinViolations || {}, "core builtin violation", problems);
229
+ diffCountBuckets(actual.clockEnvCounts, baseline.clockEnvCounts || {}, problems);
230
+
231
+ if (problems.length > 0) {
232
+ process.stderr.write(`purity gate: ${problems.length} problem(s)\n`);
233
+ for (const p of problems) process.stderr.write(` ${p}\n`);
234
+ process.exit(1);
235
+ }
236
+ process.stdout.write("purity gate: src/ matches the committed baseline (no new core/shell layer breaks, no clock/env drift).\n");
237
+ }
238
+
239
+ main();
@@ -39,6 +39,7 @@ const checks = [
39
39
  "docs/run-retention-reclamation.7.md",
40
40
  "docs/durable-state-and-locking.7.md",
41
41
  "docs/security-trust-hardening.7.md",
42
+ "docs/trust-audit-anchor.7.md",
42
43
  "../../CHANGELOG.md",
43
44
  "../../RELEASE.md"
44
45
  ]) {
@@ -58,6 +59,7 @@ const checks = [
58
59
  // `dist:check` builds from src/ AND fails closed if the committed dist/ drifted
59
60
  // from that fresh build — strictly stronger than a bare `npm run build`.
60
61
  { name: "dist freshness", command: ["npm", "run", "dist:check"] },
62
+ { name: "core/shell purity", command: ["npm", "run", "purity:check"] },
61
63
  { name: "type check", command: ["npm", "run", "check"] },
62
64
  { name: "onramp contract", command: ["npm", "run", "onramp:check"] },
63
65
  { name: "run-state schema consistency", command: ["node", "scripts/validate-run-state-schema.js"] },
@@ -66,6 +68,11 @@ const checks = [
66
68
  // (release-gate.sh) forces CW_TEST_CONCURRENCY=1 to stay sequential as the
67
69
  // deterministic backstop.
68
70
  { name: "tests", command: ["npm", "run", "test:ci"] },
71
+ // Pure core/ unit tests (test/*.test.js) — a separate suite from the smoke
72
+ // tests above (see test/run-unit.js's header). --skip-tests also skips
73
+ // this one, matching "tests": ci.yml runs test:unit directly, so this
74
+ // entry would just repeat it a second time in the release-check pass.
75
+ { name: "unit tests", command: ["npm", "run", "test:unit"] },
69
76
  { name: "canonical apps", command: ["npm", "run", "canonical-apps"] },
70
77
  { name: "golden path", command: ["npm", "run", "golden-path"] },
71
78
  { name: "CLI MCP parity", command: ["npm", "run", "parity:check"] },
@@ -88,7 +95,7 @@ function main() {
88
95
  process.stdout.write(`release:check ${check.name} ... `);
89
96
  const started = Date.now();
90
97
  try {
91
- if (skipTests && check.name === "tests") {
98
+ if (skipTests && (check.name === "tests" || check.name === "unit tests")) {
92
99
  results.push({ name: check.name, ok: true, skipped: true, elapsedMs: 0 });
93
100
  process.stdout.write("skipped\n");
94
101
  continue;
@@ -20,7 +20,7 @@ const repoRoot = path.resolve(pluginRoot, "..", "..");
20
20
  // CI checks out HEAD, so HEAD === the tree it is releasing.
21
21
  //
22
22
  // Fallback to the filesystem only when we cannot read from HEAD: not a git work
23
- // tree, or the path is not tracked at HEAD (e.g. the gitignored package-lock).
23
+ // tree, or the path is not tracked at HEAD.
24
24
  // node + git only — no ripgrep (CI portability rule).
25
25
  const insideGitWorkTree = (() => {
26
26
  const r = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd: repoRoot, encoding: "utf8" });
@@ -36,7 +36,7 @@ function readReleaseSource(relativePath) {
36
36
  maxBuffer: 1024 * 1024 * 32
37
37
  });
38
38
  if (r.status === 0) return { text: r.stdout, exists: true, fromHead: true };
39
- // Not tracked at HEAD — fall through to the working tree (e.g. package-lock).
39
+ // Not tracked at HEAD — fall through to the working tree.
40
40
  }
41
41
  const abs = path.join(repoRoot, relativePath);
42
42
  if (!fs.existsSync(abs)) return { text: null, exists: false, fromHead: false };
@@ -56,9 +56,14 @@ const canonicalApps = CANONICAL_APP_IDS;
56
56
  function main() {
57
57
  const checks = [];
58
58
  checkJson("plugins/cool-workflow/package.json", "version", VERSION, checks);
59
- // package-lock.json is a gitignored install artifact (the documented install
60
- // uses `npm install --no-package-lock`), so only validate it when present.
59
+ // package-lock.json is tracked now, but the documented install still works
60
+ // without it (`npm install --no-package-lock`), so only validate it when
61
+ // present. Check BOTH version fields: the top-level one and the root-package
62
+ // entry packages[""].version — the second one is the field npm keeps in step
63
+ // with package.json on `npm install`, and it is the one that went stale
64
+ // (0.1.97) through the v0.2.0/v0.2.1 cuts while only the first was checked.
61
65
  checkJsonIfPresent("plugins/cool-workflow/package-lock.json", "version", VERSION, checks);
66
+ checkNestedJsonIfPresent("plugins/cool-workflow/package-lock.json", ["packages", "", "version"], VERSION, checks);
62
67
  checkJson("plugins/cool-workflow/.codex-plugin/plugin.json", "version", VERSION, checks);
63
68
  checkJson("plugins/cool-workflow/server.json", "version", VERSION, checks);
64
69
  checkNestedJson("plugins/cool-workflow/server.json", ["packages", 0, "version"], VERSION, checks);
@@ -160,7 +165,7 @@ function main() {
160
165
  checkIncludes("plugins/cool-workflow/docs/run-retention-reclamation.7.md", VERSION, checks);
161
166
  checkIncludes("plugins/cool-workflow/docs/index.md", "run-retention-reclamation.7.md", checks);
162
167
  checkIncludes("plugins/cool-workflow/test/run-retention-reclamation-smoke.js", "run-retention-reclamation-smoke", checks);
163
- checkIncludes("plugins/cool-workflow/src/core/capability-table.ts", "gc.plan", checks);
168
+ checkIncludes("plugins/cool-workflow/src/wiring/capability-table/scheduling-registry.ts", "gc.plan", checks);
164
169
  checkIncludes("plugins/cool-workflow/docs/durable-state-and-locking.7.md", "Durable State & Locking", checks);
165
170
  checkIncludes("plugins/cool-workflow/docs/durable-state-and-locking.7.md", VERSION, checks);
166
171
  checkIncludes("plugins/cool-workflow/docs/index.md", "durable-state-and-locking.7.md", checks);
@@ -168,33 +173,37 @@ function main() {
168
173
  checkIncludes("plugins/cool-workflow/src/shell/fs-atomic.ts", "withFileLock", checks);
169
174
  checkIncludes("plugins/cool-workflow/src/shell/drive.ts", "driveStep", checks);
170
175
  checkIncludes("plugins/cool-workflow/dist/shell/drive.js", "driveStep", checks);
171
- checkIncludes("plugins/cool-workflow/src/core/capability-table.ts", "run.drive", checks);
176
+ checkIncludes("plugins/cool-workflow/src/wiring/capability-table/pipeline.ts", "run.drive", checks);
172
177
  checkIncludes("plugins/cool-workflow/src/shell/collaboration-io.ts", "deriveReviewState", checks);
173
178
  checkIncludes("plugins/cool-workflow/dist/shell/collaboration-io.js", "deriveReviewState", checks);
174
- checkIncludes("plugins/cool-workflow/src/core/capability-table.ts", "review.status", checks);
179
+ checkIncludes("plugins/cool-workflow/src/wiring/capability-table/multi-agent.ts", "review.status", checks);
175
180
  checkIncludes("plugins/cool-workflow/src/shell/observability.ts", "deriveMetricsReport", checks);
176
181
  checkIncludes("plugins/cool-workflow/dist/shell/observability.js", "deriveMetricsReport", checks);
177
- checkIncludes("plugins/cool-workflow/src/core/capability-table.ts", "metrics.show", checks);
182
+ checkIncludes("plugins/cool-workflow/src/wiring/capability-table/reporting.ts", "metrics.show", checks);
178
183
  checkIncludes("plugins/cool-workflow/manifest/pricing.policy.json", "schemaVersion", checks);
179
184
  checkIncludes("plugins/cool-workflow/src/shell/workbench.ts", "buildWorkbenchRunView", checks);
180
185
  checkIncludes("plugins/cool-workflow/dist/shell/workbench.js", "buildWorkbenchRunView", checks);
181
- checkIncludes("plugins/cool-workflow/src/core/capability-table.ts", "workbench.view", checks);
186
+ checkIncludes("plugins/cool-workflow/src/wiring/capability-table/reporting.ts", "workbench.view", checks);
182
187
  checkIncludes("plugins/cool-workflow/src/shell/execution-backend/registry.ts", "ExecutionBackend", checks);
183
188
  checkIncludes("plugins/cool-workflow/dist/shell/execution-backend/registry.js", "ExecutionBackend", checks);
184
- checkIncludes("plugins/cool-workflow/src/core/capability-table.ts", "backend.list", checks);
189
+ checkIncludes("plugins/cool-workflow/src/wiring/capability-table/exec-backend.ts", "backend.list", checks);
185
190
  checkIncludes("plugins/cool-workflow/src/shell/run-registry-io.ts", "RunRegistry", checks);
186
191
  checkIncludes("plugins/cool-workflow/dist/shell/run-registry-io.js", "RunRegistry", checks);
187
- checkIncludes("plugins/cool-workflow/src/core/capability-table.ts", "registry.refresh", checks);
192
+ checkIncludes("plugins/cool-workflow/src/wiring/capability-table/scheduling-registry.ts", "registry.refresh", checks);
188
193
  checkIncludes("plugins/cool-workflow/package.json", "parity:check", checks);
189
194
  checkIncludes("plugins/cool-workflow/scripts/parity-check.js", "buildParityReport", checks);
190
195
  checkIncludes("plugins/cool-workflow/test/cli-mcp-parity-smoke.js", "cli-mcp-parity-smoke", checks);
191
- checkIncludes("plugins/cool-workflow/src/core/capability-table.ts", "export const REGISTRY", checks);
196
+ checkIncludes("plugins/cool-workflow/src/wiring/capability-table/registry-core.ts", "export const REGISTRY", checks);
192
197
  checkIncludes("plugins/cool-workflow/src/shell/pipeline-cli.ts", "planSummary", checks);
193
198
  checkIncludes("plugins/cool-workflow/dist/core/capability-table.js", "REGISTRY", checks);
194
199
  checkIncludes("plugins/cool-workflow/docs/multi-agent-runtime-core.7.md", "Multi-Agent Runtime Core", checks);
195
200
  checkIncludes("plugins/cool-workflow/docs/dogfood-one-real-repo.7.md", "Dogfood One Real Repo", checks);
196
201
  checkIncludes("plugins/cool-workflow/docs/getting-started.md", "npm run release:check", checks);
197
202
  checkIncludes("plugins/cool-workflow/package.json", "eval:replay", checks);
203
+ checkIncludes("plugins/cool-workflow/docs/trust-audit-anchor.7.md", "Trust Audit Anchor", checks);
204
+ checkIncludes("plugins/cool-workflow/docs/trust-audit-anchor.7.md", VERSION, checks);
205
+ checkIncludes("plugins/cool-workflow/docs/index.md", "trust-audit-anchor.7.md", checks);
206
+ checkIncludes("plugins/cool-workflow/test/trust-audit-anchor-smoke.js", "trust-audit-anchor-smoke", checks);
198
207
  checkIncludes("plugins/cool-workflow/docs/release-and-migration.7.md", VERSION, checks);
199
208
  checkIncludes("CHANGELOG.md", `## ${VERSION}`, checks);
200
209
  checkIncludes("RELEASE.md", VERSION, checks);
@@ -227,6 +236,18 @@ function checkNestedJson(relativePath, keyPath, expected, checks) {
227
236
  checks.push({ path: relativePath, key: keyPath.join("."), value });
228
237
  }
229
238
 
239
+ function checkNestedJsonIfPresent(relativePath, keyPath, expected, checks) {
240
+ const src = readReleaseSource(relativePath);
241
+ if (!src.exists) {
242
+ checks.push({ path: relativePath, key: keyPath.join("."), skipped: "absent" });
243
+ return;
244
+ }
245
+ let value = JSON.parse(src.text);
246
+ for (const key of keyPath) value = value?.[key];
247
+ assert.equal(value, expected, `${relativePath}.${keyPath.join(".")} must be ${expected}`);
248
+ checks.push({ path: relativePath, key: keyPath.join("."), value });
249
+ }
250
+
230
251
  function checkJsonIfPresent(relativePath, key, expected, checks) {
231
252
  const src = readReleaseSource(relativePath);
232
253
  if (!src.exists) {
@@ -0,0 +1,19 @@
1
+ # workflows/ — legacy compatibility surface (pinned, do not remove)
2
+
3
+ `architecture-review.workflow.js` and `research-synthesis.workflow.js` are
4
+ the old, pre-`apps/` workflow-file format. The real, current homes for
5
+ these two workflows are `apps/architecture-review/` and
6
+ `apps/research-synthesis/` — these files exist ONLY so an id from before
7
+ the `apps/` format still resolves.
8
+
9
+ This is deliberate duplication, not drift to clean up: each file here
10
+ registers its OWN distinct id (`legacy-architecture-review`,
11
+ `legacy-research-synthesis`), pinned by
12
+ `v2/conformance/cases/multiagent-app-list.case.js` and
13
+ `plugins/cool-workflow/test/workflow-app-framework-smoke.js`. `cw list` /
14
+ `cw app list` show both the real app and its legacy id side by side
15
+ (`src/shell/workflow-app-loader.ts` discovers both roots).
16
+
17
+ Per this project's own POLA rule, removing or thinning either file would
18
+ change `cw list`'s output bytes — that is a breaking change, only doable
19
+ behind a major-version break, not a routine cleanup.