cool-workflow 0.1.90 → 0.1.91

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 (47) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +2 -0
  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/capability-core.js +31 -0
  11. package/dist/cli/command-surface.js +55 -8
  12. package/dist/drive.js +2 -1
  13. package/dist/orchestrator.js +3 -0
  14. package/dist/reporter.js +67 -0
  15. package/dist/term.js +73 -1
  16. package/dist/version.js +1 -1
  17. package/docs/agent-delegation-drive.7.md +39 -22
  18. package/docs/cli-mcp-parity.7.md +2 -0
  19. package/docs/contract-migration-tooling.7.md +2 -0
  20. package/docs/control-plane-scheduling.7.md +2 -0
  21. package/docs/durable-state-and-locking.7.md +2 -0
  22. package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
  23. package/docs/execution-backends.7.md +2 -0
  24. package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
  25. package/docs/multi-agent-eval-replay-harness.7.md +2 -0
  26. package/docs/multi-agent-operator-ux.7.md +2 -0
  27. package/docs/node-snapshot-diff-replay.7.md +2 -0
  28. package/docs/observability-cost-accounting.7.md +2 -0
  29. package/docs/project-index.md +5 -3
  30. package/docs/real-execution-backends.7.md +2 -0
  31. package/docs/release-and-migration.7.md +2 -0
  32. package/docs/release-tooling.7.md +2 -0
  33. package/docs/run-registry-control-plane.7.md +2 -0
  34. package/docs/run-retention-reclamation.7.md +2 -0
  35. package/docs/state-explosion-management.7.md +2 -0
  36. package/docs/team-collaboration.7.md +2 -0
  37. package/docs/web-desktop-workbench.7.md +2 -0
  38. package/manifest/plugin.manifest.json +1 -1
  39. package/package.json +1 -1
  40. package/scripts/agents/agent-adapter-core.js +137 -3
  41. package/scripts/agents/claude-p-agent.js +24 -15
  42. package/scripts/agents/codex-agent.js +11 -8
  43. package/scripts/agents/gemini-agent.js +11 -8
  44. package/scripts/agents/opencode-agent.js +11 -8
  45. package/scripts/canonical-apps.js +4 -4
  46. package/scripts/dogfood-release.js +1 -1
  47. package/scripts/golden-path.js +4 -4
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cool-workflow",
3
3
  "description": "A workflow control plane and run-time you are able to check: it sends out jobs in TypeScript, makes certain of work against facts before it goes through, puts state into fixed records, orders jobs by time, runs jobs again and again, gets a group of agents to do their parts together, and talks MCP. It gives the doing of the work to outside agents — it never runs the models itself.",
