cool-workflow 0.1.92 → 0.1.94

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 (81) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +104 -129
  4. package/apps/architecture-review/app.json +1 -1
  5. package/apps/architecture-review-fast/app.json +1 -1
  6. package/apps/architecture-review-fast/workflow.js +15 -2
  7. package/apps/end-to-end-golden-path/app.json +1 -1
  8. package/apps/pr-review-fix-ci/app.json +1 -1
  9. package/apps/release-cut/app.json +1 -1
  10. package/apps/research-synthesis/app.json +1 -1
  11. package/dist/capability-core.js +47 -0
  12. package/dist/capability-registry.js +2 -0
  13. package/dist/cli/command-surface.js +165 -1352
  14. package/dist/cli/format.js +56 -0
  15. package/dist/cli/handlers/audit.js +82 -0
  16. package/dist/cli/handlers/blackboard.js +81 -0
  17. package/dist/cli/handlers/candidate.js +40 -0
  18. package/dist/cli/handlers/clones.js +34 -0
  19. package/dist/cli/handlers/collaboration.js +61 -0
  20. package/dist/cli/handlers/eval.js +40 -0
  21. package/dist/cli/handlers/maintenance.js +107 -0
  22. package/dist/cli/handlers/multi-agent.js +165 -0
  23. package/dist/cli/handlers/node.js +41 -0
  24. package/dist/cli/handlers/operational.js +155 -0
  25. package/dist/cli/handlers/operator.js +146 -0
  26. package/dist/cli/handlers/registry.js +68 -0
  27. package/dist/cli/handlers/run.js +153 -0
  28. package/dist/cli/handlers/scheduling.js +126 -0
  29. package/dist/cli/handlers/workbench.js +41 -0
  30. package/dist/cli/handlers/worker.js +45 -0
  31. package/dist/cli/io.js +27 -0
  32. package/dist/cli/run-summary.js +45 -0
  33. package/dist/commit.js +0 -5
  34. package/dist/execution-backend.js +0 -11
  35. package/dist/mcp/tool-call.js +2 -0
  36. package/dist/mcp/tool-definitions.js +8 -0
  37. package/dist/orchestrator/app-operations.js +205 -0
  38. package/dist/orchestrator.js +41 -153
  39. package/dist/state-explosion.js +0 -7
  40. package/dist/term.js +0 -18
  41. package/dist/validation.js +0 -21
  42. package/dist/version.js +1 -1
  43. package/docs/agent-delegation-drive.7.md +13 -7
  44. package/docs/cli-mcp-parity.7.md +19 -2
  45. package/docs/contract-migration-tooling.7.md +3 -1
  46. package/docs/control-plane-scheduling.7.md +3 -1
  47. package/docs/durable-state-and-locking.7.md +3 -1
  48. package/docs/evidence-adoption-reasoning-chain.7.md +3 -1
  49. package/docs/execution-backends.7.md +3 -1
  50. package/docs/mcp-app-surface.7.md +12 -0
  51. package/docs/multi-agent-cli-mcp-surface.7.md +3 -1
  52. package/docs/multi-agent-eval-replay-harness.7.md +3 -1
  53. package/docs/multi-agent-operator-ux.7.md +3 -1
  54. package/docs/node-snapshot-diff-replay.7.md +3 -1
  55. package/docs/observability-cost-accounting.7.md +3 -1
  56. package/docs/project-index.md +16 -3
  57. package/docs/real-execution-backends.7.md +3 -1
  58. package/docs/release-and-migration.7.md +3 -1
  59. package/docs/release-tooling.7.md +3 -1
  60. package/docs/run-registry-control-plane.7.md +27 -3
  61. package/docs/run-retention-reclamation.7.md +3 -1
  62. package/docs/state-explosion-management.7.md +3 -1
  63. package/docs/team-collaboration.7.md +3 -1
  64. package/docs/web-desktop-workbench.7.md +3 -1
  65. package/manifest/plugin.manifest.json +1 -1
  66. package/package.json +1 -1
  67. package/scripts/agents/builtin-templates.json +3 -2
  68. package/scripts/agents/claude-p-agent.js +6 -6
  69. package/scripts/agents/codex-agent.js +17 -3
  70. package/scripts/agents/deepseek-agent.js +23 -0
  71. package/scripts/agents/gemini-opencode-agent.js +25 -0
  72. package/scripts/agents/opencode-agent.js +57 -8
  73. package/scripts/architecture-review-fast.js +19 -5
  74. package/scripts/bump-version.js +16 -0
  75. package/scripts/canonical-apps.js +4 -4
  76. package/scripts/dogfood-release.js +1 -1
  77. package/scripts/golden-path.js +4 -4
  78. package/scripts/parity-check.js +9 -1
  79. package/scripts/release-flow.js +35 -0
  80. package/scripts/vendor-preflight.js +127 -0
  81. package/scripts/version-sync-check.js +5 -0
