cool-workflow 0.2.4 → 0.2.5

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 (132) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/.gemini-plugin/mcp.json +10 -0
  4. package/.gemini-plugin/plugin.json +40 -0
  5. package/.opencode-plugin/mcp.json +10 -0
  6. package/.opencode-plugin/plugin.json +40 -0
  7. package/README.md +202 -48
  8. package/apps/architecture-review/app.json +1 -1
  9. package/apps/architecture-review-fast/app.json +1 -1
  10. package/apps/end-to-end-golden-path/app.json +1 -1
  11. package/apps/pr-review-fix-ci/app.json +1 -1
  12. package/apps/release-cut/app.json +1 -1
  13. package/apps/research-synthesis/app.json +1 -1
  14. package/dist/cli/dispatch.js +32 -4
  15. package/dist/cli/entry.js +11 -19
  16. package/dist/cli/global-flags.js +113 -0
  17. package/dist/cli/parseargv.js +7 -1
  18. package/dist/core/capability-data.js +337 -8
  19. package/dist/core/format/help.js +73 -3
  20. package/dist/core/format/recovery-hint.js +32 -0
  21. package/dist/core/multi-agent/collaboration.js +35 -6
  22. package/dist/core/multi-agent/runtime.js +7 -0
  23. package/dist/core/multi-agent/trust-policy.js +7 -1
  24. package/dist/core/pipeline/contract.js +7 -0
  25. package/dist/core/pipeline/error-feedback.js +2 -2
  26. package/dist/core/trust/evidence-grounding.js +13 -1
  27. package/dist/core/util/cli-args.js +22 -0
  28. package/dist/core/version.js +1 -1
  29. package/dist/mcp/dispatch.js +22 -2
  30. package/dist/mcp/server.js +124 -13
  31. package/dist/mcp-server.js +20 -0
  32. package/dist/shell/commit.js +8 -2
  33. package/dist/shell/coordinator-io.js +73 -1
  34. package/dist/shell/drive.js +120 -63
  35. package/dist/shell/error-feedback-io.js +6 -0
  36. package/dist/shell/execution-backend/agent.js +195 -23
  37. package/dist/shell/execution-backend/container.js +44 -10
  38. package/dist/shell/execution-backend/local.js +32 -0
  39. package/dist/shell/fs-atomic.js +93 -12
  40. package/dist/shell/ledger-cli.js +9 -2
  41. package/dist/shell/multi-agent-cli.js +5 -1
  42. package/dist/shell/onramp.js +48 -5
  43. package/dist/shell/pipeline.js +2 -1
  44. package/dist/shell/reclamation-io.js +10 -9
  45. package/dist/shell/run-export.js +52 -4
  46. package/dist/shell/run-store.js +156 -0
  47. package/dist/shell/scheduler-io.js +101 -10
  48. package/dist/shell/telemetry-ledger-io.js +36 -24
  49. package/dist/shell/trust-audit.js +104 -10
  50. package/dist/shell/workbench-host.js +121 -10
  51. package/dist/shell/workbench.js +79 -5
  52. package/dist/shell/worker-isolation.js +1 -1
  53. package/dist/wiring/capability-table/basics.js +5 -0
  54. package/dist/wiring/capability-table/exec-backend.js +40 -22
  55. package/dist/wiring/capability-table/pipeline.js +32 -0
  56. package/dist/wiring/capability-table/registry-core.js +26 -3
  57. package/dist/wiring/capability-table/reporting.js +7 -1
  58. package/dist/wiring/capability-table/scheduling-registry.js +8 -2
  59. package/dist/wiring/capability-table/trust-ledger.js +54 -12
  60. package/docs/agent-delegation-drive.7.md +15 -0
  61. package/docs/cli-mcp-parity.7.md +25 -0
  62. package/docs/contract-migration-tooling.7.md +2 -0
  63. package/docs/control-plane-scheduling.7.md +36 -0
  64. package/docs/cross-agent-ledger.7.md +20 -8
  65. package/docs/durable-state-and-locking.7.md +2 -0
  66. package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
  67. package/docs/execution-backends.7.md +2 -0
  68. package/docs/fix.7.md +4 -4
  69. package/docs/getting-started.md +40 -32
  70. package/docs/index.md +17 -0
  71. package/docs/launch/demo.tape +4 -3
  72. package/docs/mcp-app-surface.7.md +6 -0
  73. package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
  74. package/docs/multi-agent-eval-replay-harness.7.md +2 -0
  75. package/docs/multi-agent-operator-ux.7.md +2 -0
  76. package/docs/node-snapshot-diff-replay.7.md +2 -0
  77. package/docs/observability-cost-accounting.7.md +2 -0
  78. package/docs/project-index.md +39 -9
  79. package/docs/real-execution-backends.7.md +2 -0
  80. package/docs/release-and-migration.7.md +2 -0
  81. package/docs/release-tooling.7.md +2 -0
  82. package/docs/routine.7.md +22 -0
  83. package/docs/run-registry-control-plane.7.md +2 -0
  84. package/docs/run-retention-reclamation.7.md +10 -4
  85. package/docs/state-explosion-management.7.md +2 -0
  86. package/docs/team-collaboration.7.md +2 -0
  87. package/docs/trust-audit-anchor.7.md +2 -0
  88. package/docs/web-desktop-workbench.7.md +24 -2
  89. package/docs/workflow-app-framework.7.md +31 -0
  90. package/manifest/plugin.manifest.json +1 -1
  91. package/manifest/source-context-profiles.json +4 -3
  92. package/package.json +4 -2
  93. package/scripts/agents/agent-adapter-core.js +26 -0
  94. package/scripts/agents/claude-p-agent.js +4 -1
  95. package/scripts/agents/codex-agent.js +4 -0
  96. package/scripts/agents/cw-attest-wrap.js +1 -1
  97. package/scripts/agents/gemini-agent.js +4 -0
  98. package/scripts/agents/opencode-agent.js +5 -0
  99. package/scripts/block-unapproved-tag.js +160 -0
  100. package/scripts/canonical-apps.js +4 -4
  101. package/scripts/children/batch-delegate-child.js +72 -4
  102. package/scripts/children/http-batch-delegate-child.js +132 -0
  103. package/scripts/children/http-delegate-child.js +8 -0
  104. package/scripts/dogfood-release.js +2 -2
  105. package/scripts/fake-date-for-reproduction.js +1 -1
  106. package/scripts/golden-path.js +4 -4
  107. package/scripts/lang-policy-check.js +82 -0
  108. package/scripts/purity-gate.js +16 -3
  109. package/scripts/release-check.js +2 -1
  110. package/scripts/release-flow.js +128 -30
  111. package/scripts/release-gate.js +184 -0
  112. package/scripts/release-oneclick.js +38 -7
  113. package/scripts/release-tags.js +47 -0
  114. package/scripts/verdict-keygen.js +2 -2
  115. package/scripts/verify-bump-reproduction.js +193 -0
  116. package/scripts/verify-verdict-signature.js +1 -1
  117. package/ui/workbench/app.css +41 -9
  118. package/ui/workbench/app.js +160 -27
  119. package/ui/workbench/index.html +2 -2
  120. package/docs/agent-framework.md +0 -177
  121. package/docs/designs/handoff-ledger.md +0 -145
  122. package/docs/dogfood/resume-drive-real-agent-2026-06-14.md +0 -40
  123. package/docs/launch/launch-kit.md +0 -195
  124. package/docs/launch/pre-launch-checklist.md +0 -53
  125. package/docs/readme-v0.1.87-full.md +0 -301
  126. package/docs/routines.md +0 -101
  127. package/docs/scheduled-tasks.md +0 -80
  128. package/scripts/agents/claude-p-agent.sh +0 -9
  129. package/scripts/block-unapproved-tag.sh +0 -75
  130. package/scripts/release-gate.sh +0 -94
  131. package/scripts/verify-bump-reproduction.sh +0 -148
  132. package/scripts/verify-container-selfref.js +0 -64