4
- "version": "0.1.90",
4
+ "version": "0.1.91",
5
5
  "author": {
6
6
  "name": "COOLWHITE LLC"
7
7
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cool-workflow",
3
- "version": "0.1.90",
3
+ "version": "0.1.91",
4
4
  "description": "A workflow control plane and run-time you are able to check: it sends out jobs in TypeScript, makes certain of work against facts before it goes through, puts state into fixed records, orders jobs by time, runs jobs again and again, gets a group of agents to do their parts together, and talks MCP. It gives the doing of the work to outside agents — it never runs the models itself.",
5
5
  "author": {
6
6
  "name": "COOLWHITE LLC"
package/README.md CHANGED
@@ -512,3 +512,5 @@ Orchestration-parity for the agent drive — inline `subWorkflow()` nesting, bou
512
512
  npm test parallel, 4-vendor wrappers (Claude/Codex/Gemini/OpenCode), Homebrew-style CLI UX (colors/did-you-mean/categorized help/error tips/cw info/cw search/cw man/doctor --fix), post-success summaries, agent execution timing
513
513
 
514
514
  v0.1.90
515
+
516
+ v0.1.91
@@ -3,7 +3,7 @@
3
3
  "id": "architecture-review",
4
4
  "title": "Architecture Review",
5
5
  "summary": "Map a repository architecture, assess risks, verify important findings, and synthesize an evidence-backed verdict.",
6
- "version": "0.1.90",
6
+ "version": "0.1.91",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -3,7 +3,7 @@
3
3
  "id": "architecture-review-fast",
4
4
  "title": "Architecture Review Fast",
5
5
  "summary": "Run a shorter architecture review with parallel map and assess phases for faster first results.",
6
- "version": "0.1.90",
6
+ "version": "0.1.91",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -3,7 +3,7 @@
3
3
  "id": "end-to-end-golden-path",
4
4
  "title": "End-to-End Golden Path",
5
5
  "summary": "Deterministic one-worker workflow app for proving the CW integration chain.",
6
- "version": "0.1.90",
6
+ "version": "0.1.91",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -3,7 +3,7 @@
3
3
  "id": "pr-review-fix-ci",
4
4
  "title": "PR Review Fix CI",
5
5
  "summary": "Review a pull request or branch, inspect CI failures, diagnose actionable issues, optionally patch, verify, and summarize with evidence.",
6
- "version": "0.1.90",
6
+ "version": "0.1.91",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -3,7 +3,7 @@
3
3
  "id": "release-cut",
4
4
  "title": "Release Cut",
5
5
  "summary": "Prepare a release with checklist discipline: version checks, changelog, tests, packaging, release notes, and final verification.",
6
- "version": "0.1.90",
6
+ "version": "0.1.91",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -3,7 +3,7 @@
3
3
  "id": "research-synthesis",
4
4
  "title": "Research Synthesis",
5
5
  "summary": "Split a research question into claims, investigate sources, cross-check evidence, verify claims, and synthesize a concise answer.",
6
- "version": "0.1.90",
6
+ "version": "0.1.91",
7
7
  "author": "COOLWHITE LLC",
8
8
  "inputs": [
9
9
  {
@@ -56,6 +56,7 @@ exports.schedPolicyShow = schedPolicyShow;
56
56
  exports.schedPolicySet = schedPolicySet;
57
57
  exports.runDrivePreview = runDrivePreview;
58
58
  exports.runDrive = runDrive;
59
+ exports.collectRunFindings = collectRunFindings;
59
60
  exports.quickstart = quickstart;
60
61
  exports.backendAgentConfigShow = backendAgentConfigShow;
61
62
  exports.backendAgentConfigSet = backendAgentConfigSet;
@@ -83,6 +84,7 @@ const remote_source_1 = require("./remote-source");
83
84
  const telemetry_demo_1 = require("./telemetry-demo");
84
85
  const state_1 = require("./state");
85
86
  const run_export_1 = require("./run-export");
87
+ const result_normalize_1 = require("./result-normalize");
86
88
  const node_fs_1 = __importDefault(require("node:fs"));
87
89
  const node_path_1 = __importDefault(require("node:path"));
88
90
  const scheduling_1 = require("./scheduling");
@@ -553,6 +555,35 @@ exports.QUICKSTART_DEFAULT_APP = "architecture-review";
553
555
  * RED LINE (DIRECTION.md): worker execution still DELEGATES to the operator's own
554
556
  * agent backend (claude -p / codex exec / HTTP endpoint). With no agent configured
555
557
  * the drive fails closed (status=blocked) and we never fabricate a completion. */
558
+ /** Collect a COMPACT findings list for the end-of-run summary by re-parsing each completed
559
+ * worker's `result.md` `cw:result` block (the schema source of truth — `normalizeResultEnvelope`).
560
+ * Stderr/human-side ONLY: this NEVER touches the byte-exact stdout payload, so it cannot perturb
561
+ * `--json` or the `cw:result` fence. `baseDir` anchors to the run's OWN repo (quickstart may run
562
+ * cross-directory). Best-effort: a missing/garbled result.md or an unloadable run is skipped, not
563
+ * fatal — the full prose still lives in report.md + each worker transcript. */
564
+ function collectRunFindings(runner, runId, baseDir) {
565
+ const rows = [];
566
+ try {
567
+ const run = runner.withBaseDir(baseDir).loadRun(runId);
568
+ for (const task of run.tasks) {
569
+ if (task.status !== "completed" || !task.resultPath || !node_fs_1.default.existsSync(task.resultPath))
570
+ continue;
571
+ try {
572
+ const envelope = (0, result_normalize_1.normalizeResultEnvelope)(node_fs_1.default.readFileSync(task.resultPath, "utf8"));
573
+ for (const f of envelope.findings) {
574
+ rows.push({ id: f.id, severity: f.severity || "none", classification: f.classification || "unknown" });
575
+ }
576
+ }
577
+ catch {
578
+ /* skip a garbled result.md — the transcript still holds the full record */
579
+ }
580
+ }
581
+ }
582
+ catch {
583
+ /* run not loadable on this host — the summary degrades to no findings table */
584
+ }
585
+ return rows;
586
+ }
556
587
  function quickstart(runner, args) {
557
588
  const appId = String(args.appId || args.app || args.workflowId || exports.QUICKSTART_DEFAULT_APP);
558
589
  // Remote source (v0.1.91): a `--link <url>` — or a URL passed to `--repo`/`-dir` — is
@@ -58,7 +58,7 @@ const state_explosion_1 = require("../state-explosion");
58
58
  const evidence_reasoning_1 = require("../evidence-reasoning");
59
59
  const doctor_1 = require("../doctor");
60
60
  const orchestrator_2 = require("../orchestrator");
61
- const term_1 = require("../term");
61
+ const reporter_1 = require("../reporter");
62
62
  const version_1 = require("../version");
63
63
  async function runCli(argv = process.argv.slice(2)) {
64
64
  const args = (0, orchestrator_1.parseArgv)(argv);
@@ -84,6 +84,18 @@ async function runCli(argv = process.argv.slice(2)) {
84
84
  // so `cw -q "…" -dir /path` works from any directory (no cd). Explicit --repo wins.
85
85
  if (!args.options.repo && args.options.dir)
86
86
  args.options.repo = args.options.dir;
87
+ // Presentation flags — set BEFORE any drive spawn so the out-of-process agent wrapper
88
+ // inherits them via process.env (presentation-only; stdout/the cw:result fence are untouched):
89
+ // --verbose full agent narration inline (default is compact: current action + summary)
90
+ // --no-color disable ANSI everywhere (CW_NO_COLOR is honored by term.colorEnabled AND the
91
+ // wrapper); complements NO_COLOR/FORCE_COLOR
92
+ // --full also stream full narration AND print the report inline at run end
93
+ if (args.options.verbose)
94
+ process.env.CW_VERBOSE = "1";
95
+ if (args.options["no-color"])
96
+ process.env.CW_NO_COLOR = "1";
97
+ if (args.options.full)
98
+ process.env.CW_OUTPUT = "full";
87
99
  // Bare -q / --question -> redirect to quickstart (auto-detect repo/agent/app).
88
100
  // CONSUME the positional (shift) so the question never survives as positionals[0]
89
101
  // — otherwise the quickstart handler reads it as the app id ("Workflow app not found").
@@ -240,15 +252,17 @@ async function runCli(argv = process.argv.slice(2)) {
240
252
  const qs = (0, capability_core_1.quickstart)(runner, { ...args.options, ...(appId ? { appId } : {}), ...(runId ? { runId } : {}) });
241
253
  printJson(qs);
242
254
  const qr = qs;
243
- // Clean human summary on stderr (TTY-gated). Suppressed under --json so machine
244
- // mode emits ONLY the stdout payload — no stderr chrome to parse around. The
245
- // type guard also skips --check/--preview results (no reportPath of their own).
255
+ // Clean human summary on stderr (TTY-gated, inside the reporter). Suppressed under --json so
256
+ // machine mode emits ONLY the stdout payload — no stderr chrome to parse around. The type
257
+ // guard also skips --check/--preview results (no reportPath of their own). The summary is the
258
+ // COMPACT findings table (re-parsed from each completed worker's cw:result), the report path,
259
+ // and where the per-worker transcripts live — NOT the full prose (that's report.md/--full).
246
260
  if (!wantsJson(args.options) && typeof qr.runId === "string" && typeof qr.reportPath === "string") {
247
- (0, term_1.printSuccessSummary)({
261
+ emitRunSummary(runner, args.options, {
248
262
  runId: qr.runId,
249
263
  reportPath: qr.reportPath,
250
264
  status: String(qr.status || ""),
251
- bundle: Boolean(args.options.bundle),
265
+ statePath: typeof qr.statePath === "string" ? qr.statePath : undefined,
252
266
  completedWorkers: typeof qr.completedWorkers === "number" ? qr.completedWorkers : undefined,
253
267
  plannedWorkers: typeof qr.plannedWorkers === "number" ? qr.plannedWorkers : undefined,
254
268
  agentConfigured: typeof qr.agentConfigured === "boolean" ? qr.agentConfigured : undefined
@@ -1218,10 +1232,11 @@ async function runCli(argv = process.argv.slice(2)) {
1218
1232
  const dr = (0, capability_core_1.runDrive)(runner, driveArgs);
1219
1233
  printJson(dr);
1220
1234
  if (!wantsJson(args.options)) {
1221
- (0, term_1.printSuccessSummary)({
1235
+ emitRunSummary(runner, args.options, {
1222
1236
  runId: dr.runId,
1223
1237
  reportPath: dr.reportPath,
1224
1238
  status: dr.status,
1239
+ statePath: dr.statePath,
1225
1240
  completedWorkers: dr.completedWorkers,
1226
1241
  plannedWorkers: dr.plannedWorkers,
1227
1242
  agentConfigured: dr.agentConfigured
@@ -1241,10 +1256,11 @@ async function runCli(argv = process.argv.slice(2)) {
1241
1256
  const dr = (0, capability_core_1.runDrive)(runner, driveArgs);
1242
1257
  printJson(dr);
1243
1258
  if (!wantsJson(args.options)) {
1244
- (0, term_1.printSuccessSummary)({
1259
+ emitRunSummary(runner, args.options, {
1245
1260
  runId: dr.runId,
1246
1261
  reportPath: dr.reportPath,
1247
1262
  status: dr.status,
1263
+ statePath: dr.statePath,
1248
1264
  completedWorkers: dr.completedWorkers,
1249
1265
  plannedWorkers: dr.plannedWorkers,
1250
1266
  agentConfigured: dr.agentConfigured
@@ -1556,6 +1572,37 @@ function required(value, label) {
1556
1572
  function optionalArg(value) {
1557
1573
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
1558
1574
  }
1575
+ /** Emit the calm end-of-run summary (stderr, TTY-gated inside the reporter): the COMPACT findings
1576
+ * table re-parsed from each completed worker's `cw:result`, the report path, where the per-worker
1577
+ * transcripts live, and — under `--full` — the report inline. Stderr/human-side ONLY: stdout (the
1578
+ * `--json` payload printed just before this) stays byte-exact. Shared by the quickstart and the
1579
+ * two `run --drive` paths so all three render an identical summary. */
1580
+ function emitRunSummary(runner, options, fields) {
1581
+ // Anchor run reads to the run's OWN repo (a drive/quickstart may run cross-directory): the run
1582
+ // dir is <repo>/.cw/runs/<id>/, holding each worker's transcript.md next to its result.md.
1583
+ const runDir = typeof fields.statePath === "string" ? node_path_1.default.dirname(fields.statePath) : undefined;
1584
+ const baseDir = runDir ? node_path_1.default.resolve(runDir, "..", "..", "..") : undefined;
1585
+ const findings = (0, capability_core_1.collectRunFindings)(runner, fields.runId, baseDir);
1586
+ // --full ALSO prints the report inline at run end (the compact table stays the default summary).
1587
+ let fullReport;
1588
+ if (options.full && fields.reportPath && node_fs_1.default.existsSync(fields.reportPath)) {
1589
+ try {
1590
+ fullReport = node_fs_1.default.readFileSync(fields.reportPath, "utf8");
1591
+ }
1592
+ catch { /* best-effort inline */ }
1593
+ }
1594
+ reporter_1.reporter.runSummary({
1595
+ runId: fields.runId,
1596
+ reportPath: fields.reportPath,
1597
+ status: fields.status,
1598
+ completedWorkers: fields.completedWorkers,
1599
+ plannedWorkers: fields.plannedWorkers,
1600
+ agentConfigured: fields.agentConfigured,
1601
+ findings,
1602
+ runDir,
1603
+ fullReport
1604
+ });
1605
+ }
1559
1606
  function printJson(value) {
1560
1607
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
1561
1608
  }
package/dist/drive.js CHANGED
@@ -33,6 +33,7 @@ exports.drivePreview = drivePreview;
33
33
  const node_fs_1 = __importDefault(require("node:fs"));
34
34
  const node_path_1 = __importDefault(require("node:path"));
35
35
  const term_1 = require("./term");
36
+ const reporter_1 = require("./reporter");
36
37
  const dispatch_1 = require("./dispatch");
37
38
  const execution_backend_1 = require("./execution-backend");
38
39
  const worker_isolation_1 = require("./worker-isolation");
@@ -83,7 +84,7 @@ function emitProgress(message) {
83
84
  const forcedOff = process.env.CW_DRIVE_PROGRESS === "0";
84
85
  const forcedOn = process.env.CW_DRIVE_PROGRESS === "1";
85
86
  if ((Boolean(process.stderr.isTTY) && !forcedOff) || forcedOn)
86
- process.stderr.write(`[drive] ${message}\n`);
87
+ reporter_1.reporter.progress(`[drive] ${message}`);
87
88
  }
88
89
  /** Advance exactly ONE deterministic step. Pure-ish: all mutation is through the
89
90
  * existing runner verbs + runBackend. */
@@ -968,6 +968,9 @@ function formatHelp() {
968
968
  " -claude Use Claude agent",
969
969
  " -codex Use Codex agent",
970
970
  " -deepseek Use DeepSeek (via opencode)",
971
+ " --verbose Show full agent narration live (default: compact)",
972
+ " --full Verbose, plus the report printed inline at the end",
973
+ " --no-color Disable ANSI color (also honors NO_COLOR / FORCE_COLOR)",
971
974
  "",
972
975
  (0, term_1.bold)("More commands", out),
973
976
  ...wrapped
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ // src/reporter.ts — the cw-side run reporter.
3
+ //
4
+ // Rendering lives behind a small interface so the drive/command-surface stay logic-only: the
5
+ // orchestrator emits events (progress lines, the end-of-run summary) and the Reporter decides how
6
+ // to draw them. The live AGENT view (spinner / streamed tokens / folding tool lines) is rendered
7
+ // by the async agent wrapper — cw is blocked in spawnSync during a run, so cw only renders the
8
+ // calm orchestration BETWEEN agents plus the final summary. Everything here goes to STDERR; the
9
+ // MACHINE payloads on stdout (the cw:result fence + cw's --json via printJson) carry NO term
10
+ // styling, so they stay byte-exact under any color env. Color is TTY-gated and honors
11
+ // NO_COLOR/CW_NO_COLOR(--no-color)/FORCE_COLOR via term — FORCE_COLOR may color human-readable
12
+ // output (its purpose) but never the machine payloads, which use no styling at all.
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.reporter = void 0;
15
+ exports.createReporter = createReporter;
16
+ const term_1 = require("./term");
17
+ function isTTY(stream) {
18
+ return Boolean(stream.isTTY);
19
+ }
20
+ class StderrReporter {
21
+ s;
22
+ constructor(s) {
23
+ this.s = s;
24
+ }
25
+ progress(line) {
26
+ // The caller already decided WHETHER to emit (drive's CW_DRIVE_PROGRESS/TTY gate) and has
27
+ // already styled the line (phaseProgressLine etc.) — the reporter just centralizes the write
28
+ // so all orchestration output flows through one interface. No extra styling (avoids ANSI nesting).
29
+ this.s.write(`${line}\n`);
30
+ }
31
+ runSummary(f) {
32
+ if (!isTTY(this.s))
33
+ return; // summary is human chrome; piped/--json stdout already carries the data
34
+ const s = this.s;
35
+ const counts = (typeof f.completedWorkers === "number" && typeof f.plannedWorkers === "number")
36
+ ? ` — ${f.completedWorkers}/${f.plannedWorkers}` : "";
37
+ s.write("\n");
38
+ if (f.findings && f.findings.length)
39
+ s.write(`${(0, term_1.formatFindingsSummary)(f.findings, s)}\n\n`);
40
+ s.write(`${(0, term_1.green)("✓", s)} Report: ${f.reportPath}\n`);
41
+ if (f.status === "complete") {
42
+ s.write(` ${(0, term_1.green)("✓", s)} Status: complete${counts}\n`);
43
+ if (f.runDir)
44
+ s.write(` ${(0, term_1.dim)(`Transcript: ${f.runDir}`, s)}\n`);
45
+ s.write(` ${(0, term_1.nextHint)(`cw report ${f.runId} --show`, s)}\n`);
46
+ }
47
+ else {
48
+ s.write(` ${(0, term_1.yellow)("!", s)} Status: ${f.status}${counts}\n`);
49
+ if (f.agentConfigured === false)
50
+ s.write(` ${(0, term_1.tryHint)("cw doctor", s)}\n`);
51
+ else
52
+ s.write(` ${(0, term_1.nextHint)(`cw status ${f.runId}`, s)}\n`);
53
+ }
54
+ if (typeof f.fullReport === "string" && f.fullReport.trim()) {
55
+ s.write(`\n${(0, term_1.dim)("──── full report ────", s)}\n${f.fullReport.trim()}\n`);
56
+ }
57
+ }
58
+ }
59
+ /** Build a reporter over an explicit stream. Used by the default singleton and by tests, which
60
+ * drive a fake `{ isTTY, write }` stream to assert the TTY path renders (findings table + report
61
+ * path + hints) and the non-TTY path stays silent. */
62
+ function createReporter(stream) {
63
+ return new StderrReporter(stream);
64
+ }
65
+ /** The default reporter writes the orchestration view to stderr. (A future non-TTY-specific
66
+ * reporter could differ; today the single impl gates internally on isTTY, matching emitProgress.) */
67
+ exports.reporter = createReporter(process.stderr);
package/dist/term.js CHANGED
@@ -22,9 +22,23 @@ exports.sectionHeader = sectionHeader;
22
22
  exports.phaseProgressLine = phaseProgressLine;
23
23
  exports.formatDuration = formatDuration;
24
24
  exports.printSuccessSummary = printSuccessSummary;
25
+ exports.stripAnsi = stripAnsi;
26
+ exports.visibleWidth = visibleWidth;
27
+ exports.truncate = truncate;
28
+ exports.formatFindingsSummary = formatFindingsSummary;
25
29
  function isTTY(stream = process.stderr) {
26
30
  return Boolean(stream.isTTY);
27
31
  }
32
+ /** Whether to emit ANSI color on a stream, honoring the de-facto env standards:
33
+ * NO_COLOR / CW_NO_COLOR (any non-empty value) disable; FORCE_COLOR (non-"0") forces on
34
+ * even when piped; otherwise fall back to isTTY. The `--no-color` flag sets CW_NO_COLOR. */
35
+ function colorEnabled(stream, env = process.env) {
36
+ if ((env.NO_COLOR ?? "") !== "" || (env.CW_NO_COLOR ?? "") !== "")
37
+ return false;
38
+ if (env.FORCE_COLOR !== undefined && env.FORCE_COLOR !== "" && env.FORCE_COLOR !== "0")
39
+ return true;
40
+ return isTTY(stream);
41
+ }
28
42
  // ---- ansi codes ----
29
43
  const ansi = {
30
44
  reset: "\x1b[0m",
@@ -37,7 +51,7 @@ const ansi = {
37
51
  };
38
52
  // ---- styled text ----
39
53
  function style(code, text, stream) {
40
- if (!isTTY(stream))
54
+ if (!colorEnabled(stream))
41
55
  return text;
42
56
  return `${code}${text}${ansi.reset}`;
43
57
  }
@@ -137,3 +151,61 @@ function printSuccessSummary(fields, stream) {
137
151
  s.write(` ${nextHint(`cw status ${fields.runId}`, s)}\n`);
138
152
  }
139
153
  }
154
+ // ---- width-aware truncation (zero-dep) ----
155
+ const ANSI_RE = /\x1b\[[0-9;]*m/g;
156
+ /** Strip ANSI SGR codes (for measuring visible width). */
157
+ function stripAnsi(text) {
158
+ return text.replace(ANSI_RE, "");
159
+ }
160
+ /** Visible width of a string, ignoring ANSI. Counts each code point as width 1 — a known
161
+ * minor caveat for wide (CJK/emoji) glyphs, acceptable for one-line status truncation. */
162
+ function visibleWidth(text) {
163
+ return [...stripAnsi(text)].length;
164
+ }
165
+ /** Truncate a (possibly styled) string to `maxWidth` visible columns, appending `…` when cut.
166
+ * Operates on the PLAIN text (callers truncate before styling), so no ANSI is split. */
167
+ function truncate(text, maxWidth) {
168
+ if (maxWidth <= 0)
169
+ return "";
170
+ const chars = [...stripAnsi(text)];
171
+ if (chars.length <= maxWidth)
172
+ return text;
173
+ if (maxWidth === 1)
174
+ return "…";
175
+ return `${chars.slice(0, maxWidth - 1).join("")}…`;
176
+ }
177
+ // ---- findings summary table (end-of-run, compact) ----
178
+ /** Severity order for sorting + counting (most severe first). */
179
+ const SEVERITY_ORDER = ["P0", "P1", "P2", "P3", "none"];
180
+ /** Render a compact findings summary assembled from the run's `cw:result` blocks: a one-line
181
+ * count headline (e.g. `Findings: 3 — 2×P1, 1×P2`) plus a small id/severity/class table. This
182
+ * is the end-of-run summary — NOT the full prose (that stays in report.md + the transcript).
183
+ * Returns "" when there are no findings (caller prints nothing). */
184
+ function formatFindingsSummary(findings, stream) {
185
+ if (!findings.length)
186
+ return "";
187
+ const bySev = new Map();
188
+ for (const f of findings)
189
+ bySev.set(f.severity || "none", (bySev.get(f.severity || "none") || 0) + 1);
190
+ const order = (s) => {
191
+ const i = SEVERITY_ORDER.indexOf(s);
192
+ return i === -1 ? SEVERITY_ORDER.length : i;
193
+ };
194
+ const counts = [...bySev.entries()]
195
+ .sort((a, b) => order(a[0]) - order(b[0]))
196
+ .map(([sev, n]) => `${n}×${sev}`)
197
+ .join(", ");
198
+ const rows = [...findings].sort((a, b) => order(a.severity) - order(b.severity));
199
+ const sevW = Math.max(8, ...rows.map((r) => (r.severity || "none").length));
200
+ const clsW = Math.max(5, ...rows.map((r) => (r.classification || "unknown").length));
201
+ const lines = [
202
+ `${bold("Findings:", stream)} ${findings.length} — ${counts}`,
203
+ dim(` ${"SEVERITY".padEnd(sevW)} ${"CLASS".padEnd(clsW)} ID`, stream)
204
+ ];
205
+ for (const r of rows) {
206
+ const sev = (r.severity || "none").padEnd(sevW);
207
+ const cls = (r.classification || "unknown").padEnd(clsW);
208
+ lines.push(` ${sev} ${cls} ${truncate(r.id || "(unnamed)", 60)}`);
209
+ }
210
+ return lines.join("\n");
211
+ }
package/dist/version.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MIN_SUPPORTED_RUN_STATE_SCHEMA_VERSION = exports.LEGACY_RUN_STATE_SCHEMA_VERSION = exports.CURRENT_RUN_STATE_SCHEMA_VERSION = exports.WORKFLOW_APP_SCHEMA_VERSION = exports.CURRENT_COOL_WORKFLOW_VERSION = void 0;
4
- exports.CURRENT_COOL_WORKFLOW_VERSION = "0.1.90";
4
+ exports.CURRENT_COOL_WORKFLOW_VERSION = "0.1.91";
5
5
  exports.WORKFLOW_APP_SCHEMA_VERSION = 1;
6
6
  exports.CURRENT_RUN_STATE_SCHEMA_VERSION = 1;
7
7
  exports.LEGACY_RUN_STATE_SCHEMA_VERSION = 0;
@@ -198,28 +198,43 @@ string). Each verb is declared once in `capability-registry.ts`, so `cw <cmd>
198
198
  --json` is byte-identical to the matching `cw_<tool>` MCP tool for the read-only
199
199
  preview/config-show verbs.
200
200
 
201
- ## Live output — opt-in stderr passthrough (Unix-clean)
202
-
203
- A drive can show the agent's activity live, without touching the evidence
204
- contract, when the operator opts in with `CW_AGENT_STREAM=1`:
205
-
206
- - **Default stays buffered.** Without `CW_AGENT_STREAM=1`, the bundled wrapper
207
- keeps the buffered path and writes one JSON report to stdout after writing
208
- `result.md`.
209
- - **The opt-in wrapper renders; stderr only.** With `CW_AGENT_STREAM=1`, the
210
- bundled Claude wrapper runs claude in `--output-format stream-json`, and the
211
- bundled Codex wrapper runs `codex exec --json --output-last-message`.
212
- Each wrapper renders a short human trace (tool uses, assistant text, per-turn
213
- summaries where present) to its **stderr**diagnostics, never data. It builds
214
- the single `{model, usage, result}` object for stdout after the final answer is
215
- captured.
216
- - **Core forwards, never parses.** `runAgentProcess` passes the agent child's
217
- stderr straight through to the operator's terminal (`stdio` inherit) only when
218
- `CW_AGENT_STREAM=1`, CW's own stderr is a TTY, and `CW_NO_STREAM` is not set.
219
- Piped / CI runs stay quiet (the Rule of Silence). Vendor-specific rendering
220
- lives in the wrapper (policy), not the kernel (mechanism).
221
- - **Determinism intact.** The backend evidence triple hashes stdout only, so
222
- the live stderr stream never changes recorded evidence or replay.
201
+ ## Live output — a calm live view, on stderr, Unix-clean
202
+
203
+ A drive shows the agent's activity live without ever touching the evidence
204
+ contract. The async per-vendor wrapper owns the live region (it parses
205
+ `--output-format stream-json` and its event loop is free); cw renders the calm
206
+ orchestration between agents plus the end-of-run summary. **Everything goes to
207
+ stderr stdout stays the byte-exact data channel** (the `cw:result` fence, the
208
+ wrapper's `{model, usage, result}` JSON, cw's `--json` payload), so a pipe is
209
+ never polluted.
210
+
211
+ - **Interactive (TTY).** The wrapper renders ONE in-place status line: a Braille
212
+ spinner + the current action + elapsed (e.g. `⠹ Read app.js 1.2s`). Tool calls
213
+ fold to a single line eachspinning while running, then resolving to
214
+ `✓ Read app.js (0.3s)` / `✗ Bash (1.1s)` (dimmed, args width-truncated). The
215
+ cursor is hidden while the spinner runs and **ALWAYS restored** on exit /
216
+ Ctrl-C / SIGTERM (Ctrl-C exits non-zero, leaving a clean terminal).
217
+ - **Non-TTY stays SILENT by default** (the Rule of Silence). `CW_AGENT_STREAM=1`
218
+ opts a CI/piped run into a **plain append-only** trace (`→ …` / `✓ … (Xs)`
219
+ lines, zero ANSI/cursor bytes) for debuggability mirroring `CW_DRIVE_PROGRESS=1`.
220
+ - **Verbosity.** Default is compact: the current action + folded tool lines, with
221
+ the model's narration/reasoning HIDDEN. `--verbose` (sets `CW_VERBOSE=1`)
222
+ surfaces the full narration inline; `--full` (sets `CW_OUTPUT=full`) implies
223
+ verbose AND prints the report inline at run end.
224
+ - **Transcript always on disk.** Regardless of verbosity — even when the screen
225
+ view is silent or compact — the wrapper writes the COMPLETE narration + tool I/O
226
+ to `transcript.md` next to that worker's `result.md`. The end-of-run summary
227
+ prints the run dir where the transcripts live, so nothing is ever lost to a
228
+ compact view.
229
+ - **Color control.** `NO_COLOR` / `CW_NO_COLOR` (the `--no-color` flag sets the
230
+ latter) disable ANSI; `FORCE_COLOR` forces it even when piped; otherwise color
231
+ follows isTTY. Honored identically by cw (`term.ts`) and every wrapper.
232
+ - **End-of-run summary (cw side).** A COMPACT findings table — id / severity /
233
+ classification + counts, re-parsed from each completed worker's `cw:result` —
234
+ plus the report path, status, and run dir. NOT the full prose (that stays in
235
+ `report.md` + the transcript); `--full` also prints the report inline.
236
+ - **Determinism intact.** The backend evidence triple hashes stdout only, so the
237
+ live stderr view never changes recorded evidence or replay.
223
238
 
224
239
  The built-in templates are:
225
240
 
@@ -319,3 +334,5 @@ Orchestration-parity for the agent drive: `run --drive --incremental` step-level
319
334
  The one-command `cw -q` headline now routes the question and defaults the repo to the caller cwd before driving the agent; the delegation contract, drive, and accept path are unchanged.
320
335
 
321
336
  0.1.90
337
+
338
+ 0.1.91
@@ -526,3 +526,5 @@ CLI surface simplified to 6 commands with agent stderr streaming on by default a
526
526
  CLI golden-path fixes: `cw -q "…"` routes the question (was read as an app id → "Workflow app not found"), auto-detects the cwd as the repo (run anywhere, no `--repo`), and `cw help` wraps its command list with a trailing newline; the CLI↔MCP parity contract and the help-token parser are unchanged.
527
527
 
528
528
  0.1.90
529
+
530
+ 0.1.91
@@ -159,3 +159,5 @@ _No behavioral change in v0.1.88 (no schema-migration edge was added; the increm
159
159
  _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes only; this subsystem is unchanged)._
160
160
 
161
161
  0.1.90
162
+
163
+ 0.1.91
@@ -143,3 +143,5 @@ _No behavioral change in v0.1.88 (the `sched` priority/readiness selection, conc
143
143
  _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes only; this subsystem is unchanged)._
144
144
 
145
145
  0.1.90
146
+
147
+ 0.1.91
@@ -142,3 +142,5 @@ _No behavioral change in v0.1.88 (atomic writes, fsync-durability for audit-esse
142
142
  _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes only; this subsystem is unchanged)._
143
143
 
144
144
  0.1.90
145
+
146
+ 0.1.91
@@ -303,3 +303,5 @@ _No behavioral change in v0.1.88 (the evidence adoption reasoning chain and its
303
303
  _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes only; this subsystem is unchanged)._
304
304
 
305
305
  0.1.90
306
+
307
+ 0.1.91
@@ -333,3 +333,5 @@ Agent stderr live-streaming is now on by default when stderr is a TTY (CW_AGENT_
333
333
  _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes only; this subsystem is unchanged)._
334
334
 
335
335
  0.1.90
336
+
337
+ 0.1.91
@@ -301,3 +301,5 @@ The host-facing surface tracks the CLI simplification to 6 commands (streaming o
301
301
  The host-facing surface tracks the CLI golden-path fixes (`cw -q` routing + repo auto-detect + clean help); the multi-agent verbs and their MCP-tool mirrors are unchanged.
302
302
 
303
303
  0.1.90
304
+
305
+ 0.1.91
@@ -335,3 +335,5 @@ _No change in behavior in v0.1.88 (no harness code changed; the new `loop-contro
335
335
  _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes only; this subsystem is unchanged)._
336
336
 
337
337
  0.1.90
338
+
339
+ 0.1.91
@@ -347,3 +347,5 @@ _No behavioral change in v0.1.88 (no operator-view code changed; the new sub-wor
347
347
  _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes only; this subsystem is unchanged)._
348
348
 
349
349
  0.1.90
350
+
351
+ 0.1.91
@@ -168,3 +168,5 @@ A new `loop-control` StateNodeKind now flows through per-node snapshot/diff/repl
168
168
  _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes only; this subsystem is unchanged)._
169
169
 
170
170
  0.1.90
171
+
172
+ 0.1.91
@@ -227,3 +227,5 @@ Attestation now signs the agent's RESULT, not just its usage: `TelemetryAttestat
227
227
  _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes only; this subsystem is unchanged)._
228
228
 
229
229
  0.1.90
230
+
231
+ 0.1.91