@@ -146,4 +146,6 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
146
146
 
147
147
  0.1.91
148
148
 
149
- 0.1.92
149
+ 0.1.93
150
+
151
+ 0.1.94
@@ -145,4 +145,6 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
145
145
 
146
146
  0.1.91
147
147
 
148
- 0.1.92
148
+ 0.1.93
149
+
150
+ 0.1.94
@@ -306,4 +306,6 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
306
306
 
307
307
  0.1.91
308
308
 
309
- 0.1.92
309
+ 0.1.93
310
+
311
+ 0.1.94
@@ -336,4 +336,6 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
336
336
 
337
337
  0.1.91
338
338
 
339
- 0.1.92
339
+ 0.1.93
340
+
341
+ 0.1.94
@@ -232,4 +232,16 @@ The CLI is still the easiest way for people to drive a run. MCP is the steady
232
232
  tool surface for agent hosts. New runtime powers should come up in both
233
233
  surfaces, keep old names as aliases or wrappers, and use clear JSON
234
234
  contracts in place of host-specific policy hidden in the bridge.
235
+
236
+ ## Implementation
237
+
238
+ The app-management surface (`listWorkflows`, `listApps`, `showApp`,
239
+ `validateApp`, `initApp`, `packageApp`) lives on the `CoolWorkflowRunner`
240
+ facade as thin delegators with no logic of their own. Their bodies sit in
241
+ `src/orchestrator/app-operations.ts` as pure functions — the same v0.1.40
242
+ router pattern used by the other `src/orchestrator/*-operations.ts` modules.
243
+ The runner-owned calls (`resolveFromBase`, `validateApp`) are passed in as
244
+ callbacks so the moved bodies stay byte-for-byte the same. The public method
245
+ names, signatures, and return types do not change, so the CLI/MCP parity gate
246
+ stays green.
235
247
  0.1.51