@@ -16,23 +16,26 @@ const registry_core_1 = require("./registry-core");
16
16
  // their milestone-2 placeholder `mcp.handler` with the real body, exactly
17
17
  // as milestones 3/4 did for their own rows.
18
18
  // ---------------------------------------------------------------------
19
- const exec_backend_cli_1 = require("../../shell/exec-backend-cli");
20
19
  const cli_args_1 = require("../../core/util/cli-args");
21
- // This slice is required unconditionally at startup for every command;
22
- // load doctor/app-run-cli lazily so only doctor/fix/sandbox.choose/
23
- // sandbox.resolve/app.run handlers pay their require cost.
20
+ // This slice is required unconditionally at startup for every command; every
21
+ // shell import below is lazy (require()'d only inside the handler that
22
+ // actually uses it), so a command that never touches doctor/fix/sandbox/
23
+ // backend/app.run never pays any of these shell modules' require cost.
24
24
  function loadDoctor() {
25
25
  return require("../../shell/doctor");
26
26
  }
27
27
  function loadAppRunCli() {
28
28
  return require("../../shell/app-run-cli");
29
29
  }
30
+ function loadExecBackendCli() {
31
+ return require("../../shell/exec-backend-cli");
32
+ }
30
33
  (0, registry_core_1.attachCliBinding)("sandbox.list", {
31
34
  path: ["sandbox", "list"],
32
35
  jsonMode: "default",
33
- handler: (args) => ({ json: (0, exec_backend_cli_1.listSandboxProfilesCli)(args.options) }),
36
+ handler: (args) => ({ json: loadExecBackendCli().listSandboxProfilesCli(args.options) }),
34
37
  });
35
- registry_core_1.REGISTRY_BY_CAPABILITY.get("sandbox.list").mcp.handler = (args) => (0, exec_backend_cli_1.listSandboxProfilesCli)(args);
38
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("sandbox.list").mcp.handler = (args) => loadExecBackendCli().listSandboxProfilesCli(args);
36
39
  // GAP #24: cw_sandbox_choose / cw_sandbox_resolve + cw_app_run were declared
37
40
  // MCP-only rows with the notYetImplemented placeholder handler. Wire them to
38
41
  // the ported shell bodies (both are MCP-only in the old build — no CLI path).
@@ -42,18 +45,18 @@ registry_core_1.REGISTRY_BY_CAPABILITY.get("app.run").mcp.handler = (args) => lo
42
45
  (0, registry_core_1.attachCliBinding)("sandbox.show", {
43
46
  path: ["sandbox", "show"],
44
47
  jsonMode: "default",
45
- handler: (args) => ({ json: (0, exec_backend_cli_1.showSandboxProfileCli)((0, cli_args_1.required)(args.positionals[0], "profile id"), args.options) }),
48
+ handler: (args) => ({ json: loadExecBackendCli().showSandboxProfileCli((0, cli_args_1.required)(args.positionals[0], "profile id"), args.options) }),
46
49
  });
47
- registry_core_1.REGISTRY_BY_CAPABILITY.get("sandbox.show").mcp.handler = (args) => (0, exec_backend_cli_1.showSandboxProfileCli)((0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.profileId), "profile id"), args);
50
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("sandbox.show").mcp.handler = (args) => loadExecBackendCli().showSandboxProfileCli((0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.profileId), "profile id"), args);
48
51
  (0, registry_core_1.attachCliBinding)("sandbox.validate", {
49
52
  path: ["sandbox", "validate"],
50
53
  jsonMode: "default",
51
54
  handler: (args) => {
52
- const result = (0, exec_backend_cli_1.validateSandboxProfileCli)((0, cli_args_1.required)(args.positionals[0], "profile file"), args.options);
55
+ const result = loadExecBackendCli().validateSandboxProfileCli((0, cli_args_1.required)(args.positionals[0], "profile file"), args.options);
53
56
  return { json: result, exitCode: result.valid ? undefined : 1 };
54
57
  },
55
58
  });
