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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +104 -129
- package/apps/architecture-review/app.json +1 -1
- package/apps/architecture-review-fast/app.json +1 -1
- package/apps/architecture-review-fast/workflow.js +15 -2
- 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/capability-core.js +47 -0
- package/dist/capability-registry.js +2 -0
- package/dist/cli/command-surface.js +165 -1352
- package/dist/cli/format.js +56 -0
- package/dist/cli/handlers/audit.js +82 -0
- package/dist/cli/handlers/blackboard.js +81 -0
- package/dist/cli/handlers/candidate.js +40 -0
- package/dist/cli/handlers/clones.js +34 -0
- package/dist/cli/handlers/collaboration.js +61 -0
- package/dist/cli/handlers/eval.js +40 -0
- package/dist/cli/handlers/maintenance.js +107 -0
- package/dist/cli/handlers/multi-agent.js +165 -0
- package/dist/cli/handlers/node.js +41 -0
- package/dist/cli/handlers/operational.js +155 -0
- package/dist/cli/handlers/operator.js +146 -0
- package/dist/cli/handlers/registry.js +68 -0
- package/dist/cli/handlers/run.js +153 -0
- package/dist/cli/handlers/scheduling.js +126 -0
- package/dist/cli/handlers/workbench.js +41 -0
- package/dist/cli/handlers/worker.js +45 -0
- package/dist/cli/io.js +27 -0
- package/dist/cli/run-summary.js +45 -0
- package/dist/commit.js +0 -5
- package/dist/execution-backend.js +0 -11
- package/dist/mcp/tool-call.js +2 -0
- package/dist/mcp/tool-definitions.js +8 -0
- package/dist/orchestrator/app-operations.js +205 -0
- package/dist/orchestrator.js +41 -153
- package/dist/state-explosion.js +0 -7
- package/dist/term.js +0 -18
- package/dist/validation.js +0 -21
- package/dist/version.js +1 -1
- package/docs/agent-delegation-drive.7.md +13 -7
- package/docs/cli-mcp-parity.7.md +19 -2
- package/docs/contract-migration-tooling.7.md +3 -1
- package/docs/control-plane-scheduling.7.md +3 -1
- package/docs/durable-state-and-locking.7.md +3 -1
- package/docs/evidence-adoption-reasoning-chain.7.md +3 -1
- package/docs/execution-backends.7.md +3 -1
- package/docs/mcp-app-surface.7.md +12 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +3 -1
- package/docs/multi-agent-eval-replay-harness.7.md +3 -1
- package/docs/multi-agent-operator-ux.7.md +3 -1
- package/docs/node-snapshot-diff-replay.7.md +3 -1
- package/docs/observability-cost-accounting.7.md +3 -1
- package/docs/project-index.md +16 -3
- package/docs/real-execution-backends.7.md +3 -1
- package/docs/release-and-migration.7.md +3 -1
- package/docs/release-tooling.7.md +3 -1
- package/docs/run-registry-control-plane.7.md +27 -3
- package/docs/run-retention-reclamation.7.md +3 -1
- package/docs/state-explosion-management.7.md +3 -1
- package/docs/team-collaboration.7.md +3 -1
- package/docs/web-desktop-workbench.7.md +3 -1
- package/manifest/plugin.manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/agents/builtin-templates.json +3 -2
- package/scripts/agents/claude-p-agent.js +6 -6
- package/scripts/agents/codex-agent.js +17 -3
- package/scripts/agents/deepseek-agent.js +23 -0
- package/scripts/agents/gemini-opencode-agent.js +25 -0
- package/scripts/agents/opencode-agent.js +57 -8
- package/scripts/architecture-review-fast.js +19 -5
- package/scripts/bump-version.js +16 -0
- package/scripts/canonical-apps.js +4 -4
- package/scripts/dogfood-release.js +1 -1
- package/scripts/golden-path.js +4 -4
- package/scripts/parity-check.js +9 -1
- package/scripts/release-flow.js +35 -0
- package/scripts/vendor-preflight.js +127 -0
- package/scripts/version-sync-check.js +5 -0
|
@@ -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
|
package/docs/project-index.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# Cool Workflow Project Index
|
|
2
2
|
|
|
3
|
-
Generated from the current repository code on 2026-06-
|
|
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.
|
|
8
|
+
- Version: `0.1.94`
|
|
9
9
|
- Source modules: `68`
|
|
10
10
|
- Workflow apps: `7`
|
|
11
11
|
- Docs: `53`
|
|
12
|
-
- Smoke tests: `
|
|
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)
|
|
@@ -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 `
|
|
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.
|
|
464
|
+
0.1.93
|
|
465
|
+
|
|
466
|
+
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.
|
|
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
|
@@ -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": "
|
|
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):
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// and vendor-specific stream parsing lives HERE 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
|
|
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 = {
|
|
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
|
-
|
|
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(
|
|
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
|
-
"--
|
|
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]",
|
package/scripts/bump-version.js
CHANGED
|
@@ -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
|
|