cool-workflow 0.2.4 → 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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.gemini-plugin/mcp.json +10 -0
- package/.gemini-plugin/plugin.json +40 -0
- package/.opencode-plugin/mcp.json +10 -0
- package/.opencode-plugin/plugin.json +40 -0
- package/README.md +202 -48
- 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 +32 -4
- package/dist/cli/entry.js +11 -19
- package/dist/cli/global-flags.js +113 -0
- package/dist/cli/parseargv.js +7 -1
- package/dist/core/capability-data.js +337 -8
- package/dist/core/format/help.js +73 -3
- package/dist/core/format/recovery-hint.js +32 -0
- package/dist/core/multi-agent/collaboration.js +35 -6
- package/dist/core/multi-agent/runtime.js +7 -0
- package/dist/core/multi-agent/trust-policy.js +7 -1
- package/dist/core/pipeline/contract.js +7 -0
- package/dist/core/pipeline/error-feedback.js +2 -2
- package/dist/core/trust/evidence-grounding.js +13 -1
- package/dist/core/util/cli-args.js +22 -0
- package/dist/core/version.js +1 -1
- package/dist/mcp/dispatch.js +22 -2
- package/dist/mcp/server.js +124 -13
- package/dist/mcp-server.js +20 -0
- package/dist/shell/commit.js +8 -2
- package/dist/shell/coordinator-io.js +73 -1
- package/dist/shell/drive.js +120 -63
- package/dist/shell/error-feedback-io.js +6 -0
- package/dist/shell/execution-backend/agent.js +195 -23
- package/dist/shell/execution-backend/container.js +44 -10
- package/dist/shell/execution-backend/local.js +32 -0
- package/dist/shell/fs-atomic.js +93 -12
- package/dist/shell/ledger-cli.js +9 -2
- package/dist/shell/multi-agent-cli.js +5 -1
- package/dist/shell/onramp.js +48 -5
- package/dist/shell/pipeline.js +2 -1
- package/dist/shell/reclamation-io.js +10 -9
- package/dist/shell/run-export.js +52 -4
- package/dist/shell/run-store.js +156 -0
- package/dist/shell/scheduler-io.js +101 -10
- package/dist/shell/telemetry-ledger-io.js +36 -24
- package/dist/shell/trust-audit.js +104 -10
- package/dist/shell/workbench-host.js +121 -10
- package/dist/shell/workbench.js +79 -5
- package/dist/shell/worker-isolation.js +1 -1
- package/dist/wiring/capability-table/basics.js +5 -0
- package/dist/wiring/capability-table/exec-backend.js +40 -22
- package/dist/wiring/capability-table/pipeline.js +32 -0
- package/dist/wiring/capability-table/registry-core.js +26 -3
- package/dist/wiring/capability-table/reporting.js +7 -1
- package/dist/wiring/capability-table/scheduling-registry.js +8 -2
- package/dist/wiring/capability-table/trust-ledger.js +54 -12
- package/docs/agent-delegation-drive.7.md +15 -0
- package/docs/cli-mcp-parity.7.md +25 -0
- package/docs/contract-migration-tooling.7.md +2 -0
- package/docs/control-plane-scheduling.7.md +36 -0
- package/docs/cross-agent-ledger.7.md +20 -8
- package/docs/durable-state-and-locking.7.md +2 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
- package/docs/execution-backends.7.md +2 -0
- package/docs/fix.7.md +4 -4
- package/docs/getting-started.md +40 -32
- package/docs/index.md +17 -0
- package/docs/launch/demo.tape +4 -3
- package/docs/mcp-app-surface.7.md +6 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
- package/docs/multi-agent-eval-replay-harness.7.md +2 -0
- package/docs/multi-agent-operator-ux.7.md +2 -0
- package/docs/node-snapshot-diff-replay.7.md +2 -0
- package/docs/observability-cost-accounting.7.md +2 -0
- package/docs/project-index.md +39 -9
- package/docs/real-execution-backends.7.md +2 -0
- package/docs/release-and-migration.7.md +2 -0
- package/docs/release-tooling.7.md +2 -0
- package/docs/routine.7.md +22 -0
- package/docs/run-registry-control-plane.7.md +2 -0
- package/docs/run-retention-reclamation.7.md +10 -4
- package/docs/state-explosion-management.7.md +2 -0
- package/docs/team-collaboration.7.md +2 -0
- package/docs/trust-audit-anchor.7.md +2 -0
- package/docs/web-desktop-workbench.7.md +24 -2
- package/docs/workflow-app-framework.7.md +31 -0
- package/manifest/plugin.manifest.json +1 -1
- package/manifest/source-context-profiles.json +4 -3
- package/package.json +4 -2
- package/scripts/agents/agent-adapter-core.js +26 -0
- package/scripts/agents/claude-p-agent.js +4 -1
- package/scripts/agents/codex-agent.js +4 -0
- package/scripts/agents/cw-attest-wrap.js +1 -1
- package/scripts/agents/gemini-agent.js +4 -0
- package/scripts/agents/opencode-agent.js +5 -0
- package/scripts/block-unapproved-tag.js +160 -0
- package/scripts/canonical-apps.js +4 -4
- package/scripts/children/batch-delegate-child.js +72 -4
- package/scripts/children/http-batch-delegate-child.js +132 -0
- package/scripts/children/http-delegate-child.js +8 -0
- package/scripts/dogfood-release.js +2 -2
- package/scripts/fake-date-for-reproduction.js +1 -1
- package/scripts/golden-path.js +4 -4
- package/scripts/lang-policy-check.js +82 -0
- package/scripts/purity-gate.js +16 -3
- package/scripts/release-check.js +2 -1
- package/scripts/release-flow.js +128 -30
- package/scripts/release-gate.js +184 -0
- package/scripts/release-oneclick.js +38 -7
- package/scripts/release-tags.js +47 -0
- package/scripts/verdict-keygen.js +2 -2
- package/scripts/verify-bump-reproduction.js +193 -0
- package/scripts/verify-verdict-signature.js +1 -1
- package/ui/workbench/app.css +41 -9
- package/ui/workbench/app.js +160 -27
- package/ui/workbench/index.html +2 -2
- package/docs/agent-framework.md +0 -177
- package/docs/designs/handoff-ledger.md +0 -145
- package/docs/dogfood/resume-drive-real-agent-2026-06-14.md +0 -40
- package/docs/launch/launch-kit.md +0 -195
- package/docs/launch/pre-launch-checklist.md +0 -53
- package/docs/readme-v0.1.87-full.md +0 -301
- package/docs/routines.md +0 -101
- package/docs/scheduled-tasks.md +0 -80
- package/scripts/agents/claude-p-agent.sh +0 -9
- package/scripts/block-unapproved-tag.sh +0 -75
- package/scripts/release-gate.sh +0 -94
- package/scripts/verify-bump-reproduction.sh +0 -148
- package/scripts/verify-container-selfref.js +0 -64
package/docs/scheduled-tasks.md
DELETED
|
@@ -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,75 +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
|
-
# 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"
|
|
50
|
-
|
|
51
|
-
if [[ ! -f "$GATE" ]]; then
|
|
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
|
|
53
|
-
exit 2
|
|
54
|
-
fi
|
|
55
|
-
|
|
56
|
-
if [[ ! -f "$VERDICT" ]] || ! grep -q '^APPROVED' "$VERDICT"; then
|
|
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
|
|
58
|
-
exit 2
|
|
59
|
-
fi
|
|
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
|
-
|
|
75
|
-
exit 0
|
package/scripts/release-gate.sh
DELETED
|
@@ -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);
|