56
- registry_core_1.REGISTRY_BY_CAPABILITY.get("sandbox.validate").mcp.handler = (args) => (0, exec_backend_cli_1.validateSandboxProfileCli)((0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.profileFile), "profile file"), args);
59
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("sandbox.validate").mcp.handler = (args) => loadExecBackendCli().validateSandboxProfileCli((0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.profileFile), "profile file"), args);
57
60
  // PARITY: `sandbox.choose`/`sandbox.resolve` are BOTH-surface capabilities
58
61
  // per SPEC/mcp.md (old build cli.path ["sandbox","choose"]/["sandbox",
59
62
  // "resolve"]) — they were left MCP-only at GAP #24 (see the comment
@@ -74,21 +77,21 @@ registry_core_1.REGISTRY_BY_CAPABILITY.get("sandbox.validate").mcp.handler = (ar
74
77
  (0, registry_core_1.attachCliBinding)("backend.list", {
75
78
  path: ["backend", "list"],
76
79
  jsonMode: "default",
77
- handler: () => ({ json: (0, exec_backend_cli_1.listBackendsCli)() }),
80
+ handler: () => ({ json: loadExecBackendCli().listBackendsCli() }),
78
81
  });
79
- registry_core_1.REGISTRY_BY_CAPABILITY.get("backend.list").mcp.handler = () => (0, exec_backend_cli_1.listBackendsCli)();
82
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("backend.list").mcp.handler = () => loadExecBackendCli().listBackendsCli();
80
83
  (0, registry_core_1.attachCliBinding)("backend.show", {
81
84
  path: ["backend", "show"],
82
85
  jsonMode: "default",
83
- handler: (args) => ({ json: (0, exec_backend_cli_1.showBackendCli)((0, cli_args_1.required)(args.positionals[0], "backend id")) }),
86
+ handler: (args) => ({ json: loadExecBackendCli().showBackendCli((0, cli_args_1.required)(args.positionals[0], "backend id")) }),
84
87
  });
85
- registry_core_1.REGISTRY_BY_CAPABILITY.get("backend.show").mcp.handler = (args) => (0, exec_backend_cli_1.showBackendCli)((0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.backendId), "backend id"));
88
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("backend.show").mcp.handler = (args) => loadExecBackendCli().showBackendCli((0, cli_args_1.required)((0, cli_args_1.optionalArg)(args.backendId), "backend id"));
86
89
  (0, registry_core_1.attachCliBinding)("backend.probe", {
87
90
  path: ["backend", "probe"],
88
91
  jsonMode: "default",
89
- handler: (args) => ({ json: (0, exec_backend_cli_1.probeBackendCli)(args.positionals[0], args.options) }),
92
+ handler: (args) => ({ json: loadExecBackendCli().probeBackendCli(args.positionals[0], args.options) }),
90
93
  });
91
- registry_core_1.REGISTRY_BY_CAPABILITY.get("backend.probe").mcp.handler = (args) => (0, exec_backend_cli_1.probeBackendCli)((0, cli_args_1.optionalArg)(args.backendId), args);
94
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("backend.probe").mcp.handler = (args) => loadExecBackendCli().probeBackendCli((0, cli_args_1.optionalArg)(args.backendId), args);
92
95
  // `backend agent config [show]` = read-only; `backend agent config set
93
96
  // ...` = mutating. CLI path is ["backend", "agent"] (2 tokens, matching
94
97
  // dispatchTable's supported path lengths); the remaining positionals
@@ -102,8 +105,8 @@ registry_core_1.REGISTRY_BY_CAPABILITY.get("backend.probe").mcp.handler = (args)
102
105
  handler: (args) => {
103
106
  const action = args.positionals[1];
104
107
  if (action === "set")
105
- return { json: (0, exec_backend_cli_1.backendAgentConfigSet)(args.options) };
106
- return { json: (0, exec_backend_cli_1.backendAgentConfigShow)(args.options) };
108
+ return { json: loadExecBackendCli().backendAgentConfigSet(args.options) };
109
+ return { json: loadExecBackendCli().backendAgentConfigShow(args.options) };
107
110
  },
108
111
  });
109
112
  // `backend.agent.config.set` shares the SAME dispatch path/handler as
@@ -124,12 +127,12 @@ registry_core_1.REGISTRY_BY_CAPABILITY.get("backend.probe").mcp.handler = (args)
124
127
  handler: (args) => {
125
128
  const action = args.positionals[1];
126
129
  if (action === "set")
127
- return { json: (0, exec_backend_cli_1.backendAgentConfigSet)(args.options) };
128
- return { json: (0, exec_backend_cli_1.backendAgentConfigShow)(args.options) };
130
+ return { json: loadExecBackendCli().backendAgentConfigSet(args.options) };
131
+ return { json: loadExecBackendCli().backendAgentConfigShow(args.options) };
129
132
  },
130
133
  });
131
- registry_core_1.REGISTRY_BY_CAPABILITY.get("backend.agent.config.show").mcp.handler = (args) => (0, exec_backend_cli_1.backendAgentConfigShow)(args);
132
- registry_core_1.REGISTRY_BY_CAPABILITY.get("backend.agent.config.set").mcp.handler = (args) => (0, exec_backend_cli_1.backendAgentConfigSet)(args);
134
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("backend.agent.config.show").mcp.handler = (args) => loadExecBackendCli().backendAgentConfigShow(args);
135
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("backend.agent.config.set").mcp.handler = (args) => loadExecBackendCli().backendAgentConfigSet(args);
133
136
  // PARITY: `backend.agent.config.set` mutates $CW_HOME/agent-config.json
134
137
  // (secret-stripped) before returning the effective config; both surfaces
135
138
  // perform the same write, so it is a documented opt-out from the
@@ -154,6 +157,21 @@ registry_core_1.REGISTRY_BY_CAPABILITY.get("backend.agent.config.set").reason =
154
157
  const text = (0, cli_args_1.wantsJson)(args.options) ? undefined : args.options.fix ? `${doctor.formatDoctorFixes(report)}\n` : doctor.formatDoctorReport(report);
155
158
  return { json: report, text, exitCode: report.ok ? undefined : 1 };
156
159
  },
160
+ // UI/UX fix: `cw help doctor` used to list only the one-line summary
161
+ // above, so a first-run user had no way to learn these flags without
162
+ // reading source. Real flag names verified against this handler and
163
+ // the `runDoctor(args.options, ...)` call it makes (shell/doctor.ts).
164
+ flags: [
165
+ { name: "--onramp", summary: "Run the extra onramp checks (3-step quick start)." },
166
+ { name: "--fix", summary: "Print fix commands instead of the full report." },
167
+ { name: "--changed-from REF", summary: "Check the onramp only for the change since REF." },
168
+ { name: "--json", summary: "Print the report as JSON." },
169
+ ],
170
+ // The handler above and runDoctor (shell/doctor.ts) read exactly
171
+ // onramp/fix/changed-from/json plus the shared globals (cwd, format)
172
+ // — the list above is complete, so the dispatcher may warn about an
173
+ // unknown flag (TTY-only; see cli/global-flags.ts).
174
+ flagsComplete: true,
157
175
  }, "Environment diagnostics are inherently local to the CLI host — Node version, $PATH, $CW_HOME/cwd writability. An MCP client diagnosing the server process's environment is not meaningful; agents already receive the same readiness facts in their typed results (e.g. status: blocked, agentConfigured). Inspired by `brew doctor`.");
158
176
  (0, registry_core_1.addCliOnlyCapability)("fix", "Print consolidated fix commands for CW setup issues.", {
159
177
  path: ["fix"],
@@ -45,6 +45,13 @@ function loadCommitSummary() {
45
45
  // only so `cw help run` lists both capabilities.
46
46
  (0, registry_core_1.attachCliBinding)("run.drive.step", {
47
47
  path: ["run"],
48
+ // NOTE: the doubled "cw run drive" command column (this row + run.drive's
49
+ // preview row below) is pinned by SPEC/cli-probe.md "Odd things" item 5
50
+ // ("run drive (preview + drive)" — "A rebuild's help printer must keep
51
+ // these doubled rows"), same as `cw sched policy`. So the UI/UX fix for
52
+ // the two look-alike rows lives in the SUMMARIES (each now names its own
53
+ // invocation spelling — see the summary edits where these capabilities
54
+ // are declared), never in the row count or the command column.
48
55
  helpPath: ["run", "drive"],
49
56
  jsonMode: "default",
50
57
  handler: async (args) => {
@@ -124,6 +131,19 @@ function loadCommitSummary() {
124
131
  });
125
132
  registry_core_1.REGISTRY_BY_CAPABILITY.get("run.drive").mcp.handler = (args) => loadPipelineCli().runDrivePreview(args);
126
133
  registry_core_1.REGISTRY_BY_CAPABILITY.get("run.drive.step").mcp.handler = (args) => loadPipelineCli().runDriveStep(args);
134
+ // UI/UX fix: `cw help run` prints TWO "cw run drive" rows — the doubling
135
+ // itself is pinned by SPEC/cli-probe.md "Odd things" item 5 ("run drive
136
+ // (preview + drive)": a rebuild's help printer must keep these doubled
137
+ // rows), so the command column and the row count must not change. What CAN
138
+ // change is the summary text: it used to leave a reader no way to tell
139
+ // which spelling gets which behavior. Each summary now ends with the exact
140
+ // command form that reaches it. Help-page-only: `summary` is overridden on
141
+ // the Capability row, `mcp.description` (the tools/list byte surface) is
142
+ // already captured by buildMcpBinding and stays untouched.
143
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("run.drive").summary =
144
+ "Preview the next agent-delegation drive step for a run (read-only, deterministic). Use: cw run drive <run-id>.";
145
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("run.drive.step").summary =
146
+ "Drive a run by delegating each worker to the agent backend (plan->dispatch->fulfill->accept->commit; --once for one step). Use: cw run <app> --drive.";
127
147
  // PARITY: `run.drive.step` advances the run by spawning the external
128
148
  // agent per worker and recording attested output — not a read probe.
129
149
  // CLI (--drive/--step) and MCP route through the same drive() core; the
@@ -246,6 +266,18 @@ registry_core_1.REGISTRY_BY_CAPABILITY.get("run.restore").mcp.handler = (args) =
246
266
  const exitCode = (result.mode === "check" && result.ok === false) || bundleFailed ? 1 : undefined;
247
267
  return { json: result, exitCode };
248
268
  },
269
+ // UI/UX fix: `cw help quickstart` used to list only the one-line
270
+ // summary above, so a first-run user had no way to learn these flags
271
+ // without reading source. Real flag names verified against
272
+ // shell/pipeline-cli.ts's quickstartRun.
273
+ flags: [
274
+ { name: "--check", summary: "Check setup only; do not plan, run, or write anything." },
275
+ { name: "--preview", summary: "Show the next step, but do not run it." },
276
+ { name: "--bundle", summary: "Seal a finished run into a portable, self-checked bundle." },
277
+ { name: "--with-trust-key KEY", summary: "Add a public key to the bundle for a strict check." },
278
+ { name: "--resume", summary: "Move a stopped run forward one step, or to the end with --run ID." },
279
+ { name: "--link URL", summary: "Review a remote repo by its URL." },
280
+ ],
249
281
  }, "quickstart composes plan/runDrive/report; SPEC/mcp.md's declared cli-only list names it explicitly (no MCP peer). `audit-run` is a CLI-only alias of the same wrapper.", "quickstart");
250
282
  (0, registry_core_1.attachCliBinding)("dispatch", {
251
283
  path: ["dispatch"],
@@ -113,11 +113,13 @@ function buildMcpBinding(row) {
113
113
  // into its separate AND-groups here (the one place the row shape is turned
114
114
  // into the runtime McpBinding.requiredArgs contract).
115
115
  const requiredArgs = row.requiredArgs.flatMap((group) => group.split(",").map((entry) => entry.trim()).filter(Boolean));
116
+ const annotations = capability_data_1.MCP_TOOL_ANNOTATIONS[row.tool];
116
117
  return {
117
118
  tool: row.tool,
118
119
  requiredArgs: requiredArgs.length ? requiredArgs : undefined,
119
120
  properties: row.properties,
120
121
  description: row.description,
122
+ ...(annotations ? { annotations } : {}),
121
123
  handler,
122
124
  };
123
125
  }
@@ -179,7 +181,19 @@ function findCapabilityByCliPath(path) {
179
181
  function cliCapabilities() {
180
182
  return exports.REGISTRY.filter((row) => Boolean(row.cli));
181
183
  }
182
- /** `tools/list`'s exact array, in the pinned source order. */
184
+ /** `tools/list`'s exact array, in the pinned source order. Each
185
+ * property's shape comes from the first hit, in order: a per-tool
186
+ * `PROPERTY_OVERRIDES` entry, then the shared `COMMON_PROPERTY_TYPES`
187
+ * entry for that property name, then the plain `stringProperty`
188
+ * fallback for any name neither table covers.
189
+ *
190
+ * `inputSchema.required` is built from `row.mcp.requiredArgs`, which
191
+ * mcp/dispatch.ts's `requiredToolArguments` also reads: each array
192
+ * entry is one AND-required group, already split from the transcript's
193
+ * comma form (see `buildMcpBinding` above). A group holding `|` is an
194
+ * OR-group — at least one of the named keys must be present, so no
195
+ * single name from it can go into the flat, AND-only `required` list;
196
+ * a group with no `|` is one plain required name. */
183
197
  function mcpToolDefinitions() {
184
198
  const definitions = [];
185
199
  for (const row of exports.REGISTRY) {
@@ -188,12 +202,21 @@ function mcpToolDefinitions() {
188
202
  const overrides = capability_data_1.PROPERTY_OVERRIDES[row.mcp.tool] ?? {};
189
203
  const properties = {};
190
204
  for (const propName of row.mcp.properties) {
191
- properties[propName] = overrides[propName] ?? (0, capability_data_1.stringProperty)(propName);
205
+ properties[propName] = overrides[propName] ?? capability_data_1.COMMON_PROPERTY_TYPES[propName] ?? (0, capability_data_1.stringProperty)(propName);
192
206
  }
207
+ const required = (row.mcp.requiredArgs ?? []).filter((group) => !group.includes("|"));
193
208
  definitions.push({
194
209
  name: row.mcp.tool,
195
210
  description: row.mcp.description,
196
- inputSchema: { type: "object", properties, additionalProperties: true },
211
+ // Additive behavior hints (MCP_TOOL_ANNOTATIONS): present only for
212
+ // tools whose handler was checked by hand; omitted otherwise.
213
+ ...(row.mcp.annotations ? { annotations: row.mcp.annotations } : {}),
214
+ inputSchema: {
215
+ type: "object",
216
+ properties,
217
+ additionalProperties: true,
218
+ ...(required.length ? { required } : {}),
219
+ },
197
220
  });
198
221
  }
199
222
  return definitions;
@@ -325,7 +325,13 @@ registry_core_1.REGISTRY_BY_CAPABILITY.get("workbench.view").mcp.handler = (args
325
325
  // renderCliResult: it is synchronous, so a genuinely blocking serve
326
326
  // must perform its own stdout write and keep the event loop alive
327
327
  // rather than returning a CliHandlerResult at all.
328
- void host.run();
328
+ // run() already fails closed internally (a `cw:` line + exit 1) for the
329
+ // known bind failures; this .catch is a backstop so any future throw
330
+ // path can never surface as an unhandled-rejection stack dump.
331
+ void host.run().catch((error) => {
332
+ process.stderr.write(`cw: ${error instanceof Error ? error.message : String(error)}\n`);
333
+ process.exitCode = 1;
334
+ });
329
335
  return { json: undefined };
330
336
  },
331
337
  });
@@ -76,8 +76,14 @@ function firstPositionalArg(args, index = 0) {
76
76
  return { json: registryCli.scheduleDaemonTickCli(args.options) };
77
77
  // Never returns (matches the old build's forever daemon loop);
78
78
  // the process stays alive via the DesktopSchedulerDaemon's own
79
- // setInterval, printing one tick line per interval.
80
- void registryCli.scheduleDaemonRunForever(args.options);
79
+ // setInterval, printing one tick line per interval. The daemon's
80
+ // run() already guards each tick and fails closed with a `cw:`
81
+ // line; this .catch is a backstop against any unguarded throw so
82
+ // it can never surface as an unhandled-rejection stack dump.
83
+ void registryCli.scheduleDaemonRunForever(args.options).catch((error) => {
84
+ process.stderr.write(`cw: ${error instanceof Error ? error.message : String(error)}\n`);
85
+ process.exitCode = 1;
86
+ });
81
87
  return {};
82
88
  }
83
89
  default:
@@ -18,10 +18,13 @@ const cli_args_1 = require("../../core/util/cli-args");
18
18
  // cli/parseargv.ts) even though dispatchTable now handles it as a real
19
19
  // row — a known, preserved wart.
20
20
  // ---------------------------------------------------------------------
21
- const ledger_cli_1 = require("../../shell/ledger-cli");
22
21
  // This file is required at startup for every command. Loading these shell
23
22
  // modules only when their handler runs, not at import time, keeps that
24
- // load cost out of commands that never touch telemetry/audit/demo/report.
23
+ // load cost out of commands that never touch ledger/telemetry/audit/demo/
24
+ // report.
25
+ function loadLedgerCli() {
26
+ return require("../../shell/ledger-cli");
27
+ }
25
28
  function loadTelemetryCli() {
26
29
  return require("../../shell/telemetry-cli");
27
30
  }
@@ -40,42 +43,81 @@ function loadReportCli() {
40
43
  (0, registry_core_1.attachCliBinding)("ledger.propose", {
41
44
  path: ["ledger", "propose"],
42
45
  jsonMode: "default",
43
- handler: (args) => ({ json: (0, ledger_cli_1.ledgerProposeCli)(args.options) }),
46
+ handler: (args) => ({ json: loadLedgerCli().ledgerProposeCli(args.options) }),
47
+ // UI/UX fix: `cw help ledger` used to list only the one-line summary for
48
+ // each row, so a first-run user had no way to learn these flags without
49
+ // reading source. Real flag names verified against ledgerProposeCli's
50
+ // required()/stringOption() calls above.
51
+ flags: [
52
+ { name: "--from AGENT/REPO", summary: "Who is making the change." },
53
+ { name: "--to AGENT/REPO", summary: "Who should get the change." },
54
+ { name: "--title TEXT", summary: "A short name for the change." },
55
+ { name: "--rationale TEXT", summary: "Why the change should happen." },
56
+ { name: "--files LIST", summary: "The files the change touches, as a comma list." },
57
+ { name: "--diff PATCH", summary: "The change as one diff file." },
58
+ ],
59
+ // ledgerProposeCli reads exactly from/to/title/rationale/files/diff
60
+ // (shell/ledger-cli.ts) — the list above is complete, so the dispatcher
61
+ // may warn about an unknown flag (TTY-only; see cli/global-flags.ts).
62
+ flagsComplete: true,
44
63
  });
45
- registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.propose").mcp.handler = (args) => (0, ledger_cli_1.ledgerProposeMcp)(args);
64
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.propose").mcp.handler = (args) => loadLedgerCli().ledgerProposeMcp(args);
46
65
  (0, registry_core_1.attachCliBinding)("ledger.review", {
47
66
  path: ["ledger", "review"],
48
67
  jsonMode: "default",
49
- handler: (args) => ({ json: (0, ledger_cli_1.ledgerReviewCli)(args.options) }),
68
+ handler: (args) => ({ json: loadLedgerCli().ledgerReviewCli(args.options) }),
69
+ // Real flag names verified against ledgerReviewCli's required()/
70
+ // stringOption() calls above — NOT the same flag set as ledger propose
71
+ // (this row needs --target/--verdict/--findings, not --title/
72
+ // --rationale/--diff).
73
+ flags: [
74
+ { name: "--from AGENT/REPO", summary: "Who is doing the review." },
75
+ { name: "--to AGENT/REPO", summary: "Who made the change being reviewed." },
76
+ { name: "--target ID", summary: "The id of the proposal or PR to review." },
77
+ { name: "--verdict approved|rejected", summary: "The result of the review." },
78
+ { name: "--findings TEXT", summary: "Notes on what the review found." },
79
+ ],
80
+ // ledgerReviewCli reads exactly from/to/target/verdict/findings
81
+ // (shell/ledger-cli.ts) — the list above is complete, so the dispatcher
82
+ // may warn about an unknown flag (TTY-only; see cli/global-flags.ts).
83
+ flagsComplete: true,
50
84
  });
51
- registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.review").mcp.handler = (args) => (0, ledger_cli_1.ledgerReviewMcp)(args);
85
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.review").mcp.handler = (args) => loadLedgerCli().ledgerReviewMcp(args);
52
86
  (0, registry_core_1.attachCliBinding)("ledger.verify", {
53
87
  path: ["ledger", "verify"],
54
88
  jsonMode: "default",
55
89
  handler: (args) => {
56
- const result = (0, ledger_cli_1.ledgerVerifyCli)(args.options);
90
+ const result = loadLedgerCli().ledgerVerifyCli(args.options);
57
91
  return { json: result, exitCode: result.ok ? undefined : 1 };
58
92
  },
59
93
  });
60
- registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.verify").mcp.handler = (args) => (0, ledger_cli_1.ledgerVerifyEntry)(args.entry);
94
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.verify").mcp.handler = (args) => loadLedgerCli().ledgerVerifyEntry(args.entry);
61
95
  (0, registry_core_1.attachCliBinding)("ledger.apply", {
62
96
  path: ["ledger", "apply"],
63
97
  jsonMode: "default",
64
98
  handler: (args) => {
65
- const result = (0, ledger_cli_1.ledgerApplyCli)(args.options);
99
+ const result = loadLedgerCli().ledgerApplyCli(args.options);
66
100
  return { json: result, exitCode: result.ok ? undefined : 1 };
67
101
  },
68
102
  });
69
- registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.apply").mcp.handler = (args) => (0, ledger_cli_1.ledgerApplyEntry)(args.entry);
103
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.apply").mcp.handler = (args) => loadLedgerCli().ledgerApplyEntry(args.entry);
70
104
  (0, registry_core_1.attachCliBinding)("ledger.list", {
71
105
  path: ["ledger", "list"],
72
106
  jsonMode: "default",
73
107
  handler: (args) => {
74
- const result = (0, ledger_cli_1.ledgerListCli)(args.options);
108
+ const result = loadLedgerCli().ledgerListCli(args.options);
75
109
  return { json: result, exitCode: result.allOk ? undefined : 1 };
76
110
  },
111
+ // UI/UX fix: `--dir` here named the ledger directory, while the global
112
+ // front door (cli/entry.ts) treats `--dir` as an alias of `--repo` for
113
+ // every command — one flag, two meanings. `--ledger-dir` is the
114
+ // unambiguous spelling; `--dir` stays as the legacy alias, byte-compat.
115
+ flags: [
116
+ { name: "--ledger-dir DIR", summary: "The ledger directory to read. Give it more than once to union-verify mirrors." },
117
+ { name: "--dir DIR", summary: "Old name for --ledger-dir (also the global --repo alias; kept working)." },
118
+ ],
77
119
  });
78
- registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.list").mcp.handler = (args) => (0, ledger_cli_1.ledgerListMcp)(args);
120
+ registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.list").mcp.handler = (args) => loadLedgerCli().ledgerListMcp(args);
79
121
  (0, registry_core_1.attachCliBinding)("telemetry.verify", {
80
122
  path: ["telemetry", "verify"],
81
123
  jsonMode: "flag",
@@ -125,6 +125,19 @@ exactly one fixed step (injected `now`); bare `--drive` runs to the end or to a
125
125
  parked/blocked stop. `run drive <run-id>` (no `--step`) is the read-only, fixed
126
126
  preview of the next step.
127
127
 
128
+ ## Concurrent rounds (`--concurrency N`)
129
+
130
+ `--drive --concurrency N` (or an auto-width parallel phase) runs a whole round's
131
+ tasks in ONE window instead of one at a time. CW dispatches the round's tasks,
132
+ collects every agent outcome concurrently, then settles + accepts them in
133
+ DETERMINISTIC batch (task-id) order — so the wall-clock drops but the recorded
134
+ state, reports, and exit codes are byte-identical to a serial run. Both agent
135
+ shapes get real concurrency: a **CLI-binary** agent (`CW_AGENT_COMMAND`) runs
136
+ all children at once through the batch delegate child, and an **HTTP-endpoint**
137
+ agent (`CW_AGENT_ENDPOINT`) POSTs all N delegations at once through the HTTP
138
+ batch delegate child. A cache-hit task still settles on the serial path inside
139
+ the same round. An unconfigured agent still refuses (it is never a fake pass).
140
+
128
141
  ## Fail closed — probe vs refusal vs park
129
142
 
130
143
  - **Probe.** `backend probe agent` reports `readiness: "ready"` iff a
@@ -423,3 +436,5 @@ The one-command `cw -q` headline now routes the question and defaults the repo t
423
436
  0.2.3
424
437
 
425
438
  0.2.4
439
+
440
+ 0.2.5
@@ -74,6 +74,29 @@ show/validate/apply/summary/graph, sandbox show/validate/choose/resolve, state
74
74
  summary refresh/show, `plan`, `approve`, `reject`, `comment.add`, `handoff`, and
75
75
  `review.policy`.
76
76
 
77
+ ## MCP Tool Hints and Version Negotiation
78
+
79
+ A `tools/list` entry may carry the standard MCP `annotations` field with
80
+ two behavior hints:
81
+
82
+ - `readOnlyHint: true` — the tool only reads; it makes no change on disk.
83
+ - `destructiveHint: true` (with `readOnlyHint: false`) — the tool deletes
84
+ stored data (the gc / delete sweeps: `cw_gc_run`, `cw_orphans_gc`,
85
+ `cw_clones_gc`, `cw_schedule_delete`, `cw_routine_delete`).
86
+
87
+ The hints come from one side table (`MCP_TOOL_ANNOTATIONS` in
88
+ `src/core/capability-data.ts`), and a tool goes in only after its handler
89
+ was read and checked by hand — a wrong "read-only" mark on a writing tool
90
+ would be a safety bug, so a tool we are not certain about carries no
91
+ `annotations` field at all. Absent beats wrong. The field is additive: a
92
+ client that does not know it simply does not read it.
93
+
94
+ The MCP `initialize` reply also negotiates the protocol version now: a
95
+ client that asks for a supported version gets that version echoed back,
96
+ and any other request falls back to the newest supported entry (see
97
+ `SUPPORTED_PROTOCOL_VERSIONS` in `src/mcp/server.ts`). With today's
98
+ one-entry list the reply bytes are unchanged.
99
+
77
100
  ## The Parity Matrix
78
101
 
79
102
  The matrix below is made from the live registry — one row per capability,
@@ -643,3 +666,5 @@ CLI golden-path fixes: `cw -q "…"` routes the question (was read as an app id
643
666
  0.2.3
644
667
 
645
668
  0.2.4
669
+
670
+ 0.2.5
@@ -183,3 +183,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
183
183
  0.2.3
184
184
 
185
185
  0.2.4
186
+
187
+ 0.2.5
@@ -66,6 +66,40 @@ Additive: `RunQueueEntry` gets the optional `attempts`/`leaseId`/`leaseExpiresAt
66
66
  `queue add|list|drain|show` verbs that are already there do not change. No new database, no
67
67
  daemon-owned state.
68
68
 
69
+ ## Scheduled Tasks In Practice
70
+
71
+ CW scheduled tasks give looping prompts, cron-like schedules, one-shot
72
+ reminders, expiration, jitter, and clear completion. Schedules live in
73
+ `.cw/schedules/tasks.json`.
74
+
75
+ ```bash
76
+ # a /loop-ready schedule (cw loop is the short form of schedule create --kind loop)
77
+ cw loop --intervalMinutes 30 --prompt "Check this workflow and continue if work is due."
78
+
79
+ # a cron schedule
80
+ cw schedule create --kind cron --cron "*/15 * * * *" --prompt "Run the due workflow scan."
81
+
82
+ # a one-shot reminder
83
+ cw schedule create --kind reminder --delayMinutes 60 --prompt "Remind me to inspect the report."
84
+
85
+ # list, inspect, and manage
86
+ cw schedule list
87
+ cw schedule due
88
+ cw schedule complete <schedule-id>
89
+ cw schedule pause <schedule-id> / resume <schedule-id> / run-now <schedule-id>
90
+ cw schedule history <schedule-id>
91
+ cw schedule delete <schedule-id>
92
+
93
+ # the local daemon: one sweep, or a loop
94
+ cw schedule daemon --once
95
+ cw schedule daemon --intervalSeconds 60
96
+ ```
97
+
98
+ Notes: time is measured to the minute; expiration defaults to 7 days;
99
+ `jitterSeconds` can put space between runs. CW does not start the daemon by
100
+ default — use `schedule daemon`, cron, or another overseer to call
101
+ `schedule due` and run due prompts.
102
+
69
103
  ## See Also
70
104
 
71
105
  run-registry-control-plane(7), cli-mcp-parity(7), release-and-migration(7)
@@ -167,3 +201,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
167
201
  0.2.3
168
202
 
169
203
  0.2.4
204
+
205
+ 0.2.5
@@ -3,8 +3,15 @@
3
3
  CW adds `cw ledger` — a way for two agents scoped to two separate repos to hand
4
4
  each other a CHANGE PROPOSAL or a REVIEW VERDICT as verifiable data, not chat.
5
5
  One side proposes or reviews; the other side verifies the entry fail-closed and
6
- turns a proposal into a real pull request. Design notes:
7
- [handoff-ledger](designs/handoff-ledger.md).
6
+ turns a proposal into a real pull request.
7
+
8
+ Design note (why not a shared folder): the obvious first design — a shared
9
+ local directory both agents read and append to — only works when both agents
10
+ run on ONE machine with ONE filesystem. Two agents scoped to two repos (or two
11
+ machines) need a transport both can already reach, with saved, inspectable,
12
+ fail-closed state — never a fabricated hand-off. That is why an entry is a
13
+ self-verifying file in a git repo both sides can push and pull, and why the
14
+ kernel holds no git logic at all.
8
15
 
9
16
  Each entry is a self-contained JSON object that carries its own sha256 content
10
17
  digest. The producing side prints one; it reaches the other session by human
@@ -51,13 +58,18 @@ cw ledger review --from <a> --to <b> --target <proposal-id|pr-ref> \
51
58
  --verdict <approved|rejected> [--findings "a,b"]
52
59
  cw ledger verify [--file <path>] # else reads the entry from stdin
53
60
  cw ledger apply [--file <path>] # verify a proposal, then print its diff
54
- cw ledger list --dir <ledger-dir> [--dir <mirror-2> ...] # verify a dir (or union of mirrors)
61
+ cw ledger list --ledger-dir <ledger-dir> [--ledger-dir <mirror-2> ...] # verify a dir (or union of mirrors)
55
62
  ```
56
63
 
57
64
  All write JSON to stdout (stdout is data). `propose` and `review` print a sealed
58
65
  entry; `verify` prints a check report; `apply` prints a verify-plus-diff report;
59
66
  `list` prints a per-entry report over a directory.
60
67
 
68
+ `--ledger-dir` is the preferred spelling for `list`: the CLI front door
69
+ treats `--dir` as a global alias of `--repo` on every command, so the old
70
+ `--dir` made one flag mean two things here. `--dir` keeps working
71
+ unchanged as the legacy alias; when both are given, `--ledger-dir` wins.
72
+
61
73
  ## Applying a proposal — fail-closed
62
74
 
63
75
  A proposal carries a `suggestedDiff`, but a proposal never mutates the target
@@ -131,12 +143,12 @@ on the mirror-union output and on the `cw_ledger_list` MCP tool.
131
143
 
132
144
  ### Mirrors — union-verifying several directories
133
145
 
134
- `--dir` is repeatable. With two or more, `cw ledger list` **union-verifies** the
146
+ `--ledger-dir` (and the legacy `--dir`) is repeatable. With two or more, `cw ledger list` **union-verifies** the
135
147
  directories as mirrors of one ledger (e.g. the same handoff repo cloned from a
136
148
  GitHub remote and one or more self-hosted Gitea remotes in different places):
137
149
 
138
150
  ```
139
- cw ledger list --dir gh/ledger --dir gitea-eu/ledger --dir gitea-asia/ledger
151
+ cw ledger list --ledger-dir gh/ledger --ledger-dir gitea-eu/ledger --ledger-dir gitea-asia/ledger
140
152
  ```
141
153
 
142
154
  The union is **conflict-free by construction**: entries are immutable and
@@ -146,10 +158,10 @@ across mirrors** — a tampered entry in ANY mirror sets `allOk:false` and exits
146
158
  `1`. This is for redundancy and reachability, not load: the ledger's traffic is
147
159
  tiny; multiple hosts guard against one being down or unreachable.
148
160
 
149
- A single `--dir` keeps the original single-directory output (a `dir` field, no
161
+ A single directory flag keeps the original single-directory output (a `dir` field, no
150
162
  `dirs`); two or more switch to the union shape (`dirs` plus a per-entry `dirs`).
151
163
  The transport stays git-host-agnostic — adding a mirror is one more clone + one
152
- more `--dir`, no code change.
164
+ more `--ledger-dir`, no code change.
153
165
 
154
166
  ## Entry shape
155
167
 
@@ -214,4 +226,4 @@ cw ledger verify --file verdict.json
214
226
  Stage 1 shipped the CLI verbs (human relay). Stage 2 adds the MCP surface and
215
227
  the git-as-ledger transport (`cw ledger list` over a shared repo). Still open:
216
228
  the operator creates the shared handoff repo and scopes both agent environments
217
- into it. See [handoff-ledger](designs/handoff-ledger.md).
229
+ into it. Setup runbook: [handoff-setup](handoff-setup.md).
@@ -186,3 +186,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
186
186
  0.2.3
187
187
 
188
188
  0.2.4
189
+
190
+ 0.2.5
@@ -327,3 +327,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
327
327
  0.2.3
328
328
 
329
329
  0.2.4
330
+
331
+ 0.2.5
@@ -357,3 +357,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
357
357
  0.2.3
358
358
 
359
359
  0.2.4
360
+
361
+ 0.2.5
package/docs/fix.7.md CHANGED
@@ -8,7 +8,6 @@
8
8
 
9
9
  ```text
10
10
  node dist/cli.js fix
11
- node dist/cli.js fix --json
12
11
  ```
13
12
 
14
13
  ## DESCRIPTION
@@ -28,9 +27,10 @@ If any check has status `fail`, the command exits with code 1.
28
27
 
29
28
  ## OPTIONS
30
29
 
31
- `--json`
32
- : Give back the full doctor report as a stable JSON object, with the same shape
33
- as `cw doctor --json`. The `checks` array carries every fix string.
30
+ `cw fix` takes no options of its own; its output is always the plain fix-command
31
+ text (a `--json` flag is silently ignored this byte behavior is pinned by
32
+ `v2/conformance/cases/report-fix.case.js`). For a machine-readable report of the
33
+ same checks, use `cw doctor --json`: its `checks` array carries every fix string.
34
34
 
35
35
  ## EXIT CODES
36
36