cool-workflow 0.2.3 → 0.2.5

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 (143) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/.gemini-plugin/mcp.json +10 -0
  4. package/.gemini-plugin/plugin.json +40 -0
  5. package/.opencode-plugin/mcp.json +10 -0
  6. package/.opencode-plugin/plugin.json +40 -0
  7. package/README.md +202 -48
  8. package/apps/architecture-review/app.json +1 -1
  9. package/apps/architecture-review-fast/app.json +1 -1
  10. package/apps/end-to-end-golden-path/app.json +1 -1
  11. package/apps/pr-review-fix-ci/app.json +1 -1
  12. package/apps/release-cut/app.json +1 -1
  13. package/apps/research-synthesis/app.json +1 -1
  14. package/dist/cli/dispatch.js +33 -4
  15. package/dist/cli/entry.js +11 -19
  16. package/dist/cli/global-flags.js +113 -0
  17. package/dist/cli/io.js +6 -20
  18. package/dist/cli/parseargv.js +7 -1
  19. package/dist/core/capability-data.js +339 -10
  20. package/dist/core/format/help.js +80 -4
  21. package/dist/core/format/recovery-hint.js +32 -0
  22. package/dist/core/multi-agent/collaboration.js +35 -6
  23. package/dist/core/multi-agent/runtime.js +7 -0
  24. package/dist/core/multi-agent/trust-policy.js +7 -1
  25. package/dist/core/pipeline/contract.js +7 -0
  26. package/dist/core/pipeline/error-feedback.js +2 -2
  27. package/dist/core/trust/evidence-grounding.js +13 -1
  28. package/dist/core/util/cli-args.js +55 -0
  29. package/dist/core/version.js +1 -1
  30. package/dist/mcp/dispatch.js +22 -2
  31. package/dist/mcp/server.js +124 -13
  32. package/dist/mcp-server.js +20 -0
  33. package/dist/shell/commit.js +8 -2
  34. package/dist/shell/coordinator-io.js +73 -1
  35. package/dist/shell/drive.js +129 -64
  36. package/dist/shell/error-feedback-io.js +6 -0
  37. package/dist/shell/execution-backend/agent.js +205 -24
  38. package/dist/shell/execution-backend/container.js +44 -10
  39. package/dist/shell/execution-backend/local.js +47 -10
  40. package/dist/shell/fs-atomic.js +93 -12
  41. package/dist/shell/ledger-cli.js +9 -2
  42. package/dist/shell/multi-agent-cli.js +5 -1
  43. package/dist/shell/onramp.js +48 -5
  44. package/dist/shell/pipeline-cli.js +20 -2
  45. package/dist/shell/pipeline.js +2 -1
  46. package/dist/shell/reclamation-io.js +76 -7
  47. package/dist/shell/registry-cli.js +4 -0
  48. package/dist/shell/run-export.js +52 -4
  49. package/dist/shell/run-registry-io.js +1 -0
  50. package/dist/shell/run-store.js +156 -0
  51. package/dist/shell/scheduler-io.js +101 -10
  52. package/dist/shell/telemetry-ledger-io.js +36 -24
  53. package/dist/shell/trust-audit.js +104 -10
  54. package/dist/shell/workbench-host.js +121 -10
  55. package/dist/shell/workbench.js +79 -5
  56. package/dist/shell/worker-isolation.js +1 -1
  57. package/dist/shell/workflow-app-loader.js +67 -1
  58. package/dist/wiring/capability-table/basics.js +7 -2
  59. package/dist/wiring/capability-table/exec-backend.js +42 -24
  60. package/dist/wiring/capability-table/multi-agent.js +69 -69
  61. package/dist/wiring/capability-table/pipeline.js +60 -28
  62. package/dist/wiring/capability-table/registry-core.js +29 -6
  63. package/dist/wiring/capability-table/reporting.js +45 -39
  64. package/dist/wiring/capability-table/scheduling-registry.js +66 -60
  65. package/dist/wiring/capability-table/state.js +34 -34
  66. package/dist/wiring/capability-table/trust-ledger.js +66 -24
  67. package/dist/wiring/capability-table/workflow-apps.js +15 -15
  68. package/docs/agent-delegation-drive.7.md +17 -0
  69. package/docs/cli-mcp-parity.7.md +43 -13
  70. package/docs/contract-migration-tooling.7.md +4 -0
  71. package/docs/control-plane-scheduling.7.md +38 -0
  72. package/docs/cross-agent-ledger.7.md +20 -8
  73. package/docs/durable-state-and-locking.7.md +4 -0
  74. package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
  75. package/docs/execution-backends.7.md +4 -0
  76. package/docs/fix.7.md +4 -4
  77. package/docs/getting-started.md +40 -32
  78. package/docs/index.md +17 -0
  79. package/docs/launch/demo.tape +4 -3
  80. package/docs/mcp-app-surface.7.md +6 -0
  81. package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
  82. package/docs/multi-agent-eval-replay-harness.7.md +4 -0
  83. package/docs/multi-agent-operator-ux.7.md +4 -0
  84. package/docs/node-snapshot-diff-replay.7.md +4 -0
  85. package/docs/observability-cost-accounting.7.md +4 -0
  86. package/docs/project-index.md +43 -9
  87. package/docs/real-execution-backends.7.md +4 -0
  88. package/docs/release-and-migration.7.md +4 -0
  89. package/docs/release-tooling.7.md +22 -0
  90. package/docs/routine.7.md +22 -0
  91. package/docs/run-registry-control-plane.7.md +4 -0
  92. package/docs/run-retention-reclamation.7.md +28 -3
  93. package/docs/state-explosion-management.7.md +4 -0
  94. package/docs/team-collaboration.7.md +4 -0
  95. package/docs/trust-audit-anchor.7.md +4 -0
  96. package/docs/web-desktop-workbench.7.md +26 -2
  97. package/docs/workflow-app-framework.7.md +31 -0
  98. package/manifest/plugin.manifest.json +1 -1
  99. package/manifest/source-context-profiles.json +4 -3
  100. package/package.json +5 -2
  101. package/scripts/agents/agent-adapter-core.js +26 -0
  102. package/scripts/agents/claude-p-agent.js +4 -1
  103. package/scripts/agents/codex-agent.js +4 -0
  104. package/scripts/agents/cw-attest-wrap.js +1 -1
  105. package/scripts/agents/gemini-agent.js +4 -0
  106. package/scripts/agents/opencode-agent.js +5 -0
  107. package/scripts/block-unapproved-tag.js +160 -0
  108. package/scripts/bump-version.js +27 -10
  109. package/scripts/canonical-apps.js +4 -4
  110. package/scripts/children/batch-delegate-child.js +72 -4
  111. package/scripts/children/http-batch-delegate-child.js +132 -0
  112. package/scripts/children/http-delegate-child.js +8 -0
  113. package/scripts/dogfood-release.js +2 -2
  114. package/scripts/fake-date-for-reproduction.js +1 -1
  115. package/scripts/golden-path.js +4 -4
  116. package/scripts/lang-policy-check.js +82 -0
  117. package/scripts/purity-baseline.json +0 -30
  118. package/scripts/purity-gate.js +16 -3
  119. package/scripts/release-check.js +2 -1
  120. package/scripts/release-flow.js +264 -41
  121. package/scripts/release-gate.js +184 -0
  122. package/scripts/release-oneclick.js +438 -0
  123. package/scripts/release-tags.js +47 -0
  124. package/scripts/verdict-keygen.js +2 -2
  125. package/scripts/verify-bump-reproduction.js +193 -0
  126. package/scripts/verify-verdict-signature.js +1 -1
  127. package/scripts/version-sync-check.js +39 -22
  128. package/ui/workbench/app.css +41 -9
  129. package/ui/workbench/app.js +160 -27
  130. package/ui/workbench/index.html +2 -2
  131. package/docs/agent-framework.md +0 -177
  132. package/docs/designs/handoff-ledger.md +0 -145
  133. package/docs/dogfood/resume-drive-real-agent-2026-06-14.md +0 -40
  134. package/docs/launch/launch-kit.md +0 -195
  135. package/docs/launch/pre-launch-checklist.md +0 -53
  136. package/docs/readme-v0.1.87-full.md +0 -301
  137. package/docs/routines.md +0 -101
  138. package/docs/scheduled-tasks.md +0 -80
  139. package/scripts/agents/claude-p-agent.sh +0 -9
  140. package/scripts/block-unapproved-tag.sh +0 -60
  141. package/scripts/release-gate.sh +0 -94
  142. package/scripts/verify-bump-reproduction.sh +0 -148
  143. package/scripts/verify-container-selfref.js +0 -64
