cool-workflow 0.1.87 → 0.1.89

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 (75) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +111 -71
  4. package/apps/architecture-review/app.json +1 -1
  5. package/apps/architecture-review-fast/app.json +1 -1
  6. package/apps/end-to-end-golden-path/app.json +1 -1
  7. package/apps/pr-review-fix-ci/app.json +1 -1
  8. package/apps/release-cut/app.json +1 -1
  9. package/apps/research-synthesis/app.json +1 -1
  10. package/dist/agent-config.js +42 -1
  11. package/dist/capability-core.js +16 -4
  12. package/dist/capability-registry.js +24 -0
  13. package/dist/cli/command-surface.js +105 -2
  14. package/dist/cli.js +2 -1
  15. package/dist/doctor.js +14 -1
  16. package/dist/drive.js +222 -16
  17. package/dist/execution-backend.js +4 -4
  18. package/dist/loop-expansion.js +60 -0
  19. package/dist/onramp.js +25 -0
  20. package/dist/orchestrator/lifecycle-operations.js +134 -3
  21. package/dist/orchestrator.js +48 -77
  22. package/dist/run-export.js +106 -2
  23. package/dist/state-node.js +13 -3
  24. package/dist/state.js +21 -0
  25. package/dist/telemetry-attestation.js +30 -6
  26. package/dist/telemetry-demo.js +29 -1
  27. package/dist/telemetry-ledger.js +6 -0
  28. package/dist/version.js +1 -1
  29. package/dist/worker-accept/telemetry-ledger.js +12 -2
  30. package/dist/workflow-api.js +33 -0
  31. package/dist/workflow-app-framework.js +20 -0
  32. package/docs/agent-delegation-drive.7.md +8 -0
  33. package/docs/capability-topology-registry.7.md +69 -46
  34. package/docs/cli-mcp-parity.7.md +16 -2
  35. package/docs/contract-migration-tooling.7.md +8 -0
  36. package/docs/control-plane-scheduling.7.md +8 -0
  37. package/docs/durable-state-and-locking.7.md +8 -0
  38. package/docs/evidence-adoption-reasoning-chain.7.md +8 -0
  39. package/docs/execution-backends.7.md +8 -0
  40. package/docs/launch/launch-kit.md +9 -9
  41. package/docs/multi-agent-cli-mcp-surface.7.md +8 -0
  42. package/docs/multi-agent-eval-replay-harness.7.md +8 -0
  43. package/docs/multi-agent-operator-ux.7.md +8 -0
  44. package/docs/node-snapshot-diff-replay.7.md +8 -0
  45. package/docs/observability-cost-accounting.7.md +8 -0
  46. package/docs/project-index.md +22 -5
  47. package/docs/readme-v0.1.87-full.md +301 -0
  48. package/docs/real-execution-backends.7.md +8 -0
  49. package/docs/release-and-migration.7.md +8 -0
  50. package/docs/release-tooling.7.md +8 -0
  51. package/docs/report-verifiable-bundle.7.md +34 -2
  52. package/docs/run-registry-control-plane.7.md +18 -0
  53. package/docs/run-retention-reclamation.7.md +8 -0
  54. package/docs/state-explosion-management.7.md +8 -0
  55. package/docs/team-collaboration.7.md +8 -0
  56. package/docs/trust-model.md +6 -4
  57. package/docs/web-desktop-workbench.7.md +8 -0
  58. package/manifest/plugin.manifest.json +1 -1
  59. package/manifest/source-context-profiles.json +1 -1
  60. package/package.json +8 -5
  61. package/scripts/agents/agent-adapter-core.js +1 -1
  62. package/scripts/agents/builtin-templates.json +2 -1
  63. package/scripts/agents/claude-p-agent.js +7 -33
  64. package/scripts/agents/codex-agent.js +1 -1
  65. package/scripts/agents/cw-attest-wrap.js +9 -1
  66. package/scripts/agents/gemini-agent.js +1 -1
  67. package/scripts/agents/opencode-agent.js +1 -1
  68. package/scripts/canonical-apps.js +4 -4
  69. package/scripts/coverage-gate.js +15 -1
  70. package/scripts/cw.js +0 -0
  71. package/scripts/dogfood-release.js +1 -1
  72. package/scripts/golden-path.js +4 -4
  73. package/scripts/release-flow.js +49 -2
  74. package/scripts/release-gate.sh +11 -2
  75. package/tsconfig.json +3 -1
@@ -79,7 +79,7 @@ child.stdout.on("data", (chunk) => {
79
79
  child.stderr.setEncoding("utf8");
80
80
  child.stderr.on("data", (chunk) => {
81
81
  childStderr += chunk;
82
- if (process.env.CW_AGENT_STREAM === "1" && process.env.CW_NO_STREAM !== "1" && process.stderr.isTTY) {
82
+ if (process.env.CW_AGENT_STREAM !== "0" && process.env.CW_NO_STREAM !== "1" && process.stderr.isTTY) {
83
83
  process.stderr.write(chunk);
84
84
  }
85
85
  });
@@ -83,7 +83,7 @@ const canonicalApps = [
83
83
  "--source",
84
84
  "plugins/cool-workflow/docs/workflow-app-framework.7.md",
85
85
  "--scope",
86
- "Cool Workflow v0.1.87",
86
+ "Cool Workflow v0.1.89",
87
87
  "--freshness",
88
88
  "as of release preparation"
89
89
  ]
@@ -117,14 +117,14 @@ function main() {
117
117
  assert.ok(summary, `${app.id} must appear in app list`);
118
118
  assert.equal(summary.sourceKind, "app-directory");
119
119
  assert.equal(summary.legacy, false);
120
- assert.equal(summary.version, "0.1.87");
120
+ assert.equal(summary.version, "0.1.89");
121
121
 
122
122
  const validation = runJson(["app", "validate", manifestPath]);
123
123
  assert.equal(validation.valid, true, `${app.id} manifest must validate`);
124
124
 
125
125
  const shown = runJson(["app", "show", app.id]);
126
126
  assert.equal(shown.app.id, app.id);
127
- assert.equal(shown.app.version, "0.1.87");
127
+ assert.equal(shown.app.version, "0.1.89");
128
128
  assert.ok(shown.app.metadata.canonical, `${app.id} must be marked canonical`);
129
129
  assert.ok(shown.app.sandboxProfiles.length > 0, `${app.id} must declare sandbox profiles`);
130
130
  assertTaskIdsUnique(shown);
@@ -135,7 +135,7 @@ function main() {
135
135
  const plan = runJson(["plan", app.id, ...app.args(workspace)]);
136
136
  const state = JSON.parse(fs.readFileSync(plan.statePath, "utf8"));
137
137
  assert.equal(state.workflow.app.id, app.id);
138
- assert.equal(state.workflow.app.version, "0.1.87");
138
+ assert.equal(state.workflow.app.version, "0.1.89");
139
139
  assert.equal(state.workflow.app.metadata.canonical, true);
140
140
  assert.ok(state.tasks.some((task) => task.requiresEvidence), `${app.id} plan must include evidence gates`);
141
141
  assert.ok(state.tasks.every((task) => task.sandboxProfileId), `${app.id} plan must include sandbox hints`);
@@ -25,7 +25,7 @@
25
25
  //
26
26
  // Usage: node scripts/coverage-gate.js [--min 80] [--concurrency <n|auto>]
27
27
 
28
- const { spawn } = require("node:child_process");
28
+ const { spawn, spawnSync } = require("node:child_process");
29
29
  const fs = require("node:fs");
30
30
  const os = require("node:os");
31
31
  const path = require("node:path");
@@ -50,11 +50,25 @@ if (!Number.isFinite(floor) || floor < 0 || floor > 100) {
50
50
 
51
51
  const covDir = fs.mkdtempSync(path.join(os.tmpdir(), "cw-coverage-"));
52
52
 
53
+ // Pre-check: ensure dist/ is built before entering the coverage merge phase
54
+ // (parity with the `test` and `test:ci` package.json scripts).
55
+ {
56
+ const cli = path.join(packageDir, "dist", "cli.js");
57
+ const check = spawnSync(process.execPath, [cli, "version"], { cwd: packageDir, stdio: "pipe", encoding: "utf8" });
58
+ const out = String(check.stdout || "").trim();
59
+ if (check.status !== 0 || !out) {
60
+ process.stderr.write(`${SELF}: dist/cli.js version failed (exit ${check.status}) — build may be stale. Run \`npm run build\` first.\n`);
61
+ process.exit(1);
62
+ }
63
+ }
64
+
53
65
  function runSuite() {
54
66
  return new Promise((resolve) => {
55
67
  const args = [path.join(packageDir, "test", "run-all.js")];
56
68
  const concurrency = flagValue("--concurrency");
57
69
  if (concurrency) args.push("--concurrency", concurrency);
70
+ const sample = flagValue("--sample");
71
+ if (sample) args.push("--sample", sample);
58
72
  const child = spawn(process.execPath, args, {
59
73
  cwd: packageDir,
60
74
  stdio: "inherit",
package/scripts/cw.js CHANGED
File without changes
@@ -6,7 +6,7 @@ const fs = require("node:fs");
6
6
  const path = require("node:path");
7
7
  const { CoolWorkflowRunner } = require("../dist/orchestrator.js");
8
8
 
9
- const TARGET_VERSION = "0.1.87";
9
+ const TARGET_VERSION = "0.1.89";
10
10
  const PREVIOUS_VERSION = "0.1.31";
11
11
  const pluginRoot = path.resolve(__dirname, "..");
12
12
  const repoRoot = path.resolve(pluginRoot, "..", "..");
@@ -33,7 +33,7 @@ function main() {
33
33
  const appValidation = runJson(["app", "validate", "end-to-end-golden-path"], pluginRoot);
34
34
  assert.equal(appValidation.valid, true);
35
35
  assert.equal(appValidation.summary.id, "end-to-end-golden-path");
36
- assert.equal(appValidation.summary.version, "0.1.87");
36
+ assert.equal(appValidation.summary.version, "0.1.89");
37
37
 
38
38
  const plan = runJson(
39
39
  [
@@ -42,7 +42,7 @@ function main() {
42
42
  "--repo",
43
43
  tmp,
44
44
  "--question",
45
- "Prove the deterministic v0.1.87 end-to-end golden path."
45
+ "Prove the deterministic v0.1.89 end-to-end golden path."
46
46
  ],
47
47
  pluginRoot
48
48
  );
@@ -52,7 +52,7 @@ function main() {
52
52
 
53
53
  let state = readJson(plan.statePath);
54
54
  assert.equal(state.workflow.app.id, "end-to-end-golden-path");
55
- assert.equal(state.workflow.app.version, "0.1.87");
55
+ assert.equal(state.workflow.app.version, "0.1.89");
56
56
  assert.equal(state.loopStage, "interpret");
57
57
 
58
58
  const dispatch = runJson(["dispatch", plan.runId, "--limit", "1", "--sandbox", "readonly"], tmp);
@@ -195,7 +195,7 @@ function main() {
195
195
  assert.equal(reportPath, plan.reportPath);
196
196
  assert.ok(fs.existsSync(reportPath));
197
197
  const report = fs.readFileSync(reportPath, "utf8");
198
- assert.match(report, /Workflow App: end-to-end-golden-path@0\.1\.87/);
198
+ assert.match(report, /Workflow App: end-to-end-golden-path@0\.1\.89/);
199
199
  assert.match(report, /## Candidates/);
200
200
  assert.match(report, /## Trust Audit/);
201
201
  assert.match(report, /## Acceptance Rationale/);
@@ -25,6 +25,12 @@
25
25
  // --soft for best-effort (skip-not-fail).
26
26
  // Flags also accepted: --prev-tag <t>, --agent-command "...", --agent-model m, --dry-run
27
27
  //
28
+ // VERDICT CONTRACT (auto-generated by --cut, never write by hand):
29
+ // Path: .cw-release/review-<FULLSHA>.verdict
30
+ // First line: "APPROVED <FULLSHA>"
31
+ // Both release-flow.js --cut and CI's release-gate.yml verify this exact path and
32
+ // format. The file MUST be committed to the tag's history.
33
+ //
28
34
  // Test seams (smoke/operator only, never the delegated agent):
29
35
  // CW_RELEASE_FLOW_GATE_CMD overrides the deterministic gate command
30
36
  // (default: `bash <thisdir>/release-gate.sh`) so the smoke can exercise the
@@ -153,6 +159,32 @@ function substitute(arg, map) {
153
159
  return arg.replace(/\{\{(\w+)\}\}/g, (m, k) => (k in map ? String(map[k]) : m));
154
160
  }
155
161
 
162
+ /** Extract verdict lines from agent stdout. Returns the verdict text (first line
163
+ * must be APPROVED <sha> or REJECTED) or null if no valid verdict found.
164
+ * Also logs failures to stderr so the operator can inspect. */
165
+ function extractVerdictFromStdout(stdout, resultPath) {
166
+ const lines = stdout.split(/\r?\n/);
167
+ const approvedLine = lines.find((line) => /^APPROVED\s+\S+/.test(line.trim()));
168
+ if (approvedLine) {
169
+ const idx = lines.indexOf(approvedLine);
170
+ // Include the APPROVED line and any capability line that follows.
171
+ const capLine = (lines[idx + 1] || "").trim();
172
+ const verdict = capLine && !capLine.startsWith("#") && !/^(APPROVED|REJECTED)\s/.test(capLine)
173
+ ? [approvedLine.trim(), capLine].join("\n")
174
+ : approvedLine.trim();
175
+ say(`reviewer verdict captured from stdout → ${resultPath}`);
176
+ return verdict;
177
+ }
178
+ const rejectedLine = lines.find((line) => /^REJECTED/i.test(line.trim()));
179
+ if (rejectedLine) {
180
+ process.stderr.write(`reviewer REJECTED via stdout — full output:\n${stdout.trim()}\n`);
181
+ return rejectedLine.trim();
182
+ }
183
+ // Partial: print the last 20 lines to help the operator diagnose.
184
+ process.stderr.write(`reviewer stdout had no APPROVED/REJECTED line. Last 20 lines:\n${lines.slice(-20).join("\n").trim()}\n`);
185
+ return null;
186
+ }
187
+
156
188
  function delegateReview(resultPath, inputPath) {
157
189
  // Reuse the canonical agent-config resolver (flags > env > file).
158
190
  let resolveAgentConfig;
@@ -188,16 +220,31 @@ function delegateReview(resultPath, inputPath) {
188
220
  say(`[2/3] reviewer — delegating to: ${cfg.command} ${(cfg.args || []).join(" ")} (model: ${cfg.model || "unreported"})`);
189
221
  // RED LINE: argv-style, shell:false. The agent runs the model in its own
190
222
  // process and inherits its own credentials; CW holds none.
223
+ // Capture stdout so agents that print verdicts (rather than writing the
224
+ // result file) are supported. Agents that DO write the file still work:
225
+ // the file takes precedence. stderr goes to the terminal for live output.
191
226
  const r = spawnSync(cfg.command, args, {
192
227
  cwd: repoRoot,
193
228
  env: { ...process.env },
194
229
  encoding: "utf8",
195
230
  timeout: cfg.timeoutMs || 600000,
196
231
  shell: false,
197
- stdio: "inherit"
232
+ stdio: ["ignore", "pipe", "inherit"],
233
+ maxBuffer: 32 * 1024 * 1024
198
234
  });
199
235
  if (r.status !== 0) die(`reviewer agent exited ${r.status === null ? "(timeout/no-exit)" : r.status} — no verdict trusted.`);
200
- return;
236
+ // If the agent already wrote the verdict file (backward compat), use it.
237
+ // Otherwise extract the verdict from stdout (headless agent path).
238
+ if (fs.existsSync(resultPath)) return;
239
+ const stdout = String(r.stdout || "").trim();
240
+ if (stdout) {
241
+ const verdictLines = extractVerdictFromStdout(stdout, resultPath);
242
+ if (verdictLines) {
243
+ fs.writeFileSync(resultPath, `${verdictLines}\n`);
244
+ return;
245
+ }
246
+ }
247
+ die("reviewer agent produced no verdict — stdout had no APPROVED <sha> or REJECTED line, and no verdict file was written. Ensure the agent writes the verdict to {{result}} or prints the verdict lines to stdout.");
201
248
  }
202
249
 
203
250
  // Endpoint mode (e.g. DeepSeek HTTP): POST the prompt, write the response as
@@ -51,7 +51,7 @@ if [[ -n "$PREV_TAG" ]]; then
51
51
  TESTS_CHANGED=$(git diff --name-only "$RANGE" | grep -cE '\.(test|spec)\.|/tests?/' || true)
52
52
  [[ "$TESTS_CHANGED" -gt 0 ]] || fail "zero test changes since $PREV_TAG"
53
53
 
54
- # --- 4. Cadence: >=4 cycles logged OR >=24h since previous tag -------
54
+ # --- 4. Cadence: >=4 cycles logged OR >=24h since previous tag, or a recorded HOTFIX ---
55
55
  say "[5/6] cadence"
56
56
  CYCLES=0
57
57
  if [[ -f ITERATION_LOG.md && -n "$PREV_TAG" ]]; then
@@ -60,8 +60,17 @@ if [[ -n "$PREV_TAG" ]]; then
60
60
  PREV_TS=$(git log -1 --format=%ct "$PREV_TAG")
61
61
  NOW_TS=$(date +%s)
62
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)"
63
68
  if [[ "$CYCLES" -lt 4 && "$HOURS" -lt 24 ]]; then
64
- fail "cadence: only $CYCLES cycles logged and ${HOURS}h since $PREV_TAG (need >=4 cycles or >=24h)"
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
65
74
  fi
66
75
  else
67
76
  say "[3-5/6] no previous tag; substance/evidence/cadence checks skipped"
package/tsconfig.json CHANGED
@@ -8,7 +8,9 @@
8
8
  "strict": true,
9
9
  "esModuleInterop": true,
10
10
  "forceConsistentCasingInFileNames": true,
11
- "skipLibCheck": true
11
+ "skipLibCheck": true,
12
+ "incremental": true,
13
+ "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo"
12
14
  },
13
15
  "include": [
14
16
  "src/**/*.ts"