cool-workflow 0.2.2 → 0.2.4
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 +1 -1
- package/.codex-plugin/plugin.json +1 -1
- 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/cli/dispatch.js +13 -6
- package/dist/cli/entry.js +41 -1
- package/dist/cli/io.js +12 -22
- package/dist/cli/parseargv.js +1 -0
- package/dist/core/capability-data.js +3 -2
- package/dist/core/format/completion.js +68 -0
- package/dist/core/format/help.js +31 -6
- package/dist/core/format/safe-json.js +73 -0
- package/dist/core/format/state-explosion-text.js +1 -1
- package/dist/core/multi-agent/candidate-scoring.js +5 -1
- package/dist/core/multi-agent/collaboration.js +3 -3
- package/dist/core/multi-agent/coordinator.js +5 -5
- package/dist/core/multi-agent/runtime.js +4 -4
- package/dist/core/multi-agent/topology.js +3 -3
- package/dist/core/pipeline/dispatch.js +18 -4
- package/dist/core/pipeline/drive-decide.js +2 -1
- package/dist/core/state/migrations.js +16 -1
- package/dist/core/state/state-explosion/digest.js +13 -13
- package/dist/core/state/state-explosion/graph.js +17 -11
- package/dist/core/state/state-explosion/report.js +6 -6
- package/dist/core/util/cli-args.js +33 -0
- package/dist/core/util/numeric-flag.js +40 -0
- package/dist/core/version.js +1 -1
- package/dist/mcp/server.js +99 -11
- package/dist/shell/audit-cli.js +57 -25
- package/dist/shell/coordinator-io.js +73 -13
- package/dist/shell/dispatch.js +1 -1
- package/dist/shell/doctor.js +80 -1
- package/dist/shell/drive.js +251 -49
- package/dist/shell/eval-text.js +2 -2
- package/dist/shell/evidence-reasoning.js +4 -4
- package/dist/shell/execution-backend/agent.js +30 -2
- package/dist/shell/execution-backend/container.js +4 -1
- package/dist/shell/execution-backend/local.js +19 -11
- package/dist/shell/feedback-cli.js +6 -6
- package/dist/shell/fs-atomic.js +218 -29
- package/dist/shell/man-cli.js +6 -0
- package/dist/shell/metrics-cli.js +2 -1
- package/dist/shell/multi-agent-cli.js +367 -323
- package/dist/shell/multi-agent-host.js +9 -9
- package/dist/shell/multi-agent-operator-ux.js +80 -38
- package/dist/shell/node-store.js +10 -4
- package/dist/shell/observability.js +1 -1
- package/dist/shell/operator-ux-text.js +22 -22
- package/dist/shell/operator-ux.js +15 -14
- package/dist/shell/orchestrator.js +49 -38
- package/dist/shell/pipeline-cli.js +107 -42
- package/dist/shell/reclamation-io.js +88 -10
- package/dist/shell/registry-cli.js +23 -17
- package/dist/shell/remote-source.js +13 -8
- package/dist/shell/report-cli.js +45 -0
- package/dist/shell/report.js +2 -1
- package/dist/shell/run-registry-io.js +78 -19
- package/dist/shell/run-store.js +74 -2
- package/dist/shell/scheduling-io.js +12 -13
- package/dist/shell/state-cli.js +2 -7
- package/dist/shell/state-explosion-cli.js +17 -9
- package/dist/shell/topology-io.js +36 -5
- package/dist/shell/trust-audit.js +224 -22
- package/dist/shell/trust-policy-io.js +1 -1
- package/dist/shell/worker-cli.js +35 -31
- package/dist/shell/workflow-app-loader.js +67 -1
- package/dist/wiring/capability-table/basics.js +33 -8
- package/dist/wiring/capability-table/exec-backend.js +28 -19
- package/dist/wiring/capability-table/multi-agent.js +187 -180
- package/dist/wiring/capability-table/parity.js +1 -0
- package/dist/wiring/capability-table/pipeline.js +64 -52
- package/dist/wiring/capability-table/registry-core.js +30 -11
- package/dist/wiring/capability-table/reporting.js +147 -106
- package/dist/wiring/capability-table/scheduling-registry.js +201 -167
- package/dist/wiring/capability-table/state.js +59 -51
- package/dist/wiring/capability-table/trust-ledger.js +52 -28
- package/dist/wiring/capability-table/workflow-apps.js +85 -70
- package/docs/agent-delegation-drive.7.md +4 -0
- package/docs/candidate-scoring.7.md +1 -1
- package/docs/canonical-workflow-apps.7.md +7 -7
- package/docs/cli-mcp-parity.7.md +26 -16
- package/docs/contract-migration-tooling.7.md +4 -0
- package/docs/control-plane-scheduling.7.md +4 -0
- package/docs/coordinator-blackboard.7.md +17 -17
- package/docs/dogfood-one-real-repo.7.md +11 -11
- package/docs/durable-state-and-locking.7.md +4 -0
- package/docs/end-to-end-golden-path.7.md +14 -14
- package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
- package/docs/execution-backends.7.md +4 -0
- package/docs/getting-started.md +37 -37
- package/docs/multi-agent-cli-mcp-surface.7.md +21 -17
- package/docs/multi-agent-eval-replay-harness.7.md +17 -13
- package/docs/multi-agent-operator-ux.7.md +23 -19
- package/docs/multi-agent-runtime-core.7.md +22 -22
- package/docs/multi-agent-topologies.7.md +6 -6
- package/docs/multi-agent-trust-policy-audit.7.md +11 -11
- package/docs/node-snapshot-diff-replay.7.md +4 -0
- package/docs/observability-cost-accounting.7.md +4 -0
- package/docs/operator-ux.7.md +34 -34
- package/docs/pipeline-runner.7.md +4 -4
- package/docs/project-index.md +31 -4
- package/docs/real-execution-backends.7.md +4 -0
- package/docs/release-and-migration.7.md +5 -1
- package/docs/release-tooling.7.md +22 -0
- package/docs/routines.md +4 -4
- package/docs/run-registry-control-plane.7.md +23 -19
- package/docs/run-retention-reclamation.7.md +24 -3
- package/docs/scheduled-tasks.md +14 -14
- package/docs/security-trust-hardening.7.md +15 -15
- package/docs/state-explosion-management.7.md +14 -10
- package/docs/team-collaboration.7.md +4 -0
- package/docs/trust-audit-anchor.7.md +4 -0
- package/docs/unix-principles.md +3 -1
- package/docs/verifier-gated-commit.7.md +1 -1
- package/docs/web-desktop-workbench.7.md +4 -0
- package/docs/workflow-app-framework.7.md +13 -13
- package/manifest/plugin.manifest.json +1 -1
- package/package.json +3 -2
- package/scripts/agents/claude-p-agent.js +2 -2
- package/scripts/block-unapproved-tag.sh +42 -6
- package/scripts/bump-version.js +27 -10
- package/scripts/canonical-apps.js +4 -4
- package/scripts/children/batch-delegate-child.js +52 -2
- package/scripts/dogfood-release.js +1 -1
- package/scripts/fake-date-for-reproduction.js +44 -0
- package/scripts/golden-path.js +4 -4
- package/scripts/purity-baseline.json +0 -27
- package/scripts/release-flow.js +194 -13
- package/scripts/release-oneclick.js +407 -0
- package/scripts/verdict-keygen.js +83 -0
- package/scripts/verify-bump-reproduction.sh +148 -0
- package/scripts/verify-verdict-signature.js +61 -0
- package/scripts/version-sync-check.js +39 -22
- package/skills/cool-workflow/SKILL.md +9 -9
- package/skills/cool-workflow/references/commands.md +89 -88
- package/ui/workbench/app.css +37 -1
- package/ui/workbench/app.js +124 -6
|
@@ -84,14 +84,14 @@ folded.
|
|
|
84
84
|
## CLI
|
|
85
85
|
|
|
86
86
|
```text
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
cw summary refresh <run-id> [--json] [--view <view> ...]
|
|
88
|
+
cw summary show <run-id> [--json]
|
|
89
|
+
cw blackboard summarize <run-id> [--json]
|
|
90
|
+
cw multi-agent summarize <run-id> [--json]
|
|
91
|
+
cw multi-agent graph <run-id> --view compact [--json]
|
|
92
|
+
cw multi-agent graph <run-id> --view critical-path [--json]
|
|
93
|
+
cw multi-agent graph <run-id> --focus <id> --depth <n> [--json]
|
|
94
|
+
cw report <run-id> --show
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
Every command supports deterministic JSON with `--json` or `--format json`.
|
|
@@ -106,8 +106,8 @@ summaries and tells the operator how to look at the full data, for example:
|
|
|
106
106
|
|
|
107
107
|
```text
|
|
108
108
|
Graph compacted: 420 nodes collapsed into 18 summary nodes
|
|
109
|
-
Use:
|
|
110
|
-
Use:
|
|
109
|
+
Use: cw multi-agent graph <run-id> --view full --json
|
|
110
|
+
Use: cw blackboard message list <run-id> --topic <topic-id>
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
## MCP parity
|
|
@@ -324,3 +324,7 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
|
|
|
324
324
|
0.2.1
|
|
325
325
|
|
|
326
326
|
0.2.2
|
|
327
|
+
|
|
328
|
+
0.2.3
|
|
329
|
+
|
|
330
|
+
0.2.4
|
package/docs/unix-principles.md
CHANGED
|
@@ -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
|
|
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
|
|
@@ -16,13 +16,13 @@ const {
|
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
|
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.
|
|
5
|
+
"version": "0.2.4",
|
|
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.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"bin": {
|
|
5
5
|
"cool-workflow": "scripts/cw.js",
|
|
6
6
|
"cw": "scripts/cw.js"
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"onramp:check": "node scripts/onramp-check.js --check",
|
|
64
64
|
"version:sync": "node scripts/version-sync-check.js",
|
|
65
65
|
"release:check": "node scripts/release-check.js",
|
|
66
|
+
"release": "node scripts/release-oneclick.js",
|
|
66
67
|
"test": "node dist/cli.js version > /dev/null && node test/run-all.js --fast --sample 35",
|
|
67
68
|
"test:full": "node dist/cli.js version > /dev/null && node test/run-all.js --sample 55",
|
|
68
69
|
"test:gate": "node dist/cli.js version > /dev/null && node test/run-all.js",
|
|
@@ -75,7 +76,7 @@
|
|
|
75
76
|
"validate:schema": "node scripts/validate-run-state-schema.js"
|
|
76
77
|
},
|
|
77
78
|
"devDependencies": {
|
|
78
|
-
"@types/node": "^
|
|
79
|
+
"@types/node": "^26.1.1",
|
|
79
80
|
"typescript": "^5.9.0"
|
|
80
81
|
},
|
|
81
82
|
"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
|
|
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
|
|
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,24 +19,57 @@ 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
|
-
|
|
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
|
|
|
24
31
|
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0
|
|
25
32
|
SHA="$(git -C "$REPO_ROOT" rev-parse HEAD)"
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
# cut() commits the verdict ON TOP of the reviewed commit, so at tag time the
|
|
34
|
+
# verdict filename is keyed on HEAD~1's sha, not HEAD's — the same
|
|
35
|
+
# HEAD-or-HEAD~1 tolerance release-gate.yml uses. Without this, a manual
|
|
36
|
+
# retag of a cut-produced commit was always blocked (v0.2.3 recovery).
|
|
37
|
+
PARENT="$(git -C "$REPO_ROOT" rev-parse HEAD~1 2>/dev/null || echo none)"
|
|
38
|
+
GATE=""
|
|
39
|
+
VERDICT=""
|
|
40
|
+
for C in "$SHA" "$PARENT"; do
|
|
41
|
+
[[ "$C" == "none" ]] && continue
|
|
42
|
+
if [[ -f "$REPO_ROOT/.cw-release/review-$C.verdict" ]]; then
|
|
43
|
+
GATE="$REPO_ROOT/.cw-release/gate-$C.ok"
|
|
44
|
+
VERDICT="$REPO_ROOT/.cw-release/review-$C.verdict"
|
|
45
|
+
break
|
|
46
|
+
fi
|
|
47
|
+
done
|
|
48
|
+
[[ -z "$VERDICT" ]] && VERDICT="$REPO_ROOT/.cw-release/review-$SHA.verdict"
|
|
49
|
+
[[ -z "$GATE" ]] && GATE="$REPO_ROOT/.cw-release/gate-$SHA.ok"
|
|
28
50
|
|
|
29
51
|
if [[ ! -f "$GATE" ]]; then
|
|
30
|
-
echo "BLOCKED: no release-gate pass for HEAD $SHA. Run plugins/cool-workflow/scripts/release-gate.sh first. Tagging without a green gate is forbidden." >&2
|
|
52
|
+
echo "BLOCKED: no release-gate pass for HEAD $SHA (or its parent). Run plugins/cool-workflow/scripts/release-gate.sh first. Tagging without a green gate is forbidden." >&2
|
|
31
53
|
exit 2
|
|
32
54
|
fi
|
|
33
55
|
|
|
34
56
|
if [[ ! -f "$VERDICT" ]] || ! grep -q '^APPROVED' "$VERDICT"; then
|
|
35
|
-
echo "BLOCKED: no APPROVED verdict from the release-reviewer agent for HEAD $SHA. Invoke the 'release-reviewer' subagent and obtain approval. Do not write the verdict file yourself — that is a gaming attempt and will be flagged in CI." >&2
|
|
57
|
+
echo "BLOCKED: no APPROVED verdict from the release-reviewer agent for HEAD $SHA (or its parent). Invoke the 'release-reviewer' subagent and obtain approval. Do not write the verdict file yourself — that is a gaming attempt and will be flagged in CI." >&2
|
|
36
58
|
exit 2
|
|
37
59
|
fi
|
|
38
60
|
|
|
61
|
+
# Once .cw-release/verdict-signing.pub is committed (see scripts/verdict-keygen.js),
|
|
62
|
+
# also require a valid ed25519 signature on the verdict — closing the gap this grep
|
|
63
|
+
# alone can't: a plain APPROVED text match can't tell a real reviewer verdict from
|
|
64
|
+
# one typed by hand. Absent that public key, this block is a no-op (unchanged,
|
|
65
|
+
# grep-only behavior).
|
|
66
|
+
PUBKEY="$REPO_ROOT/.cw-release/verdict-signing.pub"
|
|
67
|
+
if [[ -f "$PUBKEY" ]]; then
|
|
68
|
+
SIG="$VERDICT.sig"
|
|
69
|
+
if [[ ! -f "$SIG" ]] || ! node "$REPO_ROOT/plugins/cool-workflow/scripts/verify-verdict-signature.js" "$VERDICT" "$SIG" "$PUBKEY" >/dev/null 2>&1; then
|
|
70
|
+
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
|
|
71
|
+
exit 2
|
|
72
|
+
fi
|
|
73
|
+
fi
|
|
74
|
+
|
|
39
75
|
exit 0
|
package/scripts/bump-version.js
CHANGED
|
@@ -77,12 +77,18 @@ function main() {
|
|
|
77
77
|
|
|
78
78
|
const current = JSON.parse(fs.readFileSync(path.join(pluginRoot, "package.json"), "utf8")).version;
|
|
79
79
|
if (next === current) {
|
|
80
|
-
// Idempotent: re-running the bump for the same
|
|
81
|
-
// rather than
|
|
82
|
-
// the bump was already landed in a
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
80
|
+
// Idempotent for the STRUCTURED surfaces: re-running the bump for the same
|
|
81
|
+
// version skips the writes (exit 0) rather than hard-failing. Required by
|
|
82
|
+
// `release-flow --cut --version X` when the bump was already landed in a
|
|
83
|
+
// prior PR — cut should still be able to commit the verdict and tag
|
|
84
|
+
// without re-bumping. But the CONTENT-surface gate must still run: the
|
|
85
|
+
// v0.2.3 cut hit exactly this hole — package.json was already bumped, so
|
|
86
|
+
// this early-exit skipped the content check entirely and `--content`
|
|
87
|
+
// never filled the missing docs/RELEASE references. Same-version reruns
|
|
88
|
+
// now gate (or fill) content exactly like a fresh bump does.
|
|
89
|
+
process.stdout.write(`bump:version: structured surfaces already at ${next}\n`);
|
|
90
|
+
const sameVersionContent = handleContentSurfaces(current, next);
|
|
91
|
+
if (!sameVersionContent.ok) fail(sameVersionContent.error);
|
|
86
92
|
process.exit(0);
|
|
87
93
|
}
|
|
88
94
|
|
|
@@ -161,9 +167,11 @@ function main() {
|
|
|
161
167
|
// and a RegExp string (`0\\.1\\.32`, from `new RegExp(\`...@0\\.1\\.32\`)`).
|
|
162
168
|
// A plain replace silently misses the escaped forms — the exact "escaped-dot"
|
|
163
169
|
// surface that fails version:sync mid-release. Replace all three, most-escaped
|
|
164
|
-
// first so the forms can never overlap.
|
|
165
|
-
|
|
166
|
-
|
|
170
|
+
// first so the forms can never overlap. No regex is ever built from these —
|
|
171
|
+
// the forms feed the plain split/join TEXT replacement below (which is also
|
|
172
|
+
// why split/join, not a regex-escape-shaped replace, builds them).
|
|
173
|
+
const esc1 = (v) => v.split(".").join("\\.");
|
|
174
|
+
const esc2 = (v) => v.split(".").join("\\\\.");
|
|
167
175
|
const forms = [[esc2(current), esc2(next)], [esc1(current), esc1(next)], [current, next]];
|
|
168
176
|
for (const rel of targeted) {
|
|
169
177
|
const abs = path.join(pluginRoot, rel);
|
|
@@ -261,7 +269,16 @@ function handleContentSurfaces(current, next) {
|
|
|
261
269
|
const abs = path.join(repoRoot, rel);
|
|
262
270
|
if (!fs.existsSync(abs)) { missing.push({ rel, reason: "file missing" }); continue; }
|
|
263
271
|
const text = fs.readFileSync(abs, "utf8");
|
|
264
|
-
|
|
272
|
+
// Own-line match, not a plain substring: every entry here is a line this
|
|
273
|
+
// function itself appends (`\n${needle}\n`) when missing, and a bare
|
|
274
|
+
// version number can otherwise coincidentally already appear in the
|
|
275
|
+
// doc's unrelated prose (an example command, a cross-reference) without
|
|
276
|
+
// the real version-history line actually being present. See
|
|
277
|
+
// version-sync-check.js's checkIncludesOwnLine for the matching CI-side
|
|
278
|
+
// gate (found live: v0.2.4's bump masked a real missing footer line in
|
|
279
|
+
// release-tooling.7.md this way).
|
|
280
|
+
const hasOwnLine = text.split("\n").some((line) => line.trim() === needle);
|
|
281
|
+
if (!hasOwnLine) missing.push({ rel, needle });
|
|
265
282
|
}
|
|
266
283
|
|
|
267
284
|
if (missing.length === 0) {
|
|
@@ -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.
|
|
86
|
+
"Cool Workflow v0.2.4",
|
|
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.
|
|
120
|
+
assert.equal(summary.version, "0.2.4");
|
|
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.
|
|
127
|
+
assert.equal(shown.app.version, "0.2.4");
|
|
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.
|
|
138
|
+
assert.equal(state.workflow.app.version, "0.2.4");
|
|
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
|
|
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
|
-
|
|
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.
|
|
9
|
+
const TARGET_VERSION = "0.2.4";
|
|
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
|
+
}
|
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.2.
|
|
36
|
+
assert.equal(appValidation.summary.version, "0.2.4");
|
|
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.
|
|
45
|
+
"Prove the deterministic v0.2.4 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.
|
|
55
|
+
assert.equal(state.workflow.app.version, "0.2.4");
|
|
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\.
|
|
198
|
+
assert.match(report, /Workflow App: end-to-end-golden-path@0\.2\.4/);
|
|
199
199
|
assert.match(report, /## Candidates/);
|
|
200
200
|
assert.match(report, /## Trust Audit/);
|
|
201
201
|
assert.match(report, /## Acceptance Rationale/);
|
|
@@ -36,33 +36,6 @@
|
|
|
36
36
|
"layerViolations": {
|
|
37
37
|
"core/capability-table.ts": [
|
|
38
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
39
|
]
|
|
67
40
|
}
|
|
68
41
|
}
|