cool-workflow 0.2.1 → 0.2.3

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 (156) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/apps/architecture-review/app.json +1 -1
  4. package/apps/architecture-review-fast/app.json +1 -1
  5. package/apps/end-to-end-golden-path/app.json +1 -1
  6. package/apps/pr-review-fix-ci/app.json +1 -1
  7. package/apps/release-cut/app.json +1 -1
  8. package/apps/research-synthesis/app.json +1 -1
  9. package/dist/cli/dispatch.js +29 -91
  10. package/dist/cli/entry.js +41 -1
  11. package/dist/cli/io.js +6 -2
  12. package/dist/cli/parseargv.js +6 -2
  13. package/dist/core/capability-data.js +271 -0
  14. package/dist/core/capability-table.js +16 -3504
  15. package/dist/core/format/completion.js +68 -0
  16. package/dist/core/format/help.js +50 -8
  17. package/dist/core/format/safe-json.js +73 -0
  18. package/dist/core/format/state-explosion-text.js +1 -1
  19. package/dist/core/multi-agent/candidate-scoring.js +5 -1
  20. package/dist/core/multi-agent/collaboration.js +6 -5
  21. package/dist/core/multi-agent/coordinator.js +9 -8
  22. package/dist/core/multi-agent/runtime.js +4 -4
  23. package/dist/core/multi-agent/topology.js +3 -3
  24. package/dist/core/pipeline/commit-gate.js +2 -1
  25. package/dist/core/pipeline/dispatch.js +18 -4
  26. package/dist/core/pipeline/drive-decide.js +2 -1
  27. package/dist/core/state/migrations.js +16 -1
  28. package/dist/core/state/state-explosion/digest.js +16 -15
  29. package/dist/core/state/state-explosion/graph.js +26 -19
  30. package/dist/core/state/state-explosion/helpers.js +2 -1
  31. package/dist/core/state/state-explosion/report.js +6 -6
  32. package/dist/core/trust/ledger.js +2 -1
  33. package/dist/core/types/execution-backend.js +18 -0
  34. package/dist/core/types/observability.js +7 -0
  35. package/dist/core/util/collate.js +23 -0
  36. package/dist/core/util/numeric-flag.js +40 -0
  37. package/dist/core/version.js +1 -1
  38. package/dist/mcp/server.js +99 -11
  39. package/dist/shell/audit-cli.js +99 -23
  40. package/dist/shell/commit-summary.js +2 -1
  41. package/dist/shell/coordinator-io.js +73 -13
  42. package/dist/shell/dispatch.js +1 -1
  43. package/dist/shell/doctor.js +80 -1
  44. package/dist/shell/drive.js +269 -59
  45. package/dist/shell/eval-text.js +2 -2
  46. package/dist/shell/evidence-reasoning.js +8 -7
  47. package/dist/shell/execution-backend/agent.js +20 -1
  48. package/dist/shell/execution-backend/container.js +4 -1
  49. package/dist/shell/execution-backend/local.js +4 -1
  50. package/dist/shell/execution-backend/registry.js +2 -1
  51. package/dist/shell/execution-backend/types.js +0 -9
  52. package/dist/shell/feedback-cli.js +6 -6
  53. package/dist/shell/fs-atomic.js +232 -30
  54. package/dist/shell/man-cli.js +6 -0
  55. package/dist/shell/metrics-cli.js +2 -1
  56. package/dist/shell/multi-agent-cli.js +367 -323
  57. package/dist/shell/multi-agent-host.js +9 -9
  58. package/dist/shell/multi-agent-operator-ux.js +82 -39
  59. package/dist/shell/node-store.js +10 -4
  60. package/dist/shell/observability.js +13 -12
  61. package/dist/shell/onramp.js +17 -1
  62. package/dist/shell/operator-ux-text.js +24 -23
  63. package/dist/shell/operator-ux.js +22 -20
  64. package/dist/shell/orchestrator.js +49 -38
  65. package/dist/shell/pipeline-cli.js +168 -111
  66. package/dist/shell/reclamation-io.js +16 -5
  67. package/dist/shell/registry-cli.js +19 -17
  68. package/dist/shell/remote-source.js +13 -8
  69. package/dist/shell/report-cli.js +45 -0
  70. package/dist/shell/report.js +4 -2
  71. package/dist/shell/run-registry-io.js +77 -19
  72. package/dist/shell/run-store.js +91 -2
  73. package/dist/shell/scheduling-io.js +12 -13
  74. package/dist/shell/state-cli.js +2 -7
  75. package/dist/shell/state-explosion-cli.js +19 -10
  76. package/dist/shell/topology-io.js +38 -6
  77. package/dist/shell/trust-audit.js +264 -23
  78. package/dist/shell/trust-policy-io.js +1 -1
  79. package/dist/shell/worker-cli.js +41 -29
  80. package/dist/shell/worker-isolation.js +34 -9
  81. package/dist/shell/workflow-app-loader.js +3 -2
  82. package/dist/wiring/capability-table/basics.js +91 -0
  83. package/dist/wiring/capability-table/exec-backend.js +171 -0
  84. package/dist/wiring/capability-table/index.js +42 -0
  85. package/dist/wiring/capability-table/multi-agent.js +630 -0
  86. package/dist/wiring/capability-table/parity.js +467 -0
  87. package/dist/wiring/capability-table/pipeline.js +292 -0
  88. package/dist/wiring/capability-table/registry-core.js +208 -0
  89. package/dist/wiring/capability-table/reporting.js +435 -0
  90. package/dist/wiring/capability-table/scheduling-registry.js +592 -0
  91. package/dist/wiring/capability-table/state.js +181 -0
  92. package/dist/wiring/capability-table/trust-ledger.js +158 -0
  93. package/dist/wiring/capability-table/workflow-apps.js +381 -0
  94. package/docs/agent-delegation-drive.7.md +4 -0
  95. package/docs/candidate-scoring.7.md +1 -1
  96. package/docs/canonical-workflow-apps.7.md +7 -7
  97. package/docs/cli-mcp-parity.7.md +13 -3
  98. package/docs/contract-migration-tooling.7.md +4 -0
  99. package/docs/control-plane-scheduling.7.md +4 -0
  100. package/docs/coordinator-blackboard.7.md +17 -17
  101. package/docs/dogfood-one-real-repo.7.md +11 -11
  102. package/docs/durable-state-and-locking.7.md +24 -0
  103. package/docs/end-to-end-golden-path.7.md +14 -14
  104. package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
  105. package/docs/execution-backends.7.md +4 -0
  106. package/docs/getting-started.md +37 -37
  107. package/docs/index.md +1 -0
  108. package/docs/multi-agent-cli-mcp-surface.7.md +21 -17
  109. package/docs/multi-agent-eval-replay-harness.7.md +17 -13
  110. package/docs/multi-agent-operator-ux.7.md +23 -19
  111. package/docs/multi-agent-runtime-core.7.md +22 -22
  112. package/docs/multi-agent-topologies.7.md +6 -6
  113. package/docs/multi-agent-trust-policy-audit.7.md +11 -11
  114. package/docs/node-snapshot-diff-replay.7.md +4 -0
  115. package/docs/observability-cost-accounting.7.md +4 -0
  116. package/docs/operator-ux.7.md +34 -34
  117. package/docs/pipeline-runner.7.md +4 -4
  118. package/docs/project-index.md +48 -5
  119. package/docs/real-execution-backends.7.md +4 -0
  120. package/docs/release-and-migration.7.md +5 -1
  121. package/docs/release-tooling.7.md +4 -0
  122. package/docs/routines.md +4 -4
  123. package/docs/run-registry-control-plane.7.md +23 -19
  124. package/docs/run-retention-reclamation.7.md +4 -0
  125. package/docs/scheduled-tasks.md +14 -14
  126. package/docs/security-trust-hardening.7.md +43 -13
  127. package/docs/state-explosion-management.7.md +14 -10
  128. package/docs/team-collaboration.7.md +4 -0
  129. package/docs/trust-audit-anchor.7.md +71 -0
  130. package/docs/unix-principles.md +3 -1
  131. package/docs/verifier-gated-commit.7.md +1 -1
  132. package/docs/web-desktop-workbench.7.md +4 -0
  133. package/docs/workflow-app-framework.7.md +13 -13
  134. package/manifest/plugin.manifest.json +1 -1
  135. package/package.json +4 -2
  136. package/scripts/agents/claude-p-agent.js +2 -2
  137. package/scripts/block-unapproved-tag.sh +23 -2
  138. package/scripts/bump-version.js +24 -2
  139. package/scripts/canonical-apps.js +4 -4
  140. package/scripts/children/batch-delegate-child.js +52 -2
  141. package/scripts/dogfood-release.js +1 -1
  142. package/scripts/fake-date-for-reproduction.js +44 -0
  143. package/scripts/golden-path.js +4 -4
  144. package/scripts/purity-baseline.json +71 -0
  145. package/scripts/purity-gate.js +239 -0
  146. package/scripts/release-check.js +8 -1
  147. package/scripts/release-flow.js +57 -1
  148. package/scripts/verdict-keygen.js +83 -0
  149. package/scripts/verify-bump-reproduction.sh +148 -0
  150. package/scripts/verify-verdict-signature.js +61 -0
  151. package/scripts/version-sync-check.js +33 -12
  152. package/skills/cool-workflow/SKILL.md +9 -9
  153. package/skills/cool-workflow/references/commands.md +89 -88
  154. package/ui/workbench/app.css +37 -1
  155. package/ui/workbench/app.js +124 -6
  156. package/workflows/README.md +19 -0