@@ -304,4 +304,6 @@ The host-facing surface tracks the CLI golden-path fixes (`cw -q` routing + repo
304
304
 
305
305
  0.1.91
306
306
 
307
- 0.1.92
307
+ 0.1.93
308
+
309
+ 0.1.94
@@ -338,4 +338,6 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
338
338
 
339
339
  0.1.91
340
340
 
341
- 0.1.92
341
+ 0.1.93
342
+
343
+ 0.1.94
@@ -350,4 +350,6 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
350
350
 
351
351
  0.1.91
352
352
 
353
- 0.1.92
353
+ 0.1.93
354
+
355
+ 0.1.94
@@ -171,4 +171,6 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
171
171
 
172
172
  0.1.91
173
173
 
174
- 0.1.92
174
+ 0.1.93
175
+
176
+ 0.1.94
@@ -230,4 +230,6 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
230
230
 
231
231
  0.1.91
232
232
 
233
- 0.1.92
233
+ 0.1.93
234
+
235
+ 0.1.94
@@ -1,15 +1,15 @@
1
1
  # Cool Workflow Project Index
2
2
 
3
- Generated from the current repository code on 2026-06-21 by `npm run sync:project-index`.
3
+ Generated from the current repository code on 2026-06-26 by `npm run sync:project-index`.
4
4
 
5
5
  ## Snapshot
6
6
 
7
7
  - Package: `cool-workflow`
8
- - Version: `0.1.92`
8
+ - Version: `0.1.94`
9
9
  - Source modules: `68`
10
10
  - Workflow apps: `7`
11
11
  - Docs: `53`
12
- - Smoke tests: `139`
12
+ - Smoke tests: `152`
13
13
  - Repository: https://github.com/coo1white/cool-workflow
14
14
 
15
15
  ## Architecture
@@ -196,6 +196,7 @@ Smoke tests mirror the public contracts. The high-signal suites are:
196
196
  - [agent-delegation-drive-smoke.js](../test/agent-delegation-drive-smoke.js)
197
197
  - [append-run-node-no-realloc-smoke.js](../test/append-run-node-no-realloc-smoke.js)
198
198
  - [architecture-review-fast-automation-smoke.js](../test/architecture-review-fast-automation-smoke.js)
199
+ - [architecture-review-fast-phase-cache-smoke.js](../test/architecture-review-fast-phase-cache-smoke.js)
199
200
  - [architecture-review-fast-smoke.js](../test/architecture-review-fast-smoke.js)
200
201
  - [artifact-integrity-smoke.js](../test/artifact-integrity-smoke.js)
201
202
  - [audit-verify-smoke.js](../test/audit-verify-smoke.js)
@@ -209,6 +210,12 @@ Smoke tests mirror the public contracts. The high-signal suites are:
209
210
  - [claude-p-agent-wrapper-smoke.js](../test/claude-p-agent-wrapper-smoke.js)
210
211
  - [cli-arg-parsing-smoke.js](../test/cli-arg-parsing-smoke.js)
211
212
  - [cli-command-surface-smoke.js](../test/cli-command-surface-smoke.js)
213
+ - [cli-format-smoke.js](../test/cli-format-smoke.js)
214
+ - [cli-handler-clones-smoke.js](../test/cli-handler-clones-smoke.js)
215
+ - [cli-handler-eval-node-smoke.js](../test/cli-handler-eval-node-smoke.js)
216
+ - [cli-handler-maintenance-smoke.js](../test/cli-handler-maintenance-smoke.js)
217
+ - [cli-handler-workbench-smoke.js](../test/cli-handler-workbench-smoke.js)
218
+ - [cli-io-smoke.js](../test/cli-io-smoke.js)
212
219
  - [cli-jsonmode-parity-smoke.js](../test/cli-jsonmode-parity-smoke.js)
213
220
  - [cli-mcp-parity-smoke.js](../test/cli-mcp-parity-smoke.js)
214
221
  - [cli-progress-summary-smoke.js](../test/cli-progress-summary-smoke.js)
@@ -222,6 +229,9 @@ Smoke tests mirror the public contracts. The high-signal suites are:
222
229
  - [contract-migration-tooling-smoke.js](../test/contract-migration-tooling-smoke.js)
223
230
  - [control-plane-scheduling-smoke.js](../test/control-plane-scheduling-smoke.js)
224
231
  - [coordinator-blackboard-smoke.js](../test/coordinator-blackboard-smoke.js)
232
+ - [cw-help-per-command-smoke.js](../test/cw-help-per-command-smoke.js)
233
+ - [dead-export-removal-guard-smoke.js](../test/dead-export-removal-guard-smoke.js)
234
+ - [deepseek-agent-wrapper-smoke.js](../test/deepseek-agent-wrapper-smoke.js)
225
235
  - [demo-bundle-smoke.js](../test/demo-bundle-smoke.js)
226
236
  - [det-ids-b-smoke.js](../test/det-ids-b-smoke.js)
227
237
  - [doctor-smoke.js](../test/doctor-smoke.js)
@@ -239,6 +249,7 @@ Smoke tests mirror the public contracts. The high-signal suites are:
239
249
  - [execution-backends-smoke.js](../test/execution-backends-smoke.js)
240
250
  - [freebsd-audit-fixes-smoke.js](../test/freebsd-audit-fixes-smoke.js)
241
251
  - [gemini-agent-wrapper-smoke.js](../test/gemini-agent-wrapper-smoke.js)
252
+ - [gemini-opencode-agent-wrapper-smoke.js](../test/gemini-opencode-agent-wrapper-smoke.js)
242
253
  - [h7-custom-profile-persist-smoke.js](../test/h7-custom-profile-persist-smoke.js)
243
254
  - [headline-commands-smoke.js](../test/headline-commands-smoke.js)
244
255
  - [incremental-resume-smoke.js](../test/incremental-resume-smoke.js)
@@ -301,6 +312,7 @@ Smoke tests mirror the public contracts. The high-signal suites are:
301
312
  - [run-import-tamper-failclosed-smoke.js](../test/run-import-tamper-failclosed-smoke.js)
302
313
  - [run-inspect-archive-smoke.js](../test/run-inspect-archive-smoke.js)
303
314
  - [run-registry-control-plane-smoke.js](../test/run-registry-control-plane-smoke.js)
315
+ - [run-restore-failclosed-smoke.js](../test/run-restore-failclosed-smoke.js)
304
316
  - [run-resume-drive-smoke.js](../test/run-resume-drive-smoke.js)
305
317
  - [run-retention-reclamation-smoke.js](../test/run-retention-reclamation-smoke.js)
306
318
  - [sample-determinism-smoke.js](../test/sample-determinism-smoke.js)
@@ -325,6 +337,7 @@ Smoke tests mirror the public contracts. The high-signal suites are:
325
337
  - [telemetry-verify-signatures-smoke.js](../test/telemetry-verify-signatures-smoke.js)
326
338
  - [token-budget-enforcement-smoke.js](../test/token-budget-enforcement-smoke.js)
327
339
  - [vendor-manifest-load-smoke.js](../test/vendor-manifest-load-smoke.js)
340
+ - [vendor-preflight-smoke.js](../test/vendor-preflight-smoke.js)
328
341
  - [verifier-gated-commit-smoke.js](../test/verifier-gated-commit-smoke.js)
329
342
  - [verify-import-audit-chain-smoke.js](../test/verify-import-audit-chain-smoke.js)
330
343
  - [web-desktop-workbench-smoke.js](../test/web-desktop-workbench-smoke.js)
@@ -178,4 +178,6 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
178
178
 
179
179
  0.1.91
180
180
 
181
- 0.1.92
181
+ 0.1.93
182
+
183
+ 0.1.94
@@ -318,4 +318,6 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
318
318
 
319
319
  0.1.91
320
320
 
321
- 0.1.92
321
+ 0.1.93
322
+
323
+ 0.1.94
@@ -265,4 +265,6 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
265
265
 
266
266
  0.1.91
267
267
 
268
- 0.1.92
268
+ 0.1.93
269
+
270
+ 0.1.94
@@ -216,9 +216,30 @@ before importing a bad archive. It is a true preview of import: under
216
216
  turn away) also inspects as `ok:false`; with the env unset (default) an absent integrity