@@ -1,80 +0,0 @@
1
- # Scheduled Tasks
2
-
3
- CW scheduled tasks let you make looping prompts, cron-like schedules, one-shot
4
- reminders, expiration, jitter, and clear completion.
5
-
6
- CW keeps schedules in:
7
-
8
- ```text
9
- .cw/schedules/tasks.json
10
- ```
11
-
12
- ## Commands
13
-
14
- Make a `/loop`-ready schedule:
15
-
16
- ```bash
17
- cw loop \
18
- --intervalMinutes 30 \
19
- --prompt "Check this workflow and continue if work is due."
20
- ```
21
-
22
- Make a loop:
23
-
24
- ```bash
25
- cw schedule create \
26
- --kind loop \
27
- --intervalMinutes 30 \
28
- --prompt "Check this workflow and continue if work is due."
29
- ```
30
-
31
- Make a cron schedule:
32
-
33
- ```bash
34
- cw schedule create \
35
- --kind cron \
36
- --cron "*/15 * * * *" \
37
- --prompt "Run the due workflow scan."
38
- ```
39
-
40
- Make a reminder:
41
-
42
- ```bash
43
- cw schedule create \
44
- --kind reminder \
45
- --delayMinutes 60 \
46
- --prompt "Remind me to inspect the report."
47
- ```
48
-
49
- List and look through:
50
-
51
- ```bash
52
- cw schedule list
53
- cw schedule due
54
- cw schedule complete <schedule-id>
55
- cw schedule pause <schedule-id>
56
- cw schedule resume <schedule-id>
57
- cw schedule run-now <schedule-id>
58
- cw schedule history <schedule-id>
59
- cw schedule delete <schedule-id>
60
- ```
61
-
62
- Run the local desktop-like daemon one time:
63
-
64
- ```bash
65
- cw schedule daemon --once
66
- ```
67
-
68
- Run it without stopping:
69
-
70
- ```bash
71
- cw schedule daemon --intervalSeconds 60
72
- ```
73
-
74
- ## Notes
75
-
76
- - Time is measured to the minute.
77
- - By default, expiration comes after 7 days.
78
- - `jitterSeconds` can put space between runs.
79
- - CW does not start the daemon by default. Use `schedule daemon`, cron, or
80
- some other overseer to call `schedule due` and run due prompts.
@@ -1,9 +0,0 @@
1
- #!/usr/bin/env bash
2
- # EXAMPLE operator agent config for CW Agent Delegation Drive — bash entry point.
3
- #
4
- # The single source of truth is claude-p-agent.js (the portable node version);
5
- # this shim only delegates so the two can never drift. Point CW at either:
6
- # CW_AGENT_COMMAND="node $(pwd)/scripts/agents/claude-p-agent.js {{input}} {{result}}"
7
- # CW_AGENT_COMMAND="bash $(pwd)/scripts/agents/claude-p-agent.sh {{input}} {{result}}"
8
- set -euo pipefail
9
- exec node "$(dirname "$0")/claude-p-agent.js" "$@"
@@ -1,60 +0,0 @@
1
- #!/usr/bin/env bash
2
- # block-unapproved-tag.sh — PreToolUse hook for the Bash tool.
3
- # Reads hook input JSON on stdin. If the command creates or pushes a tag,
4
- # require BOTH markers for the current HEAD sha:
5
- # .cw-release/gate-<sha>.ok (written by release-gate.sh)
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.
10
- # Exit 2 blocks the tool call; stderr is fed back to the agent.
11
- set -uo pipefail
12
-
13
- INPUT="$(cat)"
14
- # Parse the tool command with node, not jq: node is guaranteed present in this
15
- # Node project (and matches the repo's node/npm/git-only portability rule),
16
- # whereas jq is not installed in every Claude Code environment. A missing jq
17
- # would make this security hook silently fail OPEN (empty command → exit 0),
18
- # letting an unreviewed tag through. node keeps it portable and fail-closed.
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)"
20
- [[ -z "$CMD" ]] && exit 0
21
-
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
28
- exit 0
29
- fi
30
-
31
- REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0
32
- SHA="$(git -C "$REPO_ROOT" rev-parse HEAD)"
33
- GATE="$REPO_ROOT/.cw-release/gate-$SHA.ok"
34
- VERDICT="$REPO_ROOT/.cw-release/review-$SHA.verdict"
35
-
36
- if [[ ! -f "$GATE" ]]; then
37
- 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
38
- exit 2
39
- fi
40
-
41
- if [[ ! -f "$VERDICT" ]] || ! grep -q '^APPROVED' "$VERDICT"; then
42
- 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
43
- exit 2
44
- fi
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
-
60
- exit 0
@@ -1,94 +0,0 @@
1
- #!/usr/bin/env bash
2
- # release-gate.sh — deterministic release checks for cool-workflow.
3
- # Pass = writes .cw-release/gate-<HEAD-sha>.ok
4
- # This script encodes everything that does NOT need LLM judgment.
5
- set -euo pipefail
6
-
7
- REPO_ROOT="$(git rev-parse --show-toplevel)"
8
- cd "$REPO_ROOT"
9
- SHA="$(git rev-parse HEAD)"
10
- # Resolve the PREVIOUS release tag. When this script runs from CI on a tag push
11
- # (.github/workflows/release-gate.yml), HEAD already carries the tag being
12
- # released, so a plain `git describe` returns *that* tag and the diff range
13
- # collapses to empty — making substance/evidence/cadence false-fail every real
14
- # release. Exclude any tag that points at HEAD so we always compare against the
15
- # prior release (the parent commit's nearest tag).
16
- HEAD_TAGS="$(git tag --points-at HEAD 2>/dev/null || echo "")"
17
- PREV_TAG="$(git describe --tags --abbrev=0 2>/dev/null || echo "")"
18
- if [[ -n "$HEAD_TAGS" ]] && printf '%s\n' "$HEAD_TAGS" | grep -qxF "$PREV_TAG"; then
19
- PREV_TAG="$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")"
20
- fi
21
- MARKER_DIR="$REPO_ROOT/.cw-release"
22
- mkdir -p "$MARKER_DIR"
23
- FAIL=0
24
-
25
- say() { printf '%s\n' "$*"; }
26
- fail() { say "GATE FAIL: $*"; FAIL=1; }
27
-
28
- # --- 1. Build & tests (run, don't trust pasted output) -----------------
29
- say "[1/6] build"
30
- npm run --prefix plugins/cool-workflow build >/dev/null 2>&1 || fail "build failed"
31
-
32
- say "[2/6] tests"
33
- CW_TEST_CONCURRENCY=1 npm run test:gate --prefix plugins/cool-workflow >/dev/null 2>&1 || fail "tests failed"
34
-
35
- if [[ -n "$PREV_TAG" ]]; then
36
- RANGE="$PREV_TAG..HEAD"
37
-
38
- # --- 2. Substance: changes must exist outside src/types/ and dist/ ---
39
- # The spec (AGENTS.md / reviewer-agent.md Gate 1) is "at least one changed
40
- # file outside src/types/ and dist/" — ANY such file (src, scripts, docs,
41
- # workflows, tests). Count every changed path that is not under those two
42
- # generated/declaration-only trees; declared-but-unread spec accretion is the
43
- # reviewer agent's deeper judgment call, not this deterministic floor.
44
- say "[3/6] substance (diff outside src/types/ and dist/)"
45
- SUBSTANCE=$(git diff --name-only "$RANGE" \
46
- | grep -cvE '^plugins/cool-workflow/(src/types/|dist/)' || true)
47
- [[ "$SUBSTANCE" -gt 0 ]] || fail "only types/dist changed since $PREV_TAG (spec accretion)"
48
-
49
- # --- 3. Test evidence: test files must have changed ------------------
50
- say "[4/6] test evidence"
51
- TESTS_CHANGED=$(git diff --name-only "$RANGE" | grep -cE '\.(test|spec)\.|/tests?/' || true)
52
- [[ "$TESTS_CHANGED" -gt 0 ]] || fail "zero test changes since $PREV_TAG"
53
-
54
- # --- 4. Cadence: >=4 cycles logged OR >=24h since previous tag, or a recorded HOTFIX ---
55
- say "[5/6] cadence"
56
- CYCLES=0
57
- if [[ -f ITERATION_LOG.md && -n "$PREV_TAG" ]]; then
58
- CYCLES=$(git diff "$RANGE" -- ITERATION_LOG.md | grep -c '^+.*|' || true)
59
- fi
60
- PREV_TS=$(git log -1 --format=%ct "$PREV_TAG")
61
- NOW_TS=$(date +%s)
62
- HOURS=$(( (NOW_TS - PREV_TS) / 3600 ))
63
- # Hotfix path: an urgent fix may ship inside the cadence window, but ONLY via an
64
- # EXPLICIT, RECORDED declaration — a "HOTFIX:" line added to ITERATION_LOG.md in this
65
- # release range, carrying a reason. It is committed (auditable in the tag's history)
66
- # and echoed here, so the bypass is never silent and a reviewer sees the reason.
67
- HOTFIX="$(git diff "$RANGE" -- ITERATION_LOG.md | grep -E '^\+.*HOTFIX:' | head -1 | sed -E 's/^\+[[:space:]]*//' || true)"
68
- if [[ "$CYCLES" -lt 4 && "$HOURS" -lt 24 ]]; then
69
- if [[ -n "$HOTFIX" ]]; then
70
- say " cadence bypassed by recorded HOTFIX (${HOURS}h, ${CYCLES} cycle-lines): ${HOTFIX}"
71
- else
72
- fail "cadence: only $CYCLES cycles logged and ${HOURS}h since $PREV_TAG (need >=4 cycles, >=24h, or a recorded 'HOTFIX:' line in ITERATION_LOG.md)"
73
- fi
74
- fi
75
- else
76
- say "[3-5/6] no previous tag; substance/evidence/cadence checks skipped"
77
- fi
78
-
79
- # --- 5. Branch naming: forbid version-number branches -------------------
80
- say "[6/6] branch naming"
81
- BRANCH="$(git rev-parse --abbrev-ref HEAD)"
82
- if [[ "$BRANCH" =~ ^feat/(batch-)?v?[0-9]+ ]]; then
83
- fail "branch '$BRANCH' is version-number-driven; name the capability instead"
84
- fi
85
-
86
- # --- Verdict ------------------------------------------------------------
87
- if [[ "$FAIL" -ne 0 ]]; then
88
- rm -f "$MARKER_DIR/gate-$SHA.ok"
89
- say "RELEASE GATE: REJECTED ($SHA)"
90
- exit 1
91
- fi
92
-
93
- date -u +"%Y-%m-%dT%H:%M:%SZ" > "$MARKER_DIR/gate-$SHA.ok"
94
- say "RELEASE GATE: PASSED ($SHA) — next step: release-reviewer agent must record APPROVED"
@@ -1,148 +0,0 @@
1
- #!/usr/bin/env bash
2
- # verify-bump-reproduction.sh — closes the HEAD~1 verdict-replay bypass.
3
- #
4
- # release-gate.yml / npm-publish.yml tolerate a verdict written for the tag
5
- # commit's PARENT (release-flow.js's cut() reviews content, then adds a
6
- # mechanical version-bump + the verdict as a NEW child commit that actually
7
- # gets tagged). A validly-signed verdict at the parent proves someone once
8
- # approved THAT commit — it does NOT prove the child (the thing actually being
9
- # tagged) is the deterministic bump cut() would have produced, rather than an
10
- # attacker's own commit smuggling in arbitrary changes on top of an old,
11
- # genuinely-approved parent (a real signature on a REPLAYED verdict).
12
- #
13
- # This script independently reproduces the bump: checks out the approved
14
- # parent into a scratch worktree, runs the SAME bump:version/sync:project-index
15
- # steps cut() runs, stages the SAME way cut() stages (git add -u + the
16
- # explicit verdict/.sig paths, never -A), and requires the resulting tree to
17
- # match the ACTUAL tagged commit's tree byte-for-byte. Any difference —
18
- # anywhere, including a single added file — fails closed.
19
- #
20
- # Usage: verify-bump-reproduction.sh <approved-parent-sha> <tagged-sha> <verdict-repo-relative-path> [sig-repo-relative-path]
21
- #
22
- # Exit 0: the tagged commit's tree matches the reproduced bump exactly.
23
- # Exit 1: mismatch, or any step failed (install, bump, sync, worktree, etc).
24
- set -uo pipefail
25
-
26
- # Where THIS SCRIPT ITSELF lives — distinct from REPO_ROOT (the git repo being
27
- # operated ON, resolved below from the invocation cwd). These coincide in
28
- # ordinary production use (the script runs from within the same checkout it
29
- # verifies), but conflating them is wrong: REPO_ROOT could be an ARBITRARY git
30
- # context (e.g. a test harness's throwaway clone of old history, which never
31
- # has this script's own sibling files, since they postdate whatever was
32
- # cloned). Sibling files this script depends on must be found relative to
33
- # where it is installed, never relative to the repo it happens to be pointed at.
34
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
35
-
36
- PARENT="${1:-}"
37
- TAGGED="${2:-}"
38
- VERDICT_REL="${3:-}"
39
- SIG_REL="${4:-}"
40
- if [[ -z "$PARENT" || -z "$TAGGED" || -z "$VERDICT_REL" ]]; then
41
- echo "usage: verify-bump-reproduction.sh <parent-sha> <tagged-sha> <verdict-rel-path> [sig-rel-path]" >&2
42
- exit 1
43
- fi
44
-
45
- REPO_ROOT="$(git rev-parse --show-toplevel)" || exit 1
46
- SCRATCH_PARENT="$(mktemp -d)"
47
- SCRATCH="$SCRATCH_PARENT/wt"
48
- cleanup() {
49
- git -C "$REPO_ROOT" worktree remove --force "$SCRATCH" >/dev/null 2>&1
50
- rm -rf "$SCRATCH_PARENT"
51
- }
52
- trap cleanup EXIT
53
-
54
- if ! git -C "$REPO_ROOT" worktree add --quiet --detach "$SCRATCH" "$PARENT" 2>/dev/null; then
55
- echo "verify-bump-reproduction: could not create a scratch worktree at $PARENT" >&2
56
- exit 1
57
- fi
58
-
59
- # The target version comes from the TAGGED commit (not the parent, which may
60
- # still be pre-bump) — read via git plumbing, no separate checkout needed.
61
- VERSION="$(git -C "$REPO_ROOT" show "$TAGGED:plugins/cool-workflow/package.json" 2>/dev/null | node -e '
62
- let s=""; process.stdin.on("data",d=>s+=d).on("end",()=>{ try { process.stdout.write(JSON.parse(s).version||""); } catch { process.stdout.write(""); } });
63
- ')"
64
- if [[ -z "$VERSION" ]]; then
65
- echo "verify-bump-reproduction: could not read package.json version from tagged commit $TAGGED" >&2
66
- exit 1
67
- fi
68
-
69
- # sync-project-index.js embeds a wall-clock "Generated on <date>" line. Pin it
70
- # to the TAGGED commit's own committer date (UTC) via a Node --require preload
71
- # that overrides the global Date constructor (fake-date-for-reproduction.js),
72
- # NOT an application-level env var: the scratch worktree runs the APPROVED
73
- # PARENT's OWN checked-out copy of sync-project-index.js, which for every
74
- # release cut before this mechanism existed has no idea any such env var is
75
- # meant to be read — an app-level opt-in is a no-op against code that doesn't
76
- # know to opt in, so it can only ever "work" for releases cut after the
77
- # mechanism landed (confirmed empirically: re-running an already-shipped
78
- # release's real approved-parent/tagged pair on a later calendar day produced
79
- # a tree mismatch whose ONLY diff was that one date line). A global override
80
- # of the Date constructor, injected before any application code runs, is
81
- # transparent to the code being executed regardless of which version it is.
82
- CUT_DATE="$(TZ=UTC git -C "$REPO_ROOT" show -s --format=%cd --date=format-local:%Y-%m-%d "$TAGGED" 2>/dev/null)"
83
- if [[ -z "$CUT_DATE" ]]; then
84
- echo "verify-bump-reproduction: could not read the tagged commit's committer date" >&2
85
- exit 1
86
- fi
87
- FAKE_DATE_PRELOAD="$SCRIPT_DIR/fake-date-for-reproduction.js"
88
-
89
- # Each step's exit code is checked EXPLICITLY and immediately (not via a
90
- # `cmd1 && cmd2 && cmd3` chain) — a chain's overall success can silently
91
- # survive an earlier step's failure if a future edit ever loosens it (e.g.
92
- # `&&` accidentally becoming `;`), which would flip this fail-closed gate to
93
- # fail-open without changing its outward shape. stdout/stderr from each step
94
- # are captured (not discarded to /dev/null) so a real failure — a transient
95
- # npm registry hiccup vs. an actual reproduction mismatch — is distinguishable
96
- # in the workflow log instead of both collapsing into the same generic message.
97
- run_step() {
98
- local label="$1"
99
- shift
100
- local out
101
- out="$("$@" 2>&1)"
102
- local code=$?
103
- if [[ $code -ne 0 ]]; then
104
- echo "verify-bump-reproduction: $label failed (exit $code):" >&2
105
- echo "$out" >&2
106
- return 1
107
- fi
108
- return 0
109
- }
110
-
111
- (
112
- cd "$SCRATCH/plugins/cool-workflow" || exit 1
113
- export NODE_OPTIONS="--require $FAKE_DATE_PRELOAD"
114
- export CW_FAKE_DATE="$CUT_DATE"
115
- run_step "npm install" npm install --no-package-lock --ignore-scripts || exit 1
116
- run_step "bump:version" npm run bump:version -- "$VERSION" || exit 1
117
- run_step "sync:project-index" npm run sync:project-index -- --repo-only || exit 1
118
- )
119
- BUMP_STATUS=$?
120
- if [[ $BUMP_STATUS -ne 0 ]]; then
121
- echo "verify-bump-reproduction: reproducing the bump for $PARENT -> v$VERSION failed" >&2
122
- exit 1
123
- fi
124
-
125
- mkdir -p "$SCRATCH/.cw-release"
126
- cp "$REPO_ROOT/$VERDICT_REL" "$SCRATCH/$VERDICT_REL" || exit 1
127
- if [[ -n "$SIG_REL" && -f "$REPO_ROOT/$SIG_REL" ]]; then
128
- cp "$REPO_ROOT/$SIG_REL" "$SCRATCH/$SIG_REL" || exit 1
129
- fi
130
-
131
- # Mirror cut()'s OWN staging exactly (git-add -u + the explicit verdict/.sig
132
- # paths) — never -A, so an untracked stray left by npm/tooling in the scratch
133
- # worktree can never silently ride into what we compare.
134
- git -C "$SCRATCH" add -u
135
- git -C "$SCRATCH" add -- "$VERDICT_REL"
136
- if [[ -n "$SIG_REL" && -f "$SCRATCH/$SIG_REL" ]]; then
137
- git -C "$SCRATCH" add -- "$SIG_REL"
138
- fi
139
-
140
- EXPECTED_TREE="$(git -C "$SCRATCH" write-tree)"
141
- ACTUAL_TREE="$(git -C "$REPO_ROOT" rev-parse "$TAGGED^{tree}")"
142
-
143
- if [[ "$EXPECTED_TREE" != "$ACTUAL_TREE" ]]; then
144
- echo "verify-bump-reproduction: $TAGGED's tree ($ACTUAL_TREE) does not match the deterministic bump reproduced from approved parent $PARENT ($EXPECTED_TREE) — refusing" >&2
145
- exit 1
146
- fi
147
-
148
- exit 0
@@ -1,64 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
-
4
- // verify-container-selfref — the v0.1.34 self-referential acceptance check.
5
- //
6
- // Runs CW's own `node dist/cli.js list` through the `node` backend AND the
7
- // `container` backend, and asserts result + evidence are byte-IDENTICAL after
8
- // provenance is excluded (backendId/handle/attestation differ by design). This is
9
- // the bar that proves a delegated run is backend-agnostic.
10
- //
11
- // Requires a RUNNING container daemon and a pullable node image. Not part of
12
- // release:check (CI has no daemon); run it manually before tagging v0.1.34.
13
- //
14
- // node scripts/verify-container-selfref.js
15
- // CW_CONTAINER_IMAGE=node:22-slim node scripts/verify-container-selfref.js
16
- //
17
- // Exit: 0 PASS · 1 FAIL (evidence differs) · 2 node backend did not complete ·
18
- // 3 SKIP (container refused — daemon down / image not pullable).
19
-
20
- const path = require("node:path");
21
-
22
- const pluginRoot = path.resolve(__dirname, "..");
23
- const { runBackend } = require(path.join(pluginRoot, "dist/execution-backend.js"));
24
- const { showBundledSandboxProfile, sandboxContextForValidation } = require(path.join(pluginRoot, "dist/sandbox-profile.js"));
25
-
26
- const image = (process.env.CW_CONTAINER_IMAGE || "node:lts-slim").trim();
27
- const cli = path.join(pluginRoot, "dist", "cli.js");
28
- const ctx = sandboxContextForValidation(pluginRoot);
29
- const policy = showBundledSandboxProfile("default", ctx);
30
-
31
- const common = { schemaVersion: 1, command: "node", args: [cli, "list"], cwd: pluginRoot, sandboxPolicy: policy, label: "cw-self-verify" };
32
- const nodeEnv = runBackend({ ...common, backendId: "node" });
33
- const containerEnv = runBackend({ ...common, backendId: "container", delegation: { image } });
34
-
35
- const line = (label, e) => ` ${label.padEnd(9)} status=${e.status} backend=${e.provenance.backendId} evidence=${JSON.stringify(e.evidence)}`;
36
- process.stdout.write(`verify-container-selfref (image: ${image})\n${line("node", nodeEnv)}\n${line("container", containerEnv)}\n`);
37
-
38
- if (nodeEnv.status !== "completed") {
39
- process.stderr.write(`\n✗ node backend did not complete (${nodeEnv.status}) — cannot compare.\n`);
40
- process.exit(2);
41
- }
42
- if (containerEnv.status === "refused") {
43
- process.stderr.write(
44
- `\n⚠ SKIP — container refused (${containerEnv.evidence[0]}).\n` +
45
- ` ${containerEnv.result.summary}\n` +
46
- ` Start the container daemon and ensure the image is pullable (e.g. \`docker pull ${image}\`), then re-run.\n`
47
- );
48
- process.exit(3);
49
- }
50
-
51
- const sameResult = JSON.stringify(nodeEnv.result) === JSON.stringify(containerEnv.result);
52
- const sameEvidence = JSON.stringify(nodeEnv.evidence) === JSON.stringify(containerEnv.evidence);
53
- if (containerEnv.status === "completed" && sameResult && sameEvidence) {
54
- process.stdout.write("\n✓ PASS — container result + evidence are byte-identical to node (provenance differs, as designed).\n");
55
- process.exit(0);
56
- }
57
-
58
- process.stderr.write(
59
- `\n✗ FAIL — container output differs from node (status=${containerEnv.status}):\n` +
60
- ` node evidence: ${JSON.stringify(nodeEnv.evidence)}\n` +
61
- ` container evidence: ${JSON.stringify(containerEnv.evidence)}\n` +
62
- ` (a node-version difference in the image can change stdout; pin CW_CONTAINER_IMAGE to a matching node.)\n`
63
- );
64
- process.exit(1);