@@ -258,3 +258,7 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
258
258
  0.2.0
259
259
 
260
260
  0.2.1
261
+
262
+ 0.2.2
263
+
264
+ 0.2.3
@@ -0,0 +1,71 @@
1
+ # Trust Audit Anchor
2
+
3
+ CW v0.2.1 adds the Trust Audit Anchor: a way to see when the END of a run's
4
+ trust-audit log was cut off. The hash chain in `audit/events.jsonl` lets
5
+ `cw audit verify` see an edited event, a removed middle event, a bad line, and
6
+ a mixed-era forgery. But one tamper shape gets past a pure chain walk: take
7
+ the last N lines off the file, and what is left is a shorter but fully
8
+ consistent chain — verify stays green. The anchor closes that hole, and it
9
+ does so without changing any old output byte.
10
+
11
+ ## Design Discipline
12
+
13
+ - Mechanism, not policy: the kernel gives you two small parts — a read of the
14
+ chain head, and a check against a head you saved earlier. WHEN you save a
15
+ head (after a run, before you publish, at export time) is your policy.
16
+ - Fail-closed: a saved head that is not on the chain, or an event count that
17
+ comes up short, makes verify exit non-zero with the distinct check code
18
+ `trust-audit-truncated`. A bad `--expect-head` / `--expect-count` value is
19
+ an error, never a check silently made weaker.
20
+ - POLA: with no anchor flags, `cw audit verify` output is byte-for-byte what
21
+ it was before this feature (no `anchor` key, same checks, same exit rules).
22
+ - Reuse: the anchor rides on the existing eventHash chain — no new file, no
23
+ new state, no new hash form. `cw audit head` is a read-only projection.
24
+ - Parity: `audit.head` is on both front doors (`cw audit head` and the MCP
25
+ tool `cw_audit_head`); the anchor args are on both `cw audit verify` and
26
+ `cw_audit_verify` (`expectHead` / `expectCount`).
27
+
28
+ ## CLI
29
+
30
+ ```text
31
+ node dist/cli.js audit head <run-id>
32
+ # -> { "schemaVersion": 1, "runId": "...", "eventCount": 87,
33
+ # "headHash": "sha256:..." }
34
+
35
+ node dist/cli.js audit verify <run-id> --expect-head <hash> --expect-count <n>
36
+ # green: verified true, "anchor": { ..., "satisfied": true }, exit 0
37
+ # cut tail: verified false, checks carry trust-audit-truncated, exit 1
38
+ ```
39
+
40
+ The head is the hash the NEXT appended event will link from: the last event's
41
+ `eventHash`, or the run's genesis hash when the log is empty. Save the pair
42
+ `{headHash, eventCount}` somewhere the log's writer cannot reach — a CI
43
+ variable, a note in your PR, the output of `cw run export` (the export
44
+ manifest hashes the event log bytes, so an export IS an anchor in file form).
45
+
46
+ ## How the check works
47
+
48
+ `verifyTrustAudit` walks the chain as before, and keeps the trail of head
49
+ hashes it saw (genesis, then the hash after each event). With an anchor:
50
+
51
+ - `expectCount`: the walked log must have at least that many events. Fewer =
52
+ `trust-audit-truncated` (check name `anchor-count`).
53
+ - `expectHead`: the saved head must be ON the trail. A log that was cut and
54
+ then padded back with new events reaches the old count, but the new events
55
+ link from an earlier point — the old head is no longer on the trail, so
56
+ this still fails (check name `anchor-head`).
57
+
58
+ ## Compatibility
59
+
60
+ Trust Audit Anchor is introduced in CW v0.2.1. Fields are additive and
61
+ optional; older run state loads unchanged. A plain `cw audit verify` keeps
62
+ its exact old output. The `anchor` key appears in the JSON only when the
63
+ caller passed an anchor flag.
64
+
65
+ ## See Also
66
+
67
+ security-trust-hardening(7), cli-mcp-parity(7), report-verifiable-bundle(7)
68
+
69
+ 0.2.2
70
+
71
+ 0.2.3
@@ -214,7 +214,9 @@ never in core. Core may send a vendor's stream on; it never reads one apart.
214
214
  **Rule of Silence.** stdout is data, stderr is diagnostics, and a
215
215
  non-interactive run says nothing on success. Anything friendly to a person is TTY-gated
216
216
  and can be turned off; `--json` output is fixed and with nothing added so it goes together
217
- in pipes.
217
+ in pipes. A broken pipe is not an error: when the reader at the other end
218
+ goes away early (`cw list --json | head -1`), the CLI stops quietly with
219
+ exit 0 — no stack trace, no error text (both `--json` and human output).
218
220
 
219
221
  **Fail closed, conservative defaults.** Backends not yet configured probe as
220
222
  `unverified`, telemetry that cannot be verified is made known loudly (or turned away in strict
@@ -114,7 +114,7 @@ commit-selection-not-verified
114
114
  commit-verifier-linkage-mismatch
115
115
  ```
116
116
 
117
- Use `cw.js feedback list <run-id>` and `cw.js node graph <run-id>` to look at
117
+ Use `cw feedback list <run-id>` and `cw node graph <run-id>` to look at
118
118
  the failed transition.
119
119
 
120
120
  ## CANDIDATES
@@ -276,3 +276,7 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
276
276
  0.2.0
277
277
 
278
278
  0.2.1
279
+
280
+ 0.2.2
281
+
282
+ 0.2.3
@@ -16,13 +16,13 @@ const {
16
16
  ```
17
17
 
18
18
  ```bash
19
- node scripts/cw.js app list
20
- node scripts/cw.js app show workflow-app-framework-demo
21
- node scripts/cw.js app validate apps/workflow-app-framework-demo/app.json
22
- node scripts/cw.js app show architecture-review
19
+ cw app list
20
+ cw app show workflow-app-framework-demo
21
+ cw app validate apps/workflow-app-framework-demo/app.json
22
+ cw app show architecture-review
23
23
  npm run canonical-apps
24
- node scripts/cw.js app init my-app --title "My App"
25
- node scripts/cw.js plan my-app --question "What should happen?"
24
+ cw app init my-app --title "My App"
25
+ cw plan my-app --question "What should happen?"
26
26
  ```
27
27
 
28
28
  ## Description
@@ -170,7 +170,7 @@ App loading fails closed. CW checks:
170
170
  - sandbox profile references on the app, workflow, and tasks
171
171
  - compatibility limits against the current CW runtime
172
172
 
173
- `cw.js app validate` prints a structured result. Apps that are not valid return nonzero:
173
+ `cw app validate` prints a structured result. Apps that are not valid return nonzero:
174
174
 
175
175
  ```json
176
176
  {
@@ -190,14 +190,14 @@ CW does not quietly change broken apps into workflows that can run.
190
190
  ## CLI
191
191
 
192
192
  ```bash
193
- node scripts/cw.js app list
194
- node scripts/cw.js app show <app-id>
195
- node scripts/cw.js app validate <path-or-app-id>
196
- node scripts/cw.js app init <app-id> --title "Title"
197
- node scripts/cw.js app package <app-id> --output app.cwapp.json
193
+ cw app list
194
+ cw app show <app-id>
195
+ cw app validate <path-or-app-id>
196
+ cw app init <app-id> --title "Title"
197
+ cw app package <app-id> --output app.cwapp.json
198
198
  ```
199
199
 
200
- `cw.js list`, `cw.js init`, and `cw.js plan` still work the same way. `list` shows
200
+ `cw list`, `cw init`, and `cw plan` still work the same way. `list` shows
201
201
  legacy workflow files and first-class app directories. `plan` takes either
202
202
  kind by id.
203
203
 
@@ -2,7 +2,7 @@
2
2
  "_comment": "SINGLE SOURCE OF TRUTH for every vendor manifest. Edit THIS file, then run `npm run gen:manifests`. Do NOT hand-edit the generated vendor manifests (.claude-plugin/, .codex-plugin/, .agents/, .mcp.json) — `npm run gen:manifests -- --check` (run by release:check) will fail if they drift from this source.",
3
3
  "identity": {
4
4
  "name": "cool-workflow",
5
- "version": "0.2.1",
5
+ "version": "0.2.3",
6
6
  "license": "BSD-2-Clause",
7
7
  "homepage": "https://github.com/coo1white/cool-workflow",
8
8
  "author": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cool-workflow",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "bin": {
5
5
  "cool-workflow": "scripts/cw.js",
6
6
  "cw": "scripts/cw.js"
@@ -43,6 +43,7 @@
43
43
  "build": "tsc -p tsconfig.json",
44
44
  "check": "tsc -p tsconfig.json --noEmit",
45
45
  "dist:check": "node scripts/dist-drift-check.js",
46
+ "purity:check": "node scripts/purity-gate.js",
46
47
  "golden-path": "node scripts/golden-path.js",
47
48
  "dogfood:release": "node scripts/dogfood-release.js",
48
49
  "canonical-apps": "node scripts/canonical-apps.js",
@@ -68,12 +69,13 @@
68
69
  "test:fast": "npm run build --if-present && node dist/cli.js version > /dev/null && node test/run-all.js --concurrency auto",
69
70
  "test:ci": "npm run build && node dist/cli.js version > /dev/null && node test/run-all.js --sample 55",
70
71
  "test:coverage": "node dist/cli.js version > /dev/null && node scripts/coverage-gate.js --concurrency auto",
72
+ "test:unit": "node dist/cli.js version > /dev/null && node test/run-unit.js",
71
73
  "eval:replay": "tsc -p tsconfig.json && node test/multi-agent-eval-replay-harness-smoke.js",
72
74
  "ci": "npm run build && npm run check && npm run test && npm run release:check",
73
75
  "validate:schema": "node scripts/validate-run-state-schema.js"
74
76
  },
75
77
  "devDependencies": {
76
- "@types/node": "^24.0.0",
78
+ "@types/node": "^26.1.1",
77
79
  "typescript": "^5.9.0"
78
80
  },
79
81
  "keywords": [
@@ -50,7 +50,7 @@ const traceEnabled = streamEnabled && Boolean(process.stderr.isTTY);
50
50
  if (!streamEnabled) {
51
51
  // Legacy default: --output-format json and verbatim stdout forwarding. This is
52
52
  // the public wrapper contract existing users already scripted against.
53
- const child = spawnSync("claude", ["-p", prompt, "--output-format", "json", "--allowedTools", "Read,Grep,Glob,Bash"], {
53
+ const child = spawnSync("claude", ["-p", prompt, "--output-format", "json", "--allowedTools", "Read,Grep,Glob"], {
54
54
  encoding: "utf8",
55
55
  maxBuffer: 32 * 1024 * 1024,
56
56
  shell: false
@@ -100,7 +100,7 @@ const transcriptPath = path.join(path.dirname(resultPath), "transcript.md");
100
100
  // non-zero exit.
101
101
  const child = spawn(
102
102
  "claude",
103
- ["-p", prompt, "--output-format", "stream-json", "--verbose", "--allowedTools", "Read,Grep,Glob,Bash"],
103
+ ["-p", prompt, "--output-format", "stream-json", "--verbose", "--allowedTools", "Read,Grep,Glob"],
104
104
  { stdio: ["ignore", "pipe", "pipe"] }
105
105
  );
106
106
 
@@ -4,6 +4,9 @@
4
4
  # require BOTH markers for the current HEAD sha:
5
5
  # .cw-release/gate-<sha>.ok (written by release-gate.sh)
6
6
  # .cw-release/review-<sha>.verdict (written by the release-reviewer agent, must contain APPROVED)
7
+ # If .cw-release/verdict-signing.pub is committed (scripts/verdict-keygen.js),
8
+ # also requires a valid ed25519 signature on the verdict (its .sig sidecar) —
9
+ # opt-in, backward compatible with repos that haven't set up signing yet.
7
10
  # Exit 2 blocks the tool call; stderr is fed back to the agent.
8
11
  set -uo pipefail
9
12
 
@@ -16,8 +19,12 @@ INPUT="$(cat)"
16
19
  CMD="$(printf '%s' "$INPUT" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{process.stdout.write(String(JSON.parse(s)?.tool_input?.command||""))}catch{process.stdout.write("")}})' 2>/dev/null)"
17
20
  [[ -z "$CMD" ]] && exit 0
18
21
 
19
- # Only care about tag creation / tag push
20
- if ! printf '%s' "$CMD" | grep -qE 'git\s+tag\s+(-a\s+)?v|git\s+push\b.*(--tags|refs/tags)'; then
22
+ # Only care about tag creation / tag push. Widened from the original pattern,
23
+ # which missed the single most natural bypass form: `git push origin v0.2.3`
24
+ # (a bare tag-shaped ref, no --tags flag and no refs/tags/ prefix) matched
25
+ # neither side of the old alternation. Also now tolerates a global flag before
26
+ # the tag subcommand (`git -C dir tag ...`) and the `--annotate` long form.
27
+ if ! printf '%s' "$CMD" | grep -qE 'git(\s+-[A-Za-z]+\s+\S+)*\s+tag\s+(-a\s+|--annotate\s+)?v[0-9]|git\s+push\b.*(--tags|refs/tags|[[:space:]]v[0-9])'; then
21
28
  exit 0
22
29
  fi
23
30
 
@@ -36,4 +43,18 @@ if [[ ! -f "$VERDICT" ]] || ! grep -q '^APPROVED' "$VERDICT"; then
36
43
  exit 2
37
44
  fi
38
45
 
46
+ # Once .cw-release/verdict-signing.pub is committed (see scripts/verdict-keygen.js),
47
+ # also require a valid ed25519 signature on the verdict — closing the gap this grep
48
+ # alone can't: a plain APPROVED text match can't tell a real reviewer verdict from
49
+ # one typed by hand. Absent that public key, this block is a no-op (unchanged,
50
+ # grep-only behavior).
51
+ PUBKEY="$REPO_ROOT/.cw-release/verdict-signing.pub"
52
+ if [[ -f "$PUBKEY" ]]; then
53
+ SIG="$VERDICT.sig"
54
+ if [[ ! -f "$SIG" ]] || ! node "$REPO_ROOT/plugins/cool-workflow/scripts/verify-verdict-signature.js" "$VERDICT" "$SIG" "$PUBKEY" >/dev/null 2>&1; then
55
+ echo "BLOCKED: verdict for HEAD $SHA has no valid signature, but verdict-signing.pub is committed so one is required. Do not hand-write or hand-sign a verdict — obtain a real reviewer approval via release-flow.js with CW_RELEASE_VERDICT_PRIVKEY set." >&2
56
+ exit 2
57
+ fi
58
+ fi
59
+
39
60
  exit 0
@@ -46,6 +46,23 @@ function replaceFirstVersionField(absPath, next) {
46
46
  return true;
47
47
  }
48
48
 
49
+ function replaceLockfileVersions(absPath, next) {
50
+ // Move BOTH lockfile version fields with targeted string swaps, so the byte
51
+ // formatting npm wrote is kept as it is: the top-level "version" (the first
52
+ // one in the file) and the root-package entry `"": { "name": ..., "version": ... }`.
53
+ // Dependency entries are not touched — the second swap is keyed on the
54
+ // `"": {` root-package opening, which is present only once.
55
+ const text = fs.readFileSync(absPath, "utf8");
56
+ let updated = text.replace(/"version":\s*"[^"]*"/, `"version": "${next}"`);
57
+ updated = updated.replace(
58
+ /("":\s*\{\s*"name":\s*"[^"]*",\s*"version":\s*)"[^"]*"/,
59
+ `$1"${next}"`
60
+ );
61
+ if (updated === text) return false;
62
+ fs.writeFileSync(absPath, updated);
63
+ return true;
64
+ }
65
+
49
66
  function setNestedVersion(absPath, next) {
50
67
  // For files where the first `"version"` is NOT the right one, parse + set.
51
68
  const json = JSON.parse(fs.readFileSync(absPath, "utf8"));
@@ -75,9 +92,13 @@ function main() {
75
92
  // 1. package.json (the single source of truth version:sync now reads from)
76
93
  if (replaceFirstVersionField(path.join(pluginRoot, "package.json"), next)) note("package.json");
77
94
 
78
- // 2. package-lock.json (gitignored install artifact; only if present)
95
+ // 2. package-lock.json (tracked; only if present). The lockfile keeps the
96
+ // version in TWO places: the top-level "version" and the root-package
97
+ // entry packages[""].version. The old code moved only the first one, so
98
+ // the root-package entry kept an old version till the next `npm install`
99
+ // (the v0.1.97 drift seen after the v0.2.0/v0.2.1 cuts). Move both here.
79
100
  const lock = path.join(pluginRoot, "package-lock.json");
80
- if (fs.existsSync(lock) && replaceFirstVersionField(lock, next)) note("package-lock.json");
101
+ if (fs.existsSync(lock) && replaceLockfileVersions(lock, next)) note("package-lock.json");
81
102
 
82
103
  // 2b. Official MCP Registry server metadata (top-level server version + npm package version).
83
104
  const serverJson = path.join(pluginRoot, "server.json");
@@ -218,6 +239,7 @@ function contentSurfaceFiles(next) {
218
239
  { path: "plugins/cool-workflow/docs/run-retention-reclamation.7.md", needle: next, desc: "run retention doc" },
219
240
  { path: "plugins/cool-workflow/docs/durable-state-and-locking.7.md", needle: next, desc: "durable state doc" },
220
241
  { path: "plugins/cool-workflow/docs/release-and-migration.7.md", needle: next, desc: "release & migration doc" },
242
+ { path: "plugins/cool-workflow/docs/trust-audit-anchor.7.md", needle: next, desc: "trust audit anchor doc" },
221
243
  ];
222
244
  }
223
245
 
@@ -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.2.1",
86
+ "Cool Workflow v0.2.3",
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.2.1");
120
+ assert.equal(summary.version, "0.2.3");
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.2.1");
127
+ assert.equal(shown.app.version, "0.2.3");
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.2.1");
138
+ assert.equal(state.workflow.app.version, "0.2.3");
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`);
@@ -8,7 +8,8 @@
8
8
  //
9
9
  // Reads jobs JSON on stdin, spawns ALL concurrently (shell:false, inherited env —
10
10
  // the agent's own credentials resolve; CW never reads them), per-job SIGTERM at
11
- // timeoutMs + SIGKILL at +5s, caps each captured stdout at 32MB. Streams ONE
11
+ // timeoutMs + SIGKILL at +5s, caps each captured stdout at 32MB RAW and each
12
+ // written NDJSON line at 33MB SERIALIZED (see LINE_CAP below). Streams ONE
12
13
  // NDJSON line per job — `{i, spawnError?, exitCode, stdout}\n` — the INSTANT
13
14
  // that job settles (not once at the end): the parent's spawnSync call has its
14
15
  // own combined-output cap, so writing incrementally means a job whose line
@@ -21,6 +22,34 @@
21
22
  //
22
23
  // THE RED LINE: this child only `spawn`s the operator-resolved agent binary with
23
24
  // shell:false. It imports NO model SDK and reads NO credentials.
25
+ //
26
+ // A direct kill of THIS process (e.g. the parent's spawnSync `timeout` option,
27
+ // or a signal that reaches this child but not the grandchildren it spawned)
28
+ // used to orphan every still-running job -- no SIGINT/SIGTERM handler existed
29
+ // here, so the kernel default (instant termination) left them running with no
30
+ // one left to reap them. Now the first stop signal forwards SIGTERM to every
31
+ // still-tracked child (same escalation shape the per-job timeout below
32
+ // already uses) and lets each one's own `close` handler settle it normally;
33
+ // a second signal escalates straight to SIGKILL and exits immediately.
34
+ const children = new Set();
35
+ function killAllChildren(signal) {
36
+ for (const child of children) {
37
+ try { child.kill(signal); } catch {}
38
+ }
39
+ }
40
+ let stopSignalReceived = false;
41
+ function onStopSignal(signal) {
42
+ if (stopSignalReceived) {
43
+ killAllChildren("SIGKILL");
44
+ process.exit(signal === "SIGINT" ? 130 : 143);
45
+ return;
46
+ }
47
+ stopSignalReceived = true;
48
+ killAllChildren("SIGTERM");
49
+ setTimeout(() => killAllChildren("SIGKILL"), 5000).unref();
50
+ }
51
+ process.on("SIGINT", () => onStopSignal("SIGINT"));
52
+ process.on("SIGTERM", () => onStopSignal("SIGTERM"));
24
53
 
25
54
  const { spawn } = require("node:child_process");
26
55
  let raw = "";
@@ -37,6 +66,16 @@ process.stdin.on("end", () => {
37
66
  }
38
67
  if (!jobs.length) { process.stdout.write("[]"); return; }
39
68
  const CAP = 32 * 1024 * 1024;
69
+ // The parent (runAgentBatchOutcomes) grants maxBuffer = 34MB PER JOB for
70
+ // the COMBINED NDJSON stream it captures from this child. CAP above bounds
71
+ // each job's RAW stdout bytes, but the line written by settle() is the
72
+ // ESCAPED serialization — JSON escaping grows bytes (quotes and
73
+ // backslashes 2x, control chars up to 6x as \uXXXX), so a raw-capped
74
+ // stdout could still serialize into a line far past the parent's per-job
75
+ // budget, push the combined stream over maxBuffer, and ENOBUFS the WHOLE
76
+ // batch. LINE_CAP bounds the SERIALIZED line itself, held under the
77
+ // parent's 34MB per-job grant with a small safety margin.
78
+ const LINE_CAP = 33 * 1024 * 1024;
40
79
  jobs.forEach((job, i) => {
41
80
  let stdout = "";
42
81
  let stdoutBytes = 0;
@@ -45,7 +84,15 @@ process.stdin.on("end", () => {
45
84
  const settle = (o) => {
46
85
  if (settled) return;
47
86
  settled = true;
48
- process.stdout.write(JSON.stringify({ i, ...o }) + "\n");
87
+ let line = JSON.stringify({ i, ...o }) + "\n";
88
+ const lineBytes = Buffer.byteLength(line);
89
+ if (lineBytes > LINE_CAP) {
90
+ // Same fail-closed shape as the raw-cap path in the close handler
91
+ // below: capped output is never evidence, so drop it entirely and
92
+ // name the cap — never ship a line the parent's buffer cannot hold.
93
+ line = JSON.stringify({ i, spawnError: `serialized stdout line exceeded ${LINE_CAP} byte cap (${lineBytes} bytes)`, exitCode: null, stdout: "" }) + "\n";
94
+ }
95
+ process.stdout.write(line);
49
96
  };
50
97
  let child;
51
98
  try {
@@ -54,6 +101,7 @@ process.stdin.on("end", () => {
54
101
  settle({ spawnError: String((error && error.message) || error), exitCode: null, stdout: "" });
55
102
  return;
56
103
  }
104
+ children.add(child);
57
105
  const term = setTimeout(() => { try { child.kill("SIGTERM"); } catch {} }, job.timeoutMs);
58
106
  const kill = setTimeout(() => { try { child.kill("SIGKILL"); } catch {} }, job.timeoutMs + 5000);
59
107
  child.stdout.on("data", (d) => {
@@ -71,10 +119,12 @@ process.stdin.on("end", () => {
71
119
  child.stderr.on("data", () => {});
72
120
  child.on("error", (error) => {
73
121
  clearTimeout(term); clearTimeout(kill);
122
+ children.delete(child);
74
123
  settle({ spawnError: String((error && error.message) || error), exitCode: null, stdout });
75
124
  });
76
125
  child.on("close", (code) => {
77
126
  clearTimeout(term); clearTimeout(kill);
127
+ children.delete(child);
78
128
  if (stdoutTruncated) {
79
129
  settle({ spawnError: `stdout exceeded ${CAP} byte cap (${stdoutBytes} bytes)`, exitCode: null, stdout: "" });
80
130
  return;
@@ -6,7 +6,7 @@ const fs = require("node:fs");
6
6
  const path = require("node:path");
7
7
  const { CoolWorkflowRunner } = require("../dist/shell/orchestrator.js");
8
8
 
9
- const TARGET_VERSION = "0.2.1";
9
+ const TARGET_VERSION = "0.2.3";
10
10
  const PREVIOUS_VERSION = "0.1.31";
11
11
  const pluginRoot = path.resolve(__dirname, "..");
12
12
  const repoRoot = path.resolve(pluginRoot, "..", "..");
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ // fake-date-for-reproduction.js — a Node `--require` preload used ONLY by
4
+ // verify-bump-reproduction.sh. It pins `new Date()` (no-args construction)
5
+ // and `Date.now()` to a fixed instant, read from CW_FAKE_DATE (an ISO date or
6
+ // YYYY-MM-DD string). `new Date(explicit args)` is left untouched.
7
+ //
8
+ // WHY a runtime-level override, not an application-level env var: the
9
+ // scratch worktree checks out the APPROVED PARENT commit and runs ITS OWN
10
+ // copy of bump-version.js/sync-project-index.js — which, for every commit
11
+ // that predates this mechanism (i.e. every real release ever cut so far),
12
+ // has no idea any such override exists. An app-level env var a script has to
13
+ // explicitly read is a no-op against code that doesn't know to read it — it
14
+ // can only ever help releases cut AFTER the mechanism itself lands, making
15
+ // "reproduce a past release" true only by calendar coincidence (confirmed:
16
+ // re-running v0.2.0's real approved-parent/tagged pair on a day other than
17
+ // its actual cut day fails with a date-only diff in project-index.md).
18
+ // Intercepting the Date GLOBAL, before any application code runs, works
19
+ // identically regardless of which script version executes — old or new.
20
+ //
21
+ // Usage: NODE_OPTIONS="--require /path/to/this/file.js" CW_FAKE_DATE=2026-07-05 <command>
22
+
23
+ const pinned = process.env.CW_FAKE_DATE;
24
+ if (pinned) {
25
+ const fixedMs = new Date(pinned).getTime();
26
+ if (Number.isNaN(fixedMs)) {
27
+ process.stderr.write(`fake-date-for-reproduction: CW_FAKE_DATE is not a valid date: ${pinned}\n`);
28
+ process.exit(1);
29
+ }
30
+ const RealDate = Date;
31
+ class FakeDate extends RealDate {
32
+ constructor(...args) {
33
+ if (args.length === 0) {
34
+ super(fixedMs);
35
+ } else {
36
+ super(...args);
37
+ }
38
+ }
39
+ static now() {
40
+ return fixedMs;
41
+ }
42
+ }
43
+ global.Date = FakeDate;
44
+ }
@@ -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.2.1");
36
+ assert.equal(appValidation.summary.version, "0.2.3");
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.2.1 end-to-end golden path."
45
+ "Prove the deterministic v0.2.3 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.2.1");
55
+ assert.equal(state.workflow.app.version, "0.2.3");
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\.2\.1/);
198
+ assert.match(report, /Workflow App: end-to-end-golden-path@0\.2\.3/);
199
199
  assert.match(report, /## Candidates/);
200
200
  assert.match(report, /## Trust Audit/);
201
201
  assert.match(report, /## Acceptance Rationale/);
@@ -0,0 +1,71 @@
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/basics.ts": [
41
+ "../../cli/io"
42
+ ],
43
+ "wiring/capability-table/exec-backend.ts": [
44
+ "../../cli/io"
45
+ ],
46
+ "wiring/capability-table/multi-agent.ts": [
47
+ "../../cli/io"
48
+ ],
49
+ "wiring/capability-table/pipeline.ts": [
50
+ "../../cli/io"
51
+ ],
52
+ "wiring/capability-table/registry-core.ts": [
53
+ "../../cli/io"
54
+ ],
55
+ "wiring/capability-table/reporting.ts": [
56
+ "../../cli/io"
57
+ ],
58
+ "wiring/capability-table/scheduling-registry.ts": [
59
+ "../../cli/io"
60
+ ],
61
+ "wiring/capability-table/state.ts": [
62
+ "../../cli/io"
63
+ ],
64
+ "wiring/capability-table/trust-ledger.ts": [
65
+ "../../cli/io"
66
+ ],
67
+ "wiring/capability-table/workflow-apps.ts": [
68
+ "../../cli/io"
69
+ ]
70
+ }
71
+ }