217
217
  block is only reported, not failed.
218
218
 
219
+ **Restore in one fail-closed step.** `run restore PATH --target DIR [--json]`
220
+ does the whole move-a-run-to-another-machine flow as ONE atomic, fail-closed
221
+ step: it integrity-**inspects** the bundle first (writing nothing), **imports**
222
+ it, then reuses the verification `import` already ran — and reports `ok:true`
223
+ ONLY when that verify passes. This closes a real gap: `run import` runs a
224
+ verification (it re-proves restored file digests, the **telemetry ledger**, and
225
+ the **trust-audit hash chain**) and reports it, but does NOT fail on it — it
226
+ exits `0` even when that chain does not verify. So a run whose telemetry or
227
+ trust-audit chain was tampered (yet whose file digests are intact) imports with a
228
+ made-up success. `run restore` refuses exactly that: it fails closed on the same
229
+ verification `import` only reports. A bundle that fails the up-front integrity
230
+ inspect is refused **before any import**, so nothing is written and the run is
231
+ never left part-restored; the result carries `imported:null` and `verify:null`.
232
+ A bundle that imports but fails post-import verification is reported with
233
+ `ok:false` too. It exits `1` whenever `ok:false`, so `cw run restore <path>` is a
234
+ single command that either lands a fully-proven run or refuses with a non-zero
235
+ exit — never a made-up success. The result is structured
236
+ (`{ schemaVersion, ok, target, inspect, imported, verify, registry }`) so it is
237
+ scriptable. `run import` and `run inspect-archive` are unchanged; restore is a
238
+ thin composition of `inspectArchive` + `importRun` (reusing its verification).
239
+
219
240
  MCP gives the same mechanisms as `cw_run_export`, `cw_run_import`,
220
- `cw_run_verify_import`, and `cw_run_inspect_archive`; the CLI and MCP paths share
221
- the same runtime functions.
241
+ `cw_run_verify_import`, `cw_run_inspect_archive`, and `cw_run_restore`; the CLI
242
+ and MCP paths share the same runtime functions.
222
243
 
223
244
  ## Cross-repo history
224
245
 
@@ -243,6 +264,7 @@ node scripts/cw.js run export <run-id> --output PATH
243
264
  node scripts/cw.js run import PATH --target DIR
244
265
  node scripts/cw.js run verify-import <run-id> [--cwd DIR]
245
266
  node scripts/cw.js run inspect-archive PATH [--json]
267
+ node scripts/cw.js run restore PATH --target DIR [--json]
246
268
  node scripts/cw.js queue add [--app ID|--workflow ID|--runId ID] [--repo PATH] [--priority N] [--note TEXT]
247
269
  node scripts/cw.js queue list [--status STATE] [--repo PATH] [--json]
248
270
  node scripts/cw.js queue show <queue-id>
@@ -439,4 +461,6 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
439
461
 
440
462
  0.1.91
441
463
 
442
- 0.1.92
464
+ 0.1.93
465
+
466
+ 0.1.94
@@ -229,4 +229,6 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
229
229
 
230
230
  0.1.91
231
231
 
232
- 0.1.92
232
+ 0.1.93
233
+
234
+ 0.1.94
@@ -307,4 +307,6 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
307
307
 
308
308
  0.1.91
309
309
 
310
- 0.1.92
310
+ 0.1.93
311
+
312
+ 0.1.94
@@ -243,4 +243,6 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
243
243
 
244
244
  0.1.91
245
245
 
246
- 0.1.92
246
+ 0.1.93
247
+
248
+ 0.1.94
@@ -251,4 +251,6 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
251
251
 
252
252
  0.1.91
253
253
 
254
- 0.1.92
254
+ 0.1.93
255
+
256
+ 0.1.94
@@ -2,7 +2,7 @@
2
2
  "_comment": "SINGLE SOURCE OF TRUTH for every vendor manifest. Edit THIS file, then run `npm run gen:manifests`. Do NOT hand-edit the generated vendor manifests (.claude-plugin/, .codex-plugin/, .agents/, .mcp.json) — `npm run gen:manifests -- --check` (run by release:check) will fail if they drift from this source.",
3
3
  "identity": {
4
4
  "name": "cool-workflow",
5
- "version": "0.1.92",
5
+ "version": "0.1.94",
6
6
  "license": "BSD-2-Clause",
7
7
  "homepage": "https://github.com/coo1white/cool-workflow",
8
8
  "author": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cool-workflow",
3
- "version": "0.1.92",
3
+ "version": "0.1.94",
4
4
  "bin": {
5
5
  "cool-workflow": "scripts/cw.js",
6
6
  "cw": "scripts/cw.js"
@@ -4,8 +4,9 @@
4
4
  "templates": {
5
5
  "claude": "claude-p-agent.js",
6
6
  "codex": "codex-agent.js",
7
- "gemini": "gemini-agent.js",
7
+ "gemini": "gemini-opencode-agent.js",
8
+ "gemini-cli": "gemini-agent.js",
8
9
  "opencode": "opencode-agent.js",
9
- "deepseek": "opencode-agent.js"
10
+ "deepseek": "deepseek-agent.js"
10
11
  }
11
12
  }
@@ -12,12 +12,12 @@
12
12
  // worker's result.md ({{result}}), and forward claude's JSON on STDOUT so CW
13
13
  // records the agent-reported provenance.
14
14
  //
15
- // LIVE OUTPUT (Unix discipline): default output is the legacy `--output-format
16
- // json` contract, forwarded verbatim on stdout. Set CW_AGENT_STREAM=1 to opt in
17
- // to claude `stream-json`; only then does this wrapper render a human-readable
18
- // trace to stderr, and only when stderr is a TTY. Diagnostics stay off stdout,
19
- // and vendor-specific stream parsing lives HERE in the wrapper (policy), not in
20
- // CW's core (which only forwards, never parses).
15
+ // LIVE OUTPUT (Unix discipline): the DEFAULT is claude `stream-json` — this
16
+ // wrapper renders a human-readable trace to stderr (only when stderr is a TTY).
17
+ // Set CW_AGENT_STREAM=0 (or CW_NO_STREAM=1) to fall back to the legacy
18
+ // `--output-format json` contract, forwarded verbatim on stdout. Either way
19
+ // diagnostics stay off stdout, and vendor-specific stream parsing lives HERE in
20
+ // the wrapper (policy), not in CW's core (which only forwards, never parses).
21
21
  //
22
22
  // READ-ONLY by design: claude gets NO Write tool; the architecture-review app
23
23
  // declares the `readonly` sandbox profile. This wrapper (the transport) writes
@@ -16,6 +16,7 @@
16
16
  // stderr: optional live trace when CW_AGENT_STREAM=1 and attached to a TTY.
17
17
 
18
18
  const fs = require("node:fs");
19
+ const os = require("node:os");
19
20
  const path = require("node:path");
20
21
  const { spawn } = require("node:child_process");
21
22
  const {
@@ -27,6 +28,21 @@ const {
27
28
  writeResult
28
29
  } = require("./agent-adapter-core");
29
30
 
31
+ // codex exec --json (>=0.139) emits NO model field in its JSONL (only thread/turn
32
+ // events + usage). For provenance, fall back to the model codex is configured to
33
+ // use: $CODEX_HOME/config.toml (or ~/.codex/config.toml), key `model`. Best effort
34
+ // — a stream model field (older/newer codex) still wins.
35
+ function detectCodexModel() {
36
+ try {
37
+ const home = process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
38
+ const cfg = fs.readFileSync(path.join(home, "config.toml"), "utf8");
39
+ const m = cfg.match(/^\s*model\s*=\s*["']([^"']+)["']/m);
40
+ return m ? m[1] : undefined;
41
+ } catch {
42
+ return undefined;
43
+ }
44
+ }
45
+
30
46
  const inputPath = process.argv[2];
31
47
  const resultPath = process.argv[3];
32
48
  if (!inputPath || !resultPath) {
@@ -65,8 +81,6 @@ const args = [
65
81
  finalPath,
66
82
  "--sandbox",
67
83
  "read-only",
68
- "--ask-for-approval",
69
- "never",
70
84
  "--color",
71
85
  "never",
72
86
  "-"
@@ -133,5 +147,5 @@ child.on("close", (code) => {
133
147
  process.exit(1);
134
148
  }
135
149
 
136
- emitReport(state.model, state.usage, resultText);
150
+ emitReport(state.model || detectCodexModel(), state.usage, resultText);
137
151
  });
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // deepseek-agent.js - DeepSeek (via opencode) adapter for CW Agent Delegation Drive.
5
+ //
6
+ // DeepSeek ships no standalone CLI here; CW reaches it through
7
+ // `opencode run --model deepseek/...`. This is a thin variant of the opencode
8
+ // runner: it sets the variant env (display label + model) and delegates to
9
+ // opencode-agent.js, so the run/parse/provenance logic lives in ONE place.
10
+ //
11
+ // Override the model with CW_DEEPSEEK_MODEL (default deepseek/deepseek-chat).
12
+ // Requires opencode installed AND a DeepSeek provider configured in opencode
13
+ // (`opencode auth login` or DEEPSEEK_API_KEY). Without that, opencode exits
14
+ // nonzero and CW fails closed - it never fabricates a result.
15
+ //
16
+ // Contract (unchanged from opencode-agent.js):
17
+ // argv[2] = {{input}} worker input.md
18
+ // argv[3] = {{result}} worker result.md to persist
19
+
20
+ process.env.CW_OPENCODE_LABEL = "deepseek";
21
+ process.env.CW_OPENCODE_MODEL = process.env.CW_DEEPSEEK_MODEL || "deepseek/deepseek-chat";
22
+
23
+ require("./opencode-agent.js");
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // gemini-opencode-agent.js - Gemini (via opencode) adapter for CW Agent Delegation.
5
+ //
6
+ // Reaches Gemini through `opencode run --model google/...`, the same gateway CW
7
+ // uses for DeepSeek. Use this when the Gemini key lives in opencode (provider
8
+ // "google") rather than the standalone `gemini` CLI. The native Gemini CLI path
9
+ // is still available as builtin:gemini-cli (scripts/agents/gemini-agent.js).
10
+ //
11
+ // Thin variant of the opencode runner: it sets the variant env (display label +
12
+ // model) and delegates to opencode-agent.js, so run/parse/provenance live in ONE
13
+ // place. Override the model with CW_GEMINI_MODEL (default google/gemini-3.5-flash).
14
+ // Requires opencode installed AND a "google" provider authed in opencode
15
+ // (`opencode auth login` -> google). Without it, opencode exits nonzero and CW
16
+ // fails closed - it never fabricates a result.
17
+ //
18
+ // Contract (unchanged from opencode-agent.js):
19
+ // argv[2] = {{input}} worker input.md
20
+ // argv[3] = {{result}} worker result.md to persist
21
+
22
+ process.env.CW_OPENCODE_LABEL = "gemini";
23
+ process.env.CW_OPENCODE_MODEL = process.env.CW_GEMINI_MODEL || "google/gemini-3.5-flash";
24
+
25
+ require("./opencode-agent.js");
@@ -37,11 +37,35 @@ if (!inputPath || !resultPath) {
37
37
  process.exit(2);
38
38
  }
39
39
 
40
+ // Same runner serves both `opencode` (provider default model) and `deepseek`
41
+ // (DeepSeek via opencode). The variant sets CW_OPENCODE_LABEL + CW_OPENCODE_MODEL;
42
+ // see deepseek-agent.js. Unset = plain opencode, unchanged behavior.
43
+ const label = process.env.CW_OPENCODE_LABEL || "opencode";
44
+ const requestedModel = process.env.CW_OPENCODE_MODEL || "";
45
+
40
46
  const prompt = buildPrompt(inputPath);
41
- const render = createRenderer({ env: process.env, stderr: process.stderr, label: "opencode" });
47
+ const render = createRenderer({ env: process.env, stderr: process.stderr, label });
42
48
  const transcriptPath = path.join(path.dirname(resultPath), "transcript.md");
43
- const state = { provider: "opencode", buffer: "", model: undefined, usage: undefined, textFragments: [], finalResult: undefined, renderer: render };
49
+ const state = {
50
+ provider: label,
51
+ buffer: "",
52
+ // opencode --format json carries NO model field; record the model we asked for.
53
+ model: requestedModel || undefined,
54
+ usage: undefined,
55
+ textFragments: [],
56
+ finalResult: undefined,
57
+ lastMessageId: undefined,
58
+ lastMessageText: "",
59
+ renderer: render
60
+ };
44
61
  let childStderr = "";
62
+
63
+ // opencode (>=1.x) --format json emits JSONL events of shape { type, part }:
64
+ // type:"text" -> part.text assistant text, grouped by part.messageID
65
+ // type:"step_finish" -> part.tokens { input, output, ... } per step
66
+ // The final answer is the LAST message's text (earlier "text" parts are mid-run
67
+ // narration). Older opencode shapes (ev.result / ev.text / ev.delta) are still
68
+ // accepted as a fallback so the wrapper is not pinned to one CLI version.
45
69
  function recordJsonLine(line) {
46
70
  let ev;
47
71
  try {
@@ -50,22 +74,47 @@ function recordJsonLine(line) {
50
74
  state.invalidJson = true;
51
75
  return;
52
76
  }
77
+ const part = ev && typeof ev.part === "object" && ev.part ? ev.part : {};
78
+
79
+ if (ev.type === "text" && typeof part.text === "string") {
80
+ const mid = typeof part.messageID === "string" ? part.messageID : "";
81
+ if (mid !== state.lastMessageId) {
82
+ state.lastMessageId = mid;
83
+ state.lastMessageText = "";
84
+ }
85
+ state.lastMessageText += (state.lastMessageText ? "\n" : "") + part.text;
86
+ if (part.text.trim()) state.textFragments.push(part.text);
87
+ return;
88
+ }
89
+
90
+ if (ev.type === "step_finish" && part.tokens && typeof part.tokens === "object") {
91
+ state.usage = state.usage || { input_tokens: 0, output_tokens: 0 };
92
+ state.usage.input_tokens += Number(part.tokens.input) || 0;
93
+ state.usage.output_tokens += Number(part.tokens.output) || 0;
94
+ return;
95
+ }
96
+
97
+ // Fallback: older opencode JSON shapes (single result object / delta stream).
53
98
  if (ev.result && typeof ev.result === "string") {
54
99
  state.finalResult = ev.result;
55
- } else {
56
- const text = typeof ev.text === "string" ? ev.text : (ev.delta ? (typeof ev.delta === "string" ? ev.delta : ev.delta.text) : undefined);
57
- if (typeof text === "string" && text.trim()) state.textFragments.push(text);
100
+ return;
58
101
  }
102
+ const legacy = typeof ev.text === "string" ? ev.text : (ev.delta ? (typeof ev.delta === "string" ? ev.delta : ev.delta.text) : undefined);
103
+ if (typeof legacy === "string" && legacy.trim()) state.textFragments.push(legacy);
59
104
  }
60
105
 
61
- render.action("opencode: reading the repo…");
106
+ render.action(`${label}: reading the repo…`);
62
107
 
108
+ // `opencode run` takes the message as a POSITIONAL arg ("opencode run [message..]");
109
+ // there is no --prompt flag. Pass the prompt last, as the positional message.
110
+ // --model (provider/model, e.g. deepseek/deepseek-chat) is added only when a
111
+ // variant requests it; otherwise opencode uses its configured default model.
63
112
  const args = [
64
113
  "run",
65
114
  "--format",
66
115
  "json",
67
116
  "--dangerously-skip-permissions",
68
- "--prompt",
117
+ ...(requestedModel ? ["--model", requestedModel] : []),
69
118
  prompt
70
119
  ];
71
120
 
@@ -105,7 +154,7 @@ child.on("close", (code) => {
105
154
  process.exit(1);
106
155
  }
107
156
 
108
- const resultText = state.finalResult || state.textFragments.join("\n\n");
157
+ const resultText = state.finalResult || state.lastMessageText || state.textFragments.join("\n\n");
109
158
  if (!resultText.trim()) {
110
159
  process.stderr.write("opencode produced no result text - refusing to fabricate a result\n");
111
160
  process.exit(1);
@@ -38,13 +38,20 @@ function main() {
38
38
  const fastModel = stringArg(args.fastModel || args["fast-model"]);
39
39
  const strongModel = stringArg(args.strongModel || args["strong-model"]);
40
40
  const modelEnv = modelPolicyEnv(fastModel, strongModel);
41
+ // Opt-in incremental overlay: scope the exported source context to files
42
+ // changed since REF (e.g. origin/main). The source-context export resolves
43
+ // the ref, keys its cache separately, and records the base. The fast review
44
+ // then runs over the narrower context; the scheduled full review stays a
45
+ // complete audit (see scheduleFullReview).
46
+ const changedFrom = stringArg(args.changedFrom || args["changed-from"]);
41
47
 
42
48
  const contextExport = timed(() => exportSourceContext({
43
49
  repo,
44
50
  profile,
45
51
  ref,
46
52
  profileFile,
47
- cacheDir
53
+ cacheDir,
54
+ changedFrom
48
55
  }));
49
56
  const contextText = contextExport.value;
50
57
  assertNonEmptySourceContext(contextText, profile, repo);
@@ -87,7 +94,8 @@ function main() {
87
94
  digest,
88
95
  profile,
89
96
  ref,
90
- cacheDir
97
+ cacheDir,
98
+ ...(changedFrom ? { changedFrom } : {})
91
99
  };
92
100
  const fullReviewScheduleRun = truthy(args.scheduleFull || args["schedule-full"])
93
101
  ? timed(() => scheduleFullReview(repo, question, args, fastReview, sourceContextMeta))
@@ -101,7 +109,7 @@ function main() {
101
109
  fastReview,
102
110
  ...(fastModel || strongModel ? { modelPolicy: { ...(fastModel ? { fastModel } : {}), ...(strongModel ? { strongModel } : {}) } } : {}),
103
111
  ...(fullReviewSchedule ? { fullReviewSchedule } : {}),
104
- ...(includeMetrics ? { metrics: buildMetrics(started, contextText, contextExport.elapsedMs, fastReview, fastReviewRun.elapsedMs, fullReviewScheduleRun?.elapsedMs) } : {})
112
+ ...(includeMetrics ? { metrics: buildMetrics(started, contextText, contextExport.elapsedMs, fastReview, fastReviewRun.elapsedMs, fullReviewScheduleRun?.elapsedMs, changedFrom) } : {})
105
113
  });
106
114
  }
107
115
 
@@ -119,6 +127,7 @@ function exportSourceContext(options) {
119
127
  options.cacheDir
120
128
  ];
121
129
  if (options.profileFile) argv.push("--profile-file", path.resolve(options.profileFile));
130
+ if (options.changedFrom) argv.push("--changed-from", options.changedFrom);
122
131
  const result = spawnSync(node, argv, {
123
132
  cwd: repoRoot,
124
133
  encoding: "utf8",
@@ -194,6 +203,9 @@ function scheduleFullReview(repo, question, args, fastReview, sourceContextMeta)
194
203
  fastReview?.reportPath ? `Fast review report: ${fastReview.reportPath}.` : "",
195
204
  `Fast review status: ${fastReview?.status || "unknown"} (${fastReview?.completedWorkers || 0}/${fastReview?.plannedWorkers || 0} workers completed).`,
196
205
  `Source context: ${sourceContextMeta.path} (${sourceContextMeta.digest}, profile ${sourceContextMeta.profile}, ref ${sourceContextMeta.ref}).`,
206
+ sourceContextMeta.changedFrom
207
+ ? `Fast review used an incremental overlay (changed-from ${sourceContextMeta.changedFrom}); this full review must audit the complete source, not only the changed files.`
208
+ : "",
197
209
  "Use the completed architecture-review-fast report as the foreground triage result; write the full review report path and digest when the background review finishes."
198
210
  ].filter(Boolean).join(" ");
199
211
  return runCwJson([
@@ -304,7 +316,7 @@ function timed(fn) {
304
316
  return { value, elapsedMs: elapsedMs(started) };
305
317
  }
306
318
 
307
- function buildMetrics(started, contextText, sourceContextElapsedMs, fastReview, fastReviewElapsedMs, fullReviewScheduleElapsedMs) {
319
+ function buildMetrics(started, contextText, sourceContextElapsedMs, fastReview, fastReviewElapsedMs, fullReviewScheduleElapsedMs, changedFrom) {
308
320
  const steps = Array.isArray(fastReview?.steps) ? fastReview.steps : [];
309
321
  const handleKinds = countBy(steps.map((step) => step && step.handleKind).filter(Boolean));
310
322
  const actions = countBy(steps.map((step) => step && step.action).filter(Boolean));
@@ -314,7 +326,8 @@ function buildMetrics(started, contextText, sourceContextElapsedMs, fastReview,
314
326
  sourceContext: {
315
327
  elapsedMs: sourceContextElapsedMs,
316
328
  bytes: Buffer.byteLength(contextText, "utf8"),
317
- digest: `sha256:${crypto.createHash("sha256").update(contextText, "utf8").digest("hex")}`
329
+ digest: `sha256:${crypto.createHash("sha256").update(contextText, "utf8").digest("hex")}`,
330
+ ...(changedFrom ? { changedFrom } : {})
318
331
  },
319
332
  fastReview: {
320
333
  elapsedMs: fastReviewElapsedMs,
@@ -388,6 +401,7 @@ function usage(code) {
388
401
  "",
389
402
  "options:",
390
403
  " --profile core --ref HEAD --profile-file PATH --cache-dir DIR --context-out PATH",
404
+ " --changed-from REF (incremental overlay: scope context to files changed since REF)",
391
405
  " --fast-model MODEL --strong-model MODEL",
392
406
  " --invariant TEXT --focus TEXT --preview --once",
393
407
  " --schedule-full [--full-delay-minutes N]",
@@ -143,6 +143,22 @@ function main() {
143
143
  note(rel);
144
144
  }
145
145
 
146
+ // 5c. Homebrew formula (repo root, not under pluginRoot). Structured surface:
147
+ // a git-tag formula with no sha256. Homebrew reads the version from the
148
+ // tag, so the ONLY literal that moves is `tag: "v<version>"` (an explicit
149
+ // `version` line would be redundant and brew audit rejects it). A targeted
150
+ // exact-string swap keeps the file byte-formatting intact;
151
+ // version-sync-check.js asserts the tag at HEAD, so it can never drift.
152
+ const formula = path.join(repoRoot, "Formula", "cool-workflow.rb");
153
+ if (fs.existsSync(formula)) {
154
+ const text = fs.readFileSync(formula, "utf8");
155
+ const updated = text.split(`tag: "v${current}"`).join(`tag: "v${next}"`);
156
+ if (updated !== text) {
157
+ fs.writeFileSync(formula, updated);
158
+ note("Formula/cool-workflow.rb");
159
+ }
160
+ }
161
+
146
162
  process.stdout.write(`bump:version ${current} -> ${next}\n`);
147
163
  for (const rel of touched) process.stdout.write(` updated ${rel}\n`);
148
164