cool-workflow 0.1.78
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 +20 -0
- package/.codex-plugin/mcp.json +10 -0
- package/.codex-plugin/plugin.json +38 -0
- package/.mcp.json +10 -0
- package/LICENSE +24 -0
- package/README.md +638 -0
- package/apps/architecture-review/app.json +51 -0
- package/apps/architecture-review/workflow.js +116 -0
- package/apps/end-to-end-golden-path/app.json +30 -0
- package/apps/end-to-end-golden-path/workflow.js +33 -0
- package/apps/pr-review-fix-ci/app.json +59 -0
- package/apps/pr-review-fix-ci/workflow.js +90 -0
- package/apps/release-cut/app.json +54 -0
- package/apps/release-cut/workflow.js +82 -0
- package/apps/research-synthesis/app.json +50 -0
- package/apps/research-synthesis/workflow.js +76 -0
- package/apps/workflow-app-framework-demo/app.json +29 -0
- package/apps/workflow-app-framework-demo/workflow.js +44 -0
- package/dist/agent-config.js +223 -0
- package/dist/candidate-scoring.js +715 -0
- package/dist/capability-core.js +630 -0
- package/dist/capability-dispatcher.js +86 -0
- package/dist/capability-registry.js +523 -0
- package/dist/cli.js +1276 -0
- package/dist/collaboration.js +727 -0
- package/dist/commit.js +570 -0
- package/dist/contract-migration.js +234 -0
- package/dist/coordinator.js +1163 -0
- package/dist/daemon.js +44 -0
- package/dist/dispatch.js +201 -0
- package/dist/drive.js +503 -0
- package/dist/error-feedback.js +415 -0
- package/dist/evidence-grounding.js +179 -0
- package/dist/evidence-reasoning.js +733 -0
- package/dist/execution-backend.js +1279 -0
- package/dist/harness.js +61 -0
- package/dist/mcp-server.js +1615 -0
- package/dist/multi-agent-eval.js +857 -0
- package/dist/multi-agent-host.js +764 -0
- package/dist/multi-agent-operator-ux.js +537 -0
- package/dist/multi-agent-trust.js +366 -0
- package/dist/multi-agent.js +1173 -0
- package/dist/node-snapshot.js +270 -0
- package/dist/observability.js +922 -0
- package/dist/operator-ux.js +971 -0
- package/dist/orchestrator/audit-operations.js +182 -0
- package/dist/orchestrator/candidate-operations.js +117 -0
- package/dist/orchestrator/cli-options.js +288 -0
- package/dist/orchestrator/collaboration-operations.js +86 -0
- package/dist/orchestrator/feedback-operations.js +81 -0
- package/dist/orchestrator/host-operations.js +78 -0
- package/dist/orchestrator/lifecycle-operations.js +462 -0
- package/dist/orchestrator/migration-operations.js +44 -0
- package/dist/orchestrator/multi-agent-operations.js +362 -0
- package/dist/orchestrator/report.js +369 -0
- package/dist/orchestrator/topology-operations.js +84 -0
- package/dist/orchestrator.js +874 -0
- package/dist/pipeline-contract.js +92 -0
- package/dist/pipeline-runner.js +285 -0
- package/dist/reclamation.js +882 -0
- package/dist/result-normalize.js +194 -0
- package/dist/run-export.js +64 -0
- package/dist/run-registry.js +1347 -0
- package/dist/run-state-schema.js +67 -0
- package/dist/sandbox-profile.js +471 -0
- package/dist/scheduler.js +266 -0
- package/dist/scheduling.js +184 -0
- package/dist/schema-validate.js +98 -0
- package/dist/state-explosion.js +1213 -0
- package/dist/state-migrations.js +463 -0
- package/dist/state-node.js +301 -0
- package/dist/state.js +308 -0
- package/dist/telemetry-attestation.js +156 -0
- package/dist/telemetry-ledger.js +145 -0
- package/dist/topology.js +527 -0
- package/dist/triggers.js +159 -0
- package/dist/trust-audit.js +475 -0
- package/dist/types/blackboard.js +2 -0
- package/dist/types/boundary.js +29 -0
- package/dist/types/candidate.js +2 -0
- package/dist/types/collaboration.js +2 -0
- package/dist/types/core.js +2 -0
- package/dist/types/drive.js +10 -0
- package/dist/types/error-feedback.js +2 -0
- package/dist/types/evidence-reasoning.js +2 -0
- package/dist/types/execution-backend.js +2 -0
- package/dist/types/multi-agent.js +2 -0
- package/dist/types/observability.js +2 -0
- package/dist/types/pipeline.js +2 -0
- package/dist/types/reclamation.js +8 -0
- package/dist/types/result.js +2 -0
- package/dist/types/run-registry.js +2 -0
- package/dist/types/run.js +2 -0
- package/dist/types/sandbox.js +2 -0
- package/dist/types/schedule.js +2 -0
- package/dist/types/state-node.js +2 -0
- package/dist/types/topology.js +2 -0
- package/dist/types/trust.js +2 -0
- package/dist/types/workbench.js +2 -0
- package/dist/types/worker.js +2 -0
- package/dist/types/workflow-app.js +2 -0
- package/dist/types.js +43 -0
- package/dist/verifier-registry.js +46 -0
- package/dist/verifier.js +78 -0
- package/dist/version.js +8 -0
- package/dist/workbench-host.js +172 -0
- package/dist/workbench.js +190 -0
- package/dist/worker-isolation.js +1028 -0
- package/dist/workflow-api.js +98 -0
- package/dist/workflow-app-framework.js +626 -0
- package/docs/agent-delegation-drive.7.md +190 -0
- package/docs/agent-framework.md +176 -0
- package/docs/candidate-scoring.7.md +106 -0
- package/docs/canonical-workflow-apps.7.md +137 -0
- package/docs/capability-topology-registry.7.md +168 -0
- package/docs/cli-mcp-parity.7.md +373 -0
- package/docs/contract-migration-tooling.7.md +123 -0
- package/docs/control-plane-scheduling.7.md +110 -0
- package/docs/coordinator-blackboard.7.md +183 -0
- package/docs/dogfood/architecture-review-cool-workflow.md +16 -0
- package/docs/dogfood-one-real-repo.7.md +168 -0
- package/docs/durable-state-and-locking.7.md +107 -0
- package/docs/end-to-end-golden-path.7.md +117 -0
- package/docs/error-feedback.7.md +153 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +270 -0
- package/docs/execution-backends.7.md +300 -0
- package/docs/getting-started.md +99 -0
- package/docs/index.md +41 -0
- package/docs/mcp-app-surface.7.md +235 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +265 -0
- package/docs/multi-agent-eval-replay-harness.7.md +302 -0
- package/docs/multi-agent-operator-ux.7.md +314 -0
- package/docs/multi-agent-runtime-core.7.md +231 -0
- package/docs/multi-agent-topologies.7.md +103 -0
- package/docs/multi-agent-trust-policy-audit.7.md +154 -0
- package/docs/node-snapshot-diff-replay.7.md +135 -0
- package/docs/observability-cost-accounting.7.md +194 -0
- package/docs/operator-ux.7.md +180 -0
- package/docs/pipeline-runner.7.md +136 -0
- package/docs/project-index.md +261 -0
- package/docs/real-execution-backends.7.md +142 -0
- package/docs/release-and-migration.7.md +280 -0
- package/docs/release-tooling.7.md +159 -0
- package/docs/routines.md +48 -0
- package/docs/run-registry-control-plane.7.md +312 -0
- package/docs/run-retention-reclamation.7.md +191 -0
- package/docs/sandbox-profiles.7.md +137 -0
- package/docs/scheduled-tasks.md +80 -0
- package/docs/security-trust-hardening.7.md +117 -0
- package/docs/state-explosion-management.7.md +264 -0
- package/docs/state-node.7.md +96 -0
- package/docs/team-collaboration.7.md +207 -0
- package/docs/unix-principles.md +192 -0
- package/docs/verifier-gated-commit.7.md +140 -0
- package/docs/web-desktop-workbench.7.md +215 -0
- package/docs/worker-isolation.7.md +167 -0
- package/docs/workflow-app-framework.7.md +274 -0
- package/manifest/README.md +43 -0
- package/manifest/plugin.manifest.json +316 -0
- package/manifest/pricing.policy.json +14 -0
- package/package.json +79 -0
- package/scripts/agents/claude-p-agent.js +104 -0
- package/scripts/agents/claude-p-agent.sh +9 -0
- package/scripts/agents/cw-attest-keygen.js +55 -0
- package/scripts/agents/cw-attest-wrap.js +143 -0
- package/scripts/block-unapproved-tag.sh +39 -0
- package/scripts/bump-version.js +249 -0
- package/scripts/canonical-apps.js +171 -0
- package/scripts/cw.js +4 -0
- package/scripts/dist-drift-check.js +79 -0
- package/scripts/dogfood-architecture-review.js +237 -0
- package/scripts/dogfood-release.js +624 -0
- package/scripts/forward-ref-docs.js +73 -0
- package/scripts/gen-manifests.js +232 -0
- package/scripts/golden-path.js +300 -0
- package/scripts/mcp-server.js +4 -0
- package/scripts/new-feature.js +121 -0
- package/scripts/parity-check.js +213 -0
- package/scripts/release-check.js +118 -0
- package/scripts/release-flow.js +272 -0
- package/scripts/release-gate.sh +85 -0
- package/scripts/sync-project-index.js +387 -0
- package/scripts/validate-run-state-schema.js +126 -0
- package/scripts/verify-container-selfref.js +64 -0
- package/scripts/version-sync-check.js +237 -0
- package/skills/cool-workflow/SKILL.md +162 -0
- package/skills/cool-workflow/references/commands.md +282 -0
- package/tsconfig.json +16 -0
- package/ui/workbench/app.css +76 -0
- package/ui/workbench/app.js +159 -0
- package/ui/workbench/index.html +32 -0
- package/workflows/architecture-review.workflow.js +84 -0
- package/workflows/research-synthesis.workflow.js +47 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// cw-attest-wrap (Track 1) — EXECUTOR-SIDE telemetry signing wrapper.
|
|
5
|
+
//
|
|
6
|
+
// This is operator CONFIG, NOT a CW dependency. CW spawns it out-of-process; the
|
|
7
|
+
// model runs in the INNER agent's process, never in CW. The wrapper's only job is
|
|
8
|
+
// to SIGN the agent's self-reported usage so CW can verify it `attested` instead
|
|
9
|
+
// of recording an unverifiable claim. CW holds only the PUBLIC key and verifies;
|
|
10
|
+
// this wrapper holds the PRIVATE key (CW_AGENT_ATTEST_PRIVKEY) and signs. It does
|
|
11
|
+
// NOT call a model and imports no model SDK — it execs whatever agent you name.
|
|
12
|
+
//
|
|
13
|
+
// It signs the EXACT same canonical payload CW verifies — {usage, runId, taskId,
|
|
14
|
+
// promptDigest} — sharing the canonicalization with the verifier via
|
|
15
|
+
// dist/telemetry-attestation.js, so signer and verifier can never drift.
|
|
16
|
+
//
|
|
17
|
+
// Usage (wrap any agent that prints a {model, usage} JSON report on stdout):
|
|
18
|
+
// node cw-attest-wrap.js --manifest {{manifest}} -- <agent-cmd> [agent-args...]
|
|
19
|
+
//
|
|
20
|
+
// Point CW at it (from plugins/cool-workflow/), keeping your inner agent intact:
|
|
21
|
+
// CW_AGENT_ATTEST_PRIVKEY=$PWD/cw-attest.key \
|
|
22
|
+
// CW_AGENT_COMMAND="node $PWD/scripts/agents/cw-attest-wrap.js --manifest {{manifest}} -- \
|
|
23
|
+
// bash $PWD/scripts/agents/claude-p-agent.sh {{input}} {{result}}"
|
|
24
|
+
// and configure CW's verify side: CW_AGENT_ATTEST_PUBKEY=$PWD/cw-attest.pub
|
|
25
|
+
//
|
|
26
|
+
// Honest posture: if no private key is set, or the agent reports no usage, the
|
|
27
|
+
// wrapper passes the report through UNSIGNED — CW then records it `unattested`,
|
|
28
|
+
// never a fabricated attestation.
|
|
29
|
+
|
|
30
|
+
const fs = require("node:fs");
|
|
31
|
+
const crypto = require("node:crypto");
|
|
32
|
+
const path = require("node:path");
|
|
33
|
+
const { spawnSync } = require("node:child_process");
|
|
34
|
+
|
|
35
|
+
const ta = require(path.resolve(__dirname, "..", "..", "dist", "telemetry-attestation.js"));
|
|
36
|
+
|
|
37
|
+
function fail(message) {
|
|
38
|
+
process.stderr.write(`cw-attest-wrap: ${message}\n`);
|
|
39
|
+
process.exit(2);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseArgs(argv) {
|
|
43
|
+
let manifestPath;
|
|
44
|
+
const inner = [];
|
|
45
|
+
let afterSep = false;
|
|
46
|
+
for (let i = 0; i < argv.length; i++) {
|
|
47
|
+
const arg = argv[i];
|
|
48
|
+
if (afterSep) {
|
|
49
|
+
inner.push(arg);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (arg === "--") afterSep = true;
|
|
53
|
+
else if (arg === "--manifest") manifestPath = argv[++i];
|
|
54
|
+
else fail(`unexpected arg before "--": ${arg}`);
|
|
55
|
+
}
|
|
56
|
+
if (!inner.length) fail('no inner agent command after "--"');
|
|
57
|
+
return { manifestPath, inner };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Tolerant parse of the inner agent's JSON report — whole stdout, else the last
|
|
61
|
+
// {..} line. Mirrors CW's parseAgentReport so the wrapper sees the same usage.
|
|
62
|
+
function parseReport(stdout) {
|
|
63
|
+
const text = String(stdout || "").trim();
|
|
64
|
+
if (!text) return undefined;
|
|
65
|
+
const tryObj = (value) => {
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(value);
|
|
68
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : undefined;
|
|
69
|
+
} catch {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
let obj = tryObj(text);
|
|
74
|
+
if (!obj) {
|
|
75
|
+
const line = text
|
|
76
|
+
.split(/\r?\n/)
|
|
77
|
+
.reverse()
|
|
78
|
+
.find((entry) => entry.trim().startsWith("{") && entry.trim().endsWith("}"));
|
|
79
|
+
if (line) obj = tryObj(line.trim());
|
|
80
|
+
}
|
|
81
|
+
return obj;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function sha256(value) {
|
|
85
|
+
return `sha256:${crypto.createHash("sha256").update(value, "utf8").digest("hex")}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function resolveKey(value) {
|
|
89
|
+
if (!value) return undefined;
|
|
90
|
+
const trimmed = String(value).trim();
|
|
91
|
+
if (!trimmed) return undefined;
|
|
92
|
+
if (trimmed.includes("BEGIN") && trimmed.includes("KEY")) return trimmed;
|
|
93
|
+
try {
|
|
94
|
+
if (fs.existsSync(trimmed)) return fs.readFileSync(trimmed, "utf8");
|
|
95
|
+
} catch {
|
|
96
|
+
/* fall through */
|
|
97
|
+
}
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function main() {
|
|
102
|
+
const { manifestPath, inner } = parseArgs(process.argv.slice(2));
|
|
103
|
+
|
|
104
|
+
// Run the inner agent: stderr passes through live; stdout is captured to sign.
|
|
105
|
+
const child = spawnSync(inner[0], inner.slice(1), {
|
|
106
|
+
encoding: "utf8",
|
|
107
|
+
stdio: ["inherit", "pipe", "inherit"],
|
|
108
|
+
maxBuffer: 64 * 1024 * 1024
|
|
109
|
+
});
|
|
110
|
+
const exitCode = typeof child.status === "number" ? child.status : 1;
|
|
111
|
+
const stdout = String(child.stdout || "");
|
|
112
|
+
|
|
113
|
+
// Best-effort signing. Any failure ⇒ pass the report through UNSIGNED (CW will
|
|
114
|
+
// record it `unattested`), never block the hop or fabricate a signature.
|
|
115
|
+
let out = stdout;
|
|
116
|
+
try {
|
|
117
|
+
const report = parseReport(stdout);
|
|
118
|
+
const privateKey = resolveKey(process.env.CW_AGENT_ATTEST_PRIVKEY);
|
|
119
|
+
const manifest = manifestPath && fs.existsSync(manifestPath) ? JSON.parse(fs.readFileSync(manifestPath, "utf8")) : undefined;
|
|
120
|
+
if (report && report.usage && privateKey && manifest) {
|
|
121
|
+
const inputPath = manifest.inputPath;
|
|
122
|
+
const promptDigest = inputPath && fs.existsSync(inputPath) ? sha256(fs.readFileSync(inputPath, "utf8")) : sha256(manifest.prompt || "");
|
|
123
|
+
const signature = ta.signTelemetry(report.usage, privateKey, {
|
|
124
|
+
runId: manifest.runId,
|
|
125
|
+
taskId: manifest.taskId,
|
|
126
|
+
promptDigest
|
|
127
|
+
});
|
|
128
|
+
out = JSON.stringify({ ...report, usageSignature: signature });
|
|
129
|
+
} else if (report) {
|
|
130
|
+
// Re-emit a clean single JSON object so CW's parse is unambiguous.
|
|
131
|
+
out = JSON.stringify(report);
|
|
132
|
+
if (report.usage && !privateKey) process.stderr.write("cw-attest-wrap: no CW_AGENT_ATTEST_PRIVKEY — emitting UNSIGNED (CW will record unattested)\n");
|
|
133
|
+
}
|
|
134
|
+
} catch (error) {
|
|
135
|
+
process.stderr.write(`cw-attest-wrap: signing skipped (${error && error.message ? error.message : error}) — emitting UNSIGNED\n`);
|
|
136
|
+
out = stdout;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
process.stdout.write(out);
|
|
140
|
+
process.exit(exitCode);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
main();
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
# Exit 2 blocks the tool call; stderr is fed back to the agent.
|
|
8
|
+
set -uo pipefail
|
|
9
|
+
|
|
10
|
+
INPUT="$(cat)"
|
|
11
|
+
# Parse the tool command with node, not jq: node is guaranteed present in this
|
|
12
|
+
# Node project (and matches the repo's node/npm/git-only portability rule),
|
|
13
|
+
# whereas jq is not installed in every Claude Code environment. A missing jq
|
|
14
|
+
# would make this security hook silently fail OPEN (empty command → exit 0),
|
|
15
|
+
# letting an unreviewed tag through. node keeps it portable and fail-closed.
|
|
16
|
+
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
|
+
[[ -z "$CMD" ]] && exit 0
|
|
18
|
+
|
|
19
|
+
# Only care about tag creation / tag push
|
|
20
|
+
if ! printf '%s' "$CMD" | grep -qE 'git\s+tag\s+(-a\s+)?v|git\s+push\b.*(--tags|refs/tags)'; then
|
|
21
|
+
exit 0
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0
|
|
25
|
+
SHA="$(git -C "$REPO_ROOT" rev-parse HEAD)"
|
|
26
|
+
GATE="$REPO_ROOT/.cw-release/gate-$SHA.ok"
|
|
27
|
+
VERDICT="$REPO_ROOT/.cw-release/review-$SHA.verdict"
|
|
28
|
+
|
|
29
|
+
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
|
|
31
|
+
exit 2
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
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
|
|
36
|
+
exit 2
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
exit 0
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// One-shot version bump across every STRUCTURED surface, then gate or auto-fix
|
|
5
|
+
// the CONTENT surfaces (docs, CHANGELOG, RELEASE, README) that must mention the
|
|
6
|
+
// new version for version-sync-check and dogfood-release to pass.
|
|
7
|
+
//
|
|
8
|
+
// node scripts/bump-version.js <new-version> # gate (fail if content missing)
|
|
9
|
+
// node scripts/bump-version.js <new-version> --content # auto-append version placeholders
|
|
10
|
+
// npm run bump:version -- 0.1.33
|
|
11
|
+
// npm run bump:version -- 0.1.33 --content
|
|
12
|
+
//
|
|
13
|
+
// BSD discipline (v0.1.52 corrective action):
|
|
14
|
+
// - MECHANISM: structured surfaces are deterministically bumped by this script.
|
|
15
|
+
// Content surfaces are gated (fail-closed default) or auto-filled (--content).
|
|
16
|
+
// - FAIL CLOSED: by default, any content surface missing the new version FAILS
|
|
17
|
+
// the bump with a precise list of files to update. Tagging a release with
|
|
18
|
+
// missing content surfaces is now blocked at the bump step.
|
|
19
|
+
// - POLICY: --content auto-appends placeholders for convenience; the human
|
|
20
|
+
// still edits the prose to describe the actual release.
|
|
21
|
+
|
|
22
|
+
const { spawnSync } = require("node:child_process");
|
|
23
|
+
const fs = require("node:fs");
|
|
24
|
+
const path = require("node:path");
|
|
25
|
+
|
|
26
|
+
const pluginRoot = path.resolve(__dirname, "..");
|
|
27
|
+
const repoRoot = path.resolve(pluginRoot, "..", "..");
|
|
28
|
+
|
|
29
|
+
const SEMVER = /^\d+\.\d+\.\d+$/;
|
|
30
|
+
const GATE = !process.argv.includes("--content");
|
|
31
|
+
const CONTENT = process.argv.includes("--content");
|
|
32
|
+
|
|
33
|
+
function fail(msg) {
|
|
34
|
+
process.stderr.write(`bump:version error: ${msg}\n`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function replaceFirstVersionField(absPath, next) {
|
|
39
|
+
// Replace ONLY the first `"version": "x.y.z"` (the top-level / identity one);
|
|
40
|
+
// preserves byte formatting and never touches nested minVersion fields.
|
|
41
|
+
const text = fs.readFileSync(absPath, "utf8");
|
|
42
|
+
const updated = text.replace(/"version":\s*"[^"]*"/, `"version": "${next}"`);
|
|
43
|
+
if (updated === text) return false;
|
|
44
|
+
fs.writeFileSync(absPath, updated);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function setNestedVersion(absPath, next) {
|
|
49
|
+
// For files where the first `"version"` is NOT the right one, parse + set.
|
|
50
|
+
const json = JSON.parse(fs.readFileSync(absPath, "utf8"));
|
|
51
|
+
json.identity.version = next;
|
|
52
|
+
fs.writeFileSync(absPath, `${JSON.stringify(json, null, 2)}\n`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function main() {
|
|
56
|
+
const next = process.argv[2];
|
|
57
|
+
if (!next) fail("usage: node scripts/bump-version.js <new-version>");
|
|
58
|
+
if (!SEMVER.test(next)) fail(`"${next}" is not a x.y.z version`);
|
|
59
|
+
|
|
60
|
+
const current = JSON.parse(fs.readFileSync(path.join(pluginRoot, "package.json"), "utf8")).version;
|
|
61
|
+
if (next === current) fail(`already at ${next}`);
|
|
62
|
+
|
|
63
|
+
const touched = [];
|
|
64
|
+
const note = (rel) => touched.push(rel);
|
|
65
|
+
|
|
66
|
+
// 1. package.json (the single source of truth version:sync now reads from)
|
|
67
|
+
if (replaceFirstVersionField(path.join(pluginRoot, "package.json"), next)) note("package.json");
|
|
68
|
+
|
|
69
|
+
// 2. package-lock.json (gitignored install artifact; only if present)
|
|
70
|
+
const lock = path.join(pluginRoot, "package-lock.json");
|
|
71
|
+
if (fs.existsSync(lock) && replaceFirstVersionField(lock, next)) note("package-lock.json");
|
|
72
|
+
|
|
73
|
+
// 3. src/version.ts runtime constant
|
|
74
|
+
const versionTs = path.join(pluginRoot, "src", "version.ts");
|
|
75
|
+
const vtsText = fs.readFileSync(versionTs, "utf8");
|
|
76
|
+
const vtsNext = vtsText.replace(
|
|
77
|
+
/(CURRENT_COOL_WORKFLOW_VERSION\s*=\s*)"[^"]*"/,
|
|
78
|
+
`$1"${next}"`
|
|
79
|
+
);
|
|
80
|
+
if (vtsNext !== vtsText) {
|
|
81
|
+
fs.writeFileSync(versionTs, vtsNext);
|
|
82
|
+
note("src/version.ts");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 4. manifest/plugin.manifest.json (identity.version) — gen:manifests then
|
|
86
|
+
// propagates to .claude-plugin / .codex-plugin / .agents / .mcp.json
|
|
87
|
+
setNestedVersion(path.join(pluginRoot, "manifest", "plugin.manifest.json"), next);
|
|
88
|
+
note("manifest/plugin.manifest.json");
|
|
89
|
+
|
|
90
|
+
// 5. canonical apps app.json (top-level version only; never minVersion).
|
|
91
|
+
// ONLY the canonical apps track the runtime version — workflow-app-framework-demo
|
|
92
|
+
// is pinned (e.g. 0.1.0) and must NOT be bumped. This list mirrors the one
|
|
93
|
+
// version-sync-check.js asserts.
|
|
94
|
+
const CANONICAL_APPS = [
|
|
95
|
+
"architecture-review",
|
|
96
|
+
"end-to-end-golden-path",
|
|
97
|
+
"pr-review-fix-ci",
|
|
98
|
+
"release-cut",
|
|
99
|
+
"research-synthesis"
|
|
100
|
+
];
|
|
101
|
+
for (const appId of CANONICAL_APPS) {
|
|
102
|
+
const appJson = path.join(pluginRoot, "apps", appId, "app.json");
|
|
103
|
+
if (fs.existsSync(appJson) && replaceFirstVersionField(appJson, next)) {
|
|
104
|
+
note(`apps/${appId}/app.json`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 5b. Scripts + test assertions that hard-code the CURRENT version as a
|
|
109
|
+
// current-version reference. A TARGETED `current -> next` replace is safe:
|
|
110
|
+
// it only swaps the exact old version string, leaving historical refs
|
|
111
|
+
// (minVersion, "pre-vX", fixed demo versions) untouched. This is the
|
|
112
|
+
// surface that caused the stale `@0.1.31` failure. Docs and CHANGELOG are
|
|
113
|
+
// intentionally excluded — their version labels are historical feature tags.
|
|
114
|
+
const targeted = [
|
|
115
|
+
"scripts/golden-path.js",
|
|
116
|
+
"scripts/canonical-apps.js",
|
|
117
|
+
"scripts/dogfood-release.js",
|
|
118
|
+
"test/dogfood-release-smoke.js",
|
|
119
|
+
"test/mcp-app-surface-smoke.js",
|
|
120
|
+
"test/canonical-workflow-apps-smoke.js",
|
|
121
|
+
"test/workflow-app-framework-smoke.js",
|
|
122
|
+
"test/operator-ux-smoke.js"
|
|
123
|
+
];
|
|
124
|
+
// The version appears in THREE forms across scripts/tests: plain (`0.1.32`),
|
|
125
|
+
// a regex literal (`0\.1\.32`, from `assert.match(report, /...@0\.1\.32/)`),
|
|
126
|
+
// and a RegExp string (`0\\.1\\.32`, from `new RegExp(\`...@0\\.1\\.32\`)`).
|
|
127
|
+
// A plain replace silently misses the escaped forms — the exact "escaped-dot"
|
|
128
|
+
// surface that fails version:sync mid-release. Replace all three, most-escaped
|
|
129
|
+
// first so the forms can never overlap.
|
|
130
|
+
const esc1 = (v) => v.replace(/\./g, "\\.");
|
|
131
|
+
const esc2 = (v) => v.replace(/\./g, "\\\\.");
|
|
132
|
+
const forms = [[esc2(current), esc2(next)], [esc1(current), esc1(next)], [current, next]];
|
|
133
|
+
for (const rel of targeted) {
|
|
134
|
+
const abs = path.join(pluginRoot, rel);
|
|
135
|
+
if (!fs.existsSync(abs)) continue;
|
|
136
|
+
let text = fs.readFileSync(abs, "utf8");
|
|
137
|
+
if (!forms.some(([from]) => text.includes(from))) continue;
|
|
138
|
+
for (const [from, to] of forms) text = text.split(from).join(to);
|
|
139
|
+
fs.writeFileSync(abs, text);
|
|
140
|
+
note(rel);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
process.stdout.write(`bump:version ${current} -> ${next}\n`);
|
|
144
|
+
for (const rel of touched) process.stdout.write(` updated ${rel}\n`);
|
|
145
|
+
|
|
146
|
+
// 6. Regenerate vendor manifests + rebuild dist so dist/version.js follows.
|
|
147
|
+
for (const cmd of [["npm", "run", "gen:manifests"], ["npm", "run", "build"]]) {
|
|
148
|
+
process.stdout.write(` run ${cmd.join(" ")}\n`);
|
|
149
|
+
const r = spawnSync(cmd[0], cmd.slice(1), { cwd: pluginRoot, stdio: "pipe", encoding: "utf8" });
|
|
150
|
+
if (r.status !== 0) fail(`${cmd.join(" ")} failed:\n${r.stdout}\n${r.stderr}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 7. Content-surface gate or auto-fix (v0.1.52 corrective action).
|
|
154
|
+
// By default (--gate): run version-sync-check and FAIL if any content
|
|
155
|
+
// surface is missing the new version, listing exactly which files to update.
|
|
156
|
+
// With --content: auto-append version placeholders to all missing surfaces.
|
|
157
|
+
const contentResult = handleContentSurfaces(current, next);
|
|
158
|
+
if (!contentResult.ok) fail(contentResult.error);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---- Content-surface handling (v0.1.52) -----------------------------------
|
|
162
|
+
|
|
163
|
+
function contentSurfaceFiles(next) {
|
|
164
|
+
// All files the version-sync-check script validates for VERSION or vX.Y.Z.
|
|
165
|
+
// Keep in sync with scripts/version-sync-check.js.
|
|
166
|
+
return [
|
|
167
|
+
{ path: "plugins/cool-workflow/README.md", needle: `v${next}`, desc: "README version tag" },
|
|
168
|
+
{ path: "plugins/cool-workflow/docs/multi-agent-cli-mcp-surface.7.md", needle: next, desc: "multi-agent CLI/MCP surface doc" },
|
|
169
|
+
{ path: "plugins/cool-workflow/docs/multi-agent-operator-ux.7.md", needle: next, desc: "multi-agent operator UX doc" },
|
|
170
|
+
{ path: "plugins/cool-workflow/docs/multi-agent-eval-replay-harness.7.md", needle: next, desc: "multi-agent eval/replay doc" },
|
|
171
|
+
{ path: "plugins/cool-workflow/docs/state-explosion-management.7.md", needle: next, desc: "state explosion doc" },
|
|
172
|
+
{ path: "plugins/cool-workflow/docs/evidence-adoption-reasoning-chain.7.md", needle: next, desc: "evidence reasoning doc" },
|
|
173
|
+
{ path: "plugins/cool-workflow/docs/cli-mcp-parity.7.md", needle: next, desc: "CLI/MCP parity doc" },
|
|
174
|
+
{ path: "plugins/cool-workflow/docs/run-registry-control-plane.7.md", needle: next, desc: "run registry doc" },
|
|
175
|
+
{ path: "plugins/cool-workflow/docs/execution-backends.7.md", needle: next, desc: "execution backends doc" },
|
|
176
|
+
{ path: "plugins/cool-workflow/docs/web-desktop-workbench.7.md", needle: next, desc: "workbench doc" },
|
|
177
|
+
{ path: "plugins/cool-workflow/docs/observability-cost-accounting.7.md", needle: next, desc: "observability doc" },
|
|
178
|
+
{ path: "plugins/cool-workflow/docs/team-collaboration.7.md", needle: next, desc: "team collaboration doc" },
|
|
179
|
+
{ path: "plugins/cool-workflow/docs/release-tooling.7.md", needle: next, desc: "release tooling doc" },
|
|
180
|
+
{ path: "plugins/cool-workflow/docs/real-execution-backends.7.md", needle: next, desc: "real execution backends doc" },
|
|
181
|
+
{ path: "plugins/cool-workflow/docs/node-snapshot-diff-replay.7.md", needle: next, desc: "node snapshot doc" },
|
|
182
|
+
{ path: "plugins/cool-workflow/docs/contract-migration-tooling.7.md", needle: next, desc: "contract migration doc" },
|
|
183
|
+
{ path: "plugins/cool-workflow/docs/control-plane-scheduling.7.md", needle: next, desc: "control-plane scheduling doc" },
|
|
184
|
+
{ path: "plugins/cool-workflow/docs/agent-delegation-drive.7.md", needle: next, desc: "agent delegation doc" },
|
|
185
|
+
{ path: "plugins/cool-workflow/docs/run-retention-reclamation.7.md", needle: next, desc: "run retention doc" },
|
|
186
|
+
{ path: "plugins/cool-workflow/docs/durable-state-and-locking.7.md", needle: next, desc: "durable state doc" },
|
|
187
|
+
{ path: "plugins/cool-workflow/docs/release-and-migration.7.md", needle: next, desc: "release & migration doc" },
|
|
188
|
+
];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function contentSurfaceFilesRoot(next) {
|
|
192
|
+
return [
|
|
193
|
+
{ path: "CHANGELOG.md", needle: `## ${next}`, desc: "CHANGELOG section header" },
|
|
194
|
+
{ path: "RELEASE.md", needle: next, desc: "RELEASE version reference" },
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function handleContentSurfaces(current, next) {
|
|
199
|
+
const allFiles = [
|
|
200
|
+
...contentSurfaceFiles(next),
|
|
201
|
+
...contentSurfaceFilesRoot(next)
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
const missing = [];
|
|
205
|
+
for (const { path: rel, needle } of allFiles) {
|
|
206
|
+
const abs = path.join(repoRoot, rel);
|
|
207
|
+
if (!fs.existsSync(abs)) { missing.push({ rel, reason: "file missing" }); continue; }
|
|
208
|
+
const text = fs.readFileSync(abs, "utf8");
|
|
209
|
+
if (!text.includes(needle)) missing.push({ rel, needle });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (missing.length === 0) {
|
|
213
|
+
process.stdout.write(`\nContent surfaces PASS — every file includes ${next}.\n`);
|
|
214
|
+
return { ok: true };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (CONTENT) {
|
|
218
|
+
// Auto-append version placeholders to all missing files.
|
|
219
|
+
process.stdout.write(`\nAuto-appending v${next} placeholders to ${missing.length} content surface(s):\n`);
|
|
220
|
+
for (const { rel, needle } of missing) {
|
|
221
|
+
const abs = path.join(repoRoot, rel);
|
|
222
|
+
if (!fs.existsSync(abs)) { process.stdout.write(` SKIP ${rel} (missing)\n`); continue; }
|
|
223
|
+
fs.appendFileSync(abs, `\n${needle}\n`);
|
|
224
|
+
process.stdout.write(` + ${rel}\n`);
|
|
225
|
+
}
|
|
226
|
+
// Re-run version-sync-check to confirm all content surfaces now pass.
|
|
227
|
+
const reSync = spawnSync("npm", ["run", "--silent", "version:sync"], {
|
|
228
|
+
cwd: pluginRoot, stdio: "pipe", encoding: "utf8"
|
|
229
|
+
});
|
|
230
|
+
if (reSync.status !== 0) {
|
|
231
|
+
return { ok: false, error: `version:sync still fails after auto-append:\n${reSync.stdout}\n${reSync.stderr}` };
|
|
232
|
+
}
|
|
233
|
+
process.stdout.write(`Content surfaces auto-filled. Edit prose in:\n`);
|
|
234
|
+
for (const { rel } of missing) process.stdout.write(` ${rel}\n`);
|
|
235
|
+
return { ok: true };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// GATE mode (default): fail with precise missing-file list.
|
|
239
|
+
const lines = [`\n${missing.length} content surface(s) missing version ${next}:`];
|
|
240
|
+
for (const { rel, needle } of missing) {
|
|
241
|
+
lines.push(` ${rel} — must contain "${needle}"`);
|
|
242
|
+
}
|
|
243
|
+
lines.push("");
|
|
244
|
+
lines.push("Fix with: npm run bump:version -- " + next + " --content (auto-fill placeholders)");
|
|
245
|
+
lines.push(" or edit each file manually to add the version reference.");
|
|
246
|
+
return { ok: false, error: lines.join("\n") };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
main();
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const assert = require("node:assert/strict");
|
|
5
|
+
const { execFileSync } = require("node:child_process");
|
|
6
|
+
const fs = require("node:fs");
|
|
7
|
+
const os = require("node:os");
|
|
8
|
+
const path = require("node:path");
|
|
9
|
+
|
|
10
|
+
const pluginRoot = path.resolve(__dirname, "..");
|
|
11
|
+
const cli = path.join(pluginRoot, "scripts/cw.js");
|
|
12
|
+
const node = process.execPath;
|
|
13
|
+
|
|
14
|
+
const canonicalApps = [
|
|
15
|
+
{
|
|
16
|
+
id: "architecture-review",
|
|
17
|
+
args: (workspace) => [
|
|
18
|
+
"--repo",
|
|
19
|
+
workspace,
|
|
20
|
+
"--question",
|
|
21
|
+
"Is the canonical app architecture stable and evidence-backed?",
|
|
22
|
+
"--invariant",
|
|
23
|
+
"No duplicate workflow ids",
|
|
24
|
+
"--focus",
|
|
25
|
+
"Workflow App framework"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "pr-review-fix-ci",
|
|
30
|
+
args: (workspace) => [
|
|
31
|
+
"--repo",
|
|
32
|
+
workspace,
|
|
33
|
+
"--branch",
|
|
34
|
+
"feature/canonical-apps",
|
|
35
|
+
"--base",
|
|
36
|
+
"main",
|
|
37
|
+
"--ci",
|
|
38
|
+
"local",
|
|
39
|
+
"--mode",
|
|
40
|
+
"review"
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "release-cut",
|
|
45
|
+
args: (workspace) => [
|
|
46
|
+
"--repo",
|
|
47
|
+
workspace,
|
|
48
|
+
"--version",
|
|
49
|
+
"0.1.30",
|
|
50
|
+
"--previousVersion",
|
|
51
|
+
"0.1.10",
|
|
52
|
+
"--releaseBranch",
|
|
53
|
+
"main",
|
|
54
|
+
"--dryRun",
|
|
55
|
+
"true"
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "research-synthesis",
|
|
60
|
+
args: (workspace) => [
|
|
61
|
+
"--cwd",
|
|
62
|
+
workspace,
|
|
63
|
+
"--question",
|
|
64
|
+
"What evidence supports Canonical Workflow Apps as official userland?",
|
|
65
|
+
"--source",
|
|
66
|
+
"plugins/cool-workflow/docs/workflow-app-framework.7.md",
|
|
67
|
+
"--scope",
|
|
68
|
+
"Cool Workflow v0.1.78",
|
|
69
|
+
"--freshness",
|
|
70
|
+
"as of release preparation"
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
function main() {
|
|
76
|
+
const appList = runJson(["app", "list"]);
|
|
77
|
+
const workflowList = runJson(["list"]);
|
|
78
|
+
assertUniqueIds(appList, "app list");
|
|
79
|
+
assertUniqueIds(workflowList, "workflow list");
|
|
80
|
+
|
|
81
|
+
const summaries = [];
|
|
82
|
+
for (const app of canonicalApps) {
|
|
83
|
+
const manifestPath = path.join(pluginRoot, "apps", app.id, "app.json");
|
|
84
|
+
const summary = appList.find((entry) => entry.id === app.id);
|
|
85
|
+
assert.ok(summary, `${app.id} must appear in app list`);
|
|
86
|
+
assert.equal(summary.sourceKind, "app-directory");
|
|
87
|
+
assert.equal(summary.legacy, false);
|
|
88
|
+
assert.equal(summary.version, "0.1.78");
|
|
89
|
+
|
|
90
|
+
const validation = runJson(["app", "validate", manifestPath]);
|
|
91
|
+
assert.equal(validation.valid, true, `${app.id} manifest must validate`);
|
|
92
|
+
|
|
93
|
+
const shown = runJson(["app", "show", app.id]);
|
|
94
|
+
assert.equal(shown.app.id, app.id);
|
|
95
|
+
assert.equal(shown.app.version, "0.1.78");
|
|
96
|
+
assert.ok(shown.app.metadata.canonical, `${app.id} must be marked canonical`);
|
|
97
|
+
assert.ok(shown.app.sandboxProfiles.length > 0, `${app.id} must declare sandbox profiles`);
|
|
98
|
+
assertTaskIdsUnique(shown);
|
|
99
|
+
assertUsesSandboxHints(shown);
|
|
100
|
+
assertHasEvidenceGate(shown);
|
|
101
|
+
|
|
102
|
+
const workspace = fs.mkdtempSync(path.join(os.tmpdir(), `cw-canonical-${app.id}-`));
|
|
103
|
+
const plan = runJson(["plan", app.id, ...app.args(workspace)]);
|
|
104
|
+
const state = JSON.parse(fs.readFileSync(plan.statePath, "utf8"));
|
|
105
|
+
assert.equal(state.workflow.app.id, app.id);
|
|
106
|
+
assert.equal(state.workflow.app.version, "0.1.78");
|
|
107
|
+
assert.equal(state.workflow.app.metadata.canonical, true);
|
|
108
|
+
assert.ok(state.tasks.some((task) => task.requiresEvidence), `${app.id} plan must include evidence gates`);
|
|
109
|
+
assert.ok(state.tasks.every((task) => task.sandboxProfileId), `${app.id} plan must include sandbox hints`);
|
|
110
|
+
|
|
111
|
+
summaries.push({
|
|
112
|
+
id: app.id,
|
|
113
|
+
version: shown.app.version,
|
|
114
|
+
runId: plan.runId,
|
|
115
|
+
taskCount: state.tasks.length,
|
|
116
|
+
statePath: plan.statePath,
|
|
117
|
+
reportPath: plan.reportPath
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
process.stdout.write(`${JSON.stringify({ ok: true, canonicalApps: summaries }, null, 2)}\n`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function runJson(args) {
|
|
125
|
+
return JSON.parse(execFileSync(node, [cli, ...args], { cwd: pluginRoot, encoding: "utf8" }));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function assertUniqueIds(entries, label) {
|
|
129
|
+
const seen = new Set();
|
|
130
|
+
for (const entry of entries) {
|
|
131
|
+
assert.ok(!seen.has(entry.id), `${label} contains duplicate id ${entry.id}`);
|
|
132
|
+
seen.add(entry.id);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function assertTaskIdsUnique(shown) {
|
|
137
|
+
const seen = new Set();
|
|
138
|
+
for (const phase of shown.workflow.phases) {
|
|
139
|
+
for (const task of phase.tasks) {
|
|
140
|
+
assert.ok(!seen.has(task.id), `${shown.app.id} contains duplicate task id ${task.id}`);
|
|
141
|
+
seen.add(task.id);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function assertUsesSandboxHints(shown) {
|
|
147
|
+
for (const phase of shown.workflow.phases) {
|
|
148
|
+
for (const task of phase.tasks) {
|
|
149
|
+
assert.ok(task.sandboxProfileId, `${shown.app.id} task ${task.id} needs a sandboxProfileId`);
|
|
150
|
+
assert.ok(
|
|
151
|
+
shown.app.sandboxProfiles.includes(task.sandboxProfileId),
|
|
152
|
+
`${shown.app.id} task ${task.id} uses undeclared sandbox profile ${task.sandboxProfileId}`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function assertHasEvidenceGate(shown) {
|
|
159
|
+
const gated = shown.workflow.phases.flatMap((phase) =>
|
|
160
|
+
phase.tasks
|
|
161
|
+
.filter((task) => task.requiresEvidence)
|
|
162
|
+
.map((task) => ({ phase: phase.name, task: task.id }))
|
|
163
|
+
);
|
|
164
|
+
assert.ok(gated.length > 0, `${shown.app.id} needs at least one evidence-required task`);
|
|
165
|
+
assert.ok(
|
|
166
|
+
gated.some((entry) => /verify|synth|verdict|summary/i.test(`${entry.phase}:${entry.task}`)),
|
|
167
|
+
`${shown.app.id} needs an evidence gate in verify/synthesis/verdict/summary work`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
main();
|
package/scripts/cw.js
ADDED