🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

cool-workflow

Package Overview
Dependencies
Maintainers
1
Versions
28
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cool-workflow - npm Package Compare versions

Comparing version
0.2.1
to
0.2.2
+270
dist/core/capability-data.js
"use strict";
// core/capability-data.ts — THE one data table's pure literal data: types,
// the MCP_TOOL_DATA transcript, and the handful of dependency-free helpers
// that only touch that data.
//
// Split out of core/capability-table.ts (which still wires this data into
// the REGISTRY, attaches CLI bindings, and holds the real handler bodies —
// all of which import from ../shell/ and stay there). This file has ZERO
// imports of its own: no reference-site changes anywhere else, since
// capability-table.ts imports everything below and re-exports the types
// external files already import from it.
//
// MILESTONE 2 (docs/rebuild/PLAN.md build order, step 2). Replaces the old build's
// `capability-registry.ts` (940 lines) + 40 CLI handler files + the
// 196-arm MCP switch + the 1000-line tool-definitions array with ONE data
// table plus two generic front-door readers (`cli/dispatch.ts`,
// `mcp/dispatch.ts`).
//
// Byte-compat item 5 (docs/rebuild/PLAN.md): 12 capabilities have CLI and MCP call
// DIFFERENT functions with DIFFERENT payloads. So a row carries a SEPARATE
// `cli.handler` and `mcp.handler`, never one shared "handler" — even
// though every row landed so far happens to share one function, the type
// itself must not collapse the two fields into one, or a later milestone
// would need a breaking shape change to add the first real divergent row.
//
// `MCP_TOOL_DATA` below is the full, literal 196-tool surface transcribed
// from SPEC/mcp.md's "All 196 MCP tools" table (name, capability id,
// required-argument groups, input property names, description) — this is
// exactly the kind of declarative surface data this table exists to hold.
// Every tool's `mcp.handler` not listed in capability-table.ts's
// MCP_REAL_HANDLERS side table is `notYetImplemented(capability)`, which
// throws a clean, typed error if ever actually called. A NEW post-rebuild
// capability is APPENDED after the transcript's last row (never inserted),
// so every existing position keeps its pinned order.
Object.defineProperty(exports, "__esModule", { value: true });
exports.MCP_TOOL_DATA = exports.PROPERTY_OVERRIDES = exports.CapabilityNotImplementedError = void 0;
exports.notYetImplemented = notYetImplemented;
exports.stringProperty = stringProperty;
/** Thrown by every not-yet-wired MCP tool handler. Never hit by this
* milestone's conformance filter — every tool mcp-basic.case.js actually
* calls (`cw_list`, `cw_sandbox_list`) has a real handler below. */
class CapabilityNotImplementedError extends Error {
constructor(capability) {
super(`${capability} is not implemented in this milestone`);
this.name = "CapabilityNotImplementedError";
}
}
exports.CapabilityNotImplementedError = CapabilityNotImplementedError;
function notYetImplemented(capability) {
return () => {
throw new CapabilityNotImplementedError(capability);
};
}
function stringProperty(name) {
return { type: "string", description: name };
}
/** SPEC/mcp.md's two hand-written property-shape exceptions (see that
* file's "All 196 MCP tools" section header note): every OTHER property
* on every OTHER tool is the plain string form above. */
exports.PROPERTY_OVERRIDES = {
cw_commit: {
allowUnverifiedCheckpoint: {
type: "boolean",
description: "Write a non-gated checkpoint instead of committed state",
},
},
cw_routine_fire: {
payload: { type: "object", description: "Event payload" },
},
};
exports.MCP_TOOL_DATA = [
{ tool: "cw_list", capability: "list", requiredArgs: [], properties: [], description: "List bundled CW workflows." },
{ tool: "cw_plan", capability: "plan", requiredArgs: ["workflowId"], properties: ["workflowId", "repo", "question"], description: "Create a CW run and return its canonical plan summary." },
{ tool: "cw_app_run", capability: "app.run", requiredArgs: ["appId"], properties: ["cwd", "appId", "inputs", "sandbox", "sandboxProfile", "sandboxProfileId"], description: "Create a run from an app id + structured inputs." },
{ tool: "cw_status", capability: "status", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read run checkpoint status." },
{ tool: "cw_init", capability: "init", requiredArgs: ["workflowId"], properties: ["workflowId", "title", "output"], description: "Scaffold a new workflow definition." },
{ tool: "cw_next", capability: "next", requiredArgs: ["runId"], properties: ["runId", "cwd", "limit"], description: "Read the next recommended tasks for a run." },
{ tool: "cw_state_check", capability: "state.check", requiredArgs: ["runId"], properties: ["runId", "cwd", "state", "write"], description: "Check run-state schema compatibility." },
{ tool: "cw_contract_show", capability: "contract.show", requiredArgs: ["runId"], properties: ["runId", "cwd", "contractId"], description: "Show a run's pipeline contract." },
{ tool: "cw_node_list", capability: "node.list", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "List state nodes for a run." },
{ tool: "cw_node_show", capability: "node.show", requiredArgs: ["runId, nodeId"], properties: ["runId", "cwd", "nodeId"], description: "Show one state node for a run." },
{ tool: "cw_node_graph", capability: "node.graph", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read the state-node graph for a run." },
{ tool: "cw_node_snapshot", capability: "node.snapshot", requiredArgs: [], properties: ["runId", "cwd", "nodeId"], description: "Snapshot one state node (derived + fingerprinted)." },
{ tool: "cw_node_diff", capability: "node.diff", requiredArgs: [], properties: ["runId", "cwd", "baselineSnapshotId", "candidateSnapshotId"], description: "Structurally diff two node snapshots." },
{ tool: "cw_node_replay", capability: "node.replay", requiredArgs: [], properties: ["runId", "cwd", "snapshotId"], description: "Deterministically replay one node from a snapshot." },
{ tool: "cw_node_replay_verify", capability: "node.replay.verify", requiredArgs: [], properties: ["runId", "cwd", "replayId"], description: "Verify a node replay against its source." },
{ tool: "cw_migration_list", capability: "migration.list", requiredArgs: [], properties: [], description: "List the declared migration registry." },
{ tool: "cw_migration_check", capability: "migration.check", requiredArgs: [], properties: ["target", "contract", "cwd"], description: "Dry-run migration verdict for a target." },
{ tool: "cw_migration_prove", capability: "migration.prove", requiredArgs: [], properties: ["target", "contract", "cwd"], description: "Round-trip / non-destruction migration proof for a target." },
{ tool: "cw_operator_status", capability: "operator.status", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read the structured Operator UX run status." },
{ tool: "cw_operator_graph", capability: "graph", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read the structured Operator UX run graph." },
{ tool: "cw_operator_report", capability: "operator.report", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Refresh and read the structured Operator UX report summary." },
{ tool: "cw_worker_summary", capability: "worker.summary", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read the structured worker summary for a run." },
{ tool: "cw_workbench_view", capability: "workbench.view", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read the read-only five-panel Workbench view of one run (graph, blackboard, worker, candidate, audit)." },
{ tool: "cw_workbench_serve", capability: "workbench.serve", requiredArgs: [], properties: ["cwd", "port", "scope", "requireToken"], description: "Describe/serve the optional localhost-only, read-only Workbench host. requireToken is a CLI-only opt-in (the MCP path never actually binds, so it is a no-op here)." },
{ tool: "cw_candidate_summary", capability: "candidate.summary", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read the structured candidate summary for a run." },
{ tool: "cw_feedback_summary", capability: "feedback.summary", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read the structured feedback summary for a run." },
{ tool: "cw_commit_summary", capability: "commit.summary", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read the structured commit summary for a run." },
{ tool: "cw_multi_agent_summary", capability: "multi-agent.summary", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read the structured multi-agent runtime summary for a run." },
{ tool: "cw_multi_agent_graph", capability: "multi-agent.graph", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read the structured multi-agent operator graph for a run." },
{ tool: "cw_multi_agent_dependencies", capability: "multi-agent.dependencies", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read derived multi-agent dependency edges for operator inspection." },
{ tool: "cw_multi_agent_failures", capability: "multi-agent.failures", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read failed, blocked, rejected, and ambiguous multi-agent records." },
{ tool: "cw_multi_agent_evidence", capability: "multi-agent.evidence", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read evidence adoption status from worker output through selection and commit. Each row carries a derived rationaleStatus (explained|unexplained|not-applicable)." },
{ tool: "cw_evidence_reasoning", capability: "multi-agent.reasoning", requiredArgs: ["runId"], properties: ["runId", "cwd", "evidence", "refresh"], description: "Explain why each evidence item was adopted/rejected." },
{ tool: "cw_evidence_reasoning_refresh", capability: "multi-agent.reasoning.refresh", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Refresh the durable evidence-reasoning index." },
{ tool: "cw_summary_refresh", capability: "summary.refresh", requiredArgs: ["runId"], properties: ["runId", "cwd", "view"], description: "Refresh state-explosion summaries." },
{ tool: "cw_summary_show", capability: "summary.show", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read the persisted state-explosion report." },
{ tool: "cw_blackboard_summarize", capability: "blackboard.summarize", requiredArgs: ["runId"], properties: ["runId", "cwd", "blackboardId"], description: "Read a blackboard digest with conflicts/evidence." },
{ tool: "cw_multi_agent_summarize", capability: "multi-agent.summarize", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read the combined state-explosion report." },
{ tool: "cw_multi_agent_graph_compact", capability: "multi-agent.graph.compact", requiredArgs: ["runId"], properties: ["runId", "cwd", "view", "focus", "depth"], description: "Read a compact/focused multi-agent graph view." },
{ tool: "cw_multi_agent_run", capability: "multi-agent.run", requiredArgs: [], properties: ["runId", "cwd", "app", "appId", "workflow", "workflowId", "topology", "topologyId", "task", "mapperCount", "judgeCount", "debateRounds"], description: "Create or attach a topology-backed multi-agent run." },
{ tool: "cw_multi_agent_status", capability: "multi-agent.status", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read combined topology/blackboard/worker status." },
{ tool: "cw_multi_agent_step", capability: "multi-agent.step", requiredArgs: ["runId"], properties: ["runId", "cwd", "sandbox", "backend", "limit"], description: "Perform one safe deterministic host step." },
{ tool: "cw_multi_agent_blackboard", capability: "multi-agent.blackboard", requiredArgs: ["runId"], properties: ["runId", "cwd", "action", "blackboardId", "topicId", "body", "kind", "path", "evidence"], description: "Operate on the active multi-agent blackboard." },
{ tool: "cw_multi_agent_score", capability: "multi-agent.score", requiredArgs: ["runId"], properties: ["runId", "cwd", "candidate", "candidateId", "worker", "criterion", "criteria", "evidence", "maxTotal"], description: "Score a candidate with evidence." },
{ tool: "cw_multi_agent_select", capability: "multi-agent.select", requiredArgs: ["runId"], properties: ["runId", "cwd", "candidate", "candidateId", "score", "scoreId", "reason", "allowUnverified"], description: "Select a candidate with the verifier gate." },
{ tool: "cw_eval_snapshot", capability: "eval.snapshot", requiredArgs: ["runId"], properties: ["runId", "cwd", "id"], description: "Create a deterministic replay snapshot." },
{ tool: "cw_eval_replay", capability: "eval.replay", requiredArgs: ["snapshot|snapshotId|path"], properties: ["cwd", "snapshot", "snapshotId", "path", "id"], description: "Replay a snapshot without live agents." },
{ tool: "cw_eval_compare", capability: "eval.compare", requiredArgs: ["baseline|baselinePath, replay|replayPath"], properties: ["cwd", "baseline", "baselinePath", "replay", "replayPath"], description: "Compare baseline and replay deterministically." },
{ tool: "cw_eval_score", capability: "eval.score", requiredArgs: ["replay|replayPath|path"], properties: ["cwd", "replay", "replayPath", "path"], description: "Score replay quality." },
{ tool: "cw_eval_gate", capability: "eval.gate", requiredArgs: ["suite|suiteId|path"], properties: ["cwd", "suite", "suiteId", "path"], description: "Run the eval/replay regression gate." },
{ tool: "cw_eval_report", capability: "eval.report", requiredArgs: ["replay|replayPath|path"], properties: ["cwd", "replay", "replayPath", "path"], description: "Render an eval/replay report." },
{ tool: "cw_multi_agent_run_create", capability: "multi-agent.run.create", requiredArgs: ["runId"], properties: ["runId", "cwd", "id", "title", "objective"], description: "Create a MultiAgentRun state record." },
{ tool: "cw_multi_agent_run_transition", capability: "multi-agent.run.transition", requiredArgs: ["runId"], properties: ["runId", "cwd", "multiAgentRunId", "id", "status", "reason"], description: "Transition a MultiAgentRun lifecycle." },
{ tool: "cw_multi_agent_run_show", capability: "multi-agent.run.show", requiredArgs: ["runId"], properties: ["runId", "cwd", "multiAgentRunId", "id"], description: "Show one MultiAgentRun record." },
{ tool: "cw_multi_agent_role_create", capability: "multi-agent.role.create", requiredArgs: ["runId"], properties: ["runId", "cwd", "id", "multiAgentRunId", "multiAgentRun", "title", "responsibility", "requiredEvidence", "sandboxProfileHint", "expectedArtifact", "faninObligation"], description: "Create an AgentRole record." },
{ tool: "cw_multi_agent_role_show", capability: "multi-agent.role.show", requiredArgs: ["runId, roleId"], properties: ["runId", "cwd", "roleId", "id"], description: "Show one AgentRole record." },
{ tool: "cw_multi_agent_group_create", capability: "multi-agent.group.create", requiredArgs: ["runId"], properties: ["runId", "cwd", "id", "multiAgentRunId", "multiAgentRun", "title", "phase", "task"], description: "Create an AgentGroup record." },
{ tool: "cw_multi_agent_group_show", capability: "multi-agent.group.show", requiredArgs: ["runId, groupId"], properties: ["runId", "cwd", "groupId", "id"], description: "Show one AgentGroup record." },
{ tool: "cw_multi_agent_membership_create", capability: "multi-agent.membership.create", requiredArgs: ["runId"], properties: ["runId", "cwd", "id", "groupId", "roleId", "taskId", "workerId", "dispatchId", "fanoutId"], description: "Create an AgentMembership record." },
{ tool: "cw_multi_agent_membership_show", capability: "multi-agent.membership.show", requiredArgs: ["runId, membershipId"], properties: ["runId", "cwd", "membershipId", "id"], description: "Show one AgentMembership record." },
{ tool: "cw_multi_agent_fanout_create", capability: "multi-agent.fanout.create", requiredArgs: ["runId"], properties: ["runId", "cwd", "id", "groupId", "reason", "role", "task", "limit", "sandboxChoice"], description: "Create an AgentFanout record." },
{ tool: "cw_multi_agent_fanout_show", capability: "multi-agent.fanout.show", requiredArgs: ["runId, fanoutId"], properties: ["runId", "cwd", "fanoutId", "id"], description: "Show one AgentFanout record." },
{ tool: "cw_multi_agent_fanin_collect", capability: "multi-agent.fanin.collect", requiredArgs: ["runId"], properties: ["runId", "cwd", "id", "groupId", "fanoutId", "requiredRole", "strategy"], description: "Collect an AgentFanin with evidence coverage." },
{ tool: "cw_multi_agent_fanin_show", capability: "multi-agent.fanin.show", requiredArgs: ["runId, faninId"], properties: ["runId", "cwd", "faninId", "id"], description: "Show one AgentFanin record." },
{ tool: "cw_topology_list", capability: "topology.list", requiredArgs: [], properties: [], description: "List official topology definitions." },
{ tool: "cw_topology_show", capability: "topology.show", requiredArgs: ["topologyId|id"], properties: ["runId", "cwd", "topologyId", "topologyRunId", "id"], description: "Show a topology definition or run." },
{ tool: "cw_topology_validate", capability: "topology.validate", requiredArgs: ["topologyId|id"], properties: ["topologyId", "id"], description: "Validate a topology definition." },
{ tool: "cw_topology_apply", capability: "topology.apply", requiredArgs: ["runId, topologyId|id"], properties: ["runId", "cwd", "topologyId", "id", "task", "mapperCount", "judgeCount", "debateRounds", "blackboardId", "multiAgentRunId", "collectInitialFanin"], description: "Apply a topology to a run." },
{ tool: "cw_topology_summary", capability: "topology.summary", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read topology progress and next actions." },
{ tool: "cw_topology_graph", capability: "topology.graph", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read topology graph nodes and edges." },
{ tool: "cw_blackboard_summary", capability: "blackboard.summary", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read the blackboard/coordinator summary." },
{ tool: "cw_blackboard_graph", capability: "blackboard.graph", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read blackboard graph nodes and edges." },
{ tool: "cw_blackboard_resolve", capability: "blackboard.resolve", requiredArgs: ["runId"], properties: ["runId", "cwd", "id", "title", "multiAgentRunId", "groupId", "roleId", "membershipId"], description: "Create or resolve a run blackboard." },
{ tool: "cw_blackboard_topic_create", capability: "blackboard.topic.create", requiredArgs: ["runId"], properties: ["runId", "cwd", "id", "title", "description", "blackboardId", "tag"], description: "Create a blackboard topic." },
{ tool: "cw_blackboard_message_post", capability: "blackboard.message.post", requiredArgs: ["runId"], properties: ["runId", "cwd", "id", "topic", "topicId", "body", "replyTo", "visibility", "evidence", "artifact"], description: "Post a blackboard message." },
{ tool: "cw_blackboard_message_list", capability: "blackboard.message.list", requiredArgs: ["runId"], properties: ["runId", "cwd", "topic", "topicId", "blackboardId"], description: "List blackboard messages." },
{ tool: "cw_blackboard_context_put", capability: "blackboard.context.put", requiredArgs: ["runId"], properties: ["runId", "cwd", "id", "topic", "topicId", "kind", "key", "value", "supersedes", "evidence", "artifact"], description: "Publish a shared context frame." },
{ tool: "cw_blackboard_artifact_add", capability: "blackboard.artifact.add", requiredArgs: ["runId"], properties: ["runId", "cwd", "id", "topic", "kind", "path", "locator", "source", "evidence"], description: "Index an artifact in the blackboard." },
{ tool: "cw_blackboard_artifact_list", capability: "blackboard.artifact.list", requiredArgs: ["runId"], properties: ["runId", "cwd", "topic", "blackboardId"], description: "List blackboard artifact refs." },
{ tool: "cw_blackboard_snapshot", capability: "blackboard.snapshot", requiredArgs: ["runId"], properties: ["runId", "cwd", "blackboardId"], description: "Create a durable blackboard snapshot." },
{ tool: "cw_coordinator_summary", capability: "coordinator.summary", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read the coordinator summary." },
{ tool: "cw_coordinator_decision", capability: "coordinator.decision", requiredArgs: ["runId"], properties: ["runId", "cwd", "id", "kind", "outcome", "reason", "subject", "evidence", "artifact", "message"], description: "Record a coordinator decision." },
{ tool: "cw_audit_summary", capability: "audit.summary", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read the trust/audit summary." },
{ tool: "cw_audit_verify", capability: "audit.verify", requiredArgs: ["runId"], properties: ["runId", "cwd", "expectHead", "expectCount"], description: "Re-prove a run's trust-audit hash chain (fail-closed exit)." },
{ tool: "cw_audit_worker", capability: "audit.worker", requiredArgs: ["runId"], properties: ["runId", "cwd", "workerId"], description: "Read trust/audit for one worker." },
{ tool: "cw_audit_provenance", capability: "audit.provenance", requiredArgs: ["runId"], properties: ["runId", "cwd", "workerId", "worker", "candidateId", "candidate", "commitId", "commit"], description: "Inspect evidence provenance." },
{ tool: "cw_audit_multi_agent", capability: "audit.multi-agent", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read the multi-agent trust/policy/provenance audit." },
{ tool: "cw_audit_policy", capability: "audit.policy", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read role policies and permission decisions." },
{ tool: "cw_audit_role", capability: "audit.role", requiredArgs: ["runId"], properties: ["runId", "cwd", "roleId", "id"], description: "Read policy/audit for one role." },
{ tool: "cw_audit_blackboard", capability: "audit.blackboard", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read the blackboard write audit." },
{ tool: "cw_audit_judge", capability: "audit.judge", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read judge rationale/panel decision audit." },
{ tool: "cw_audit_attest", capability: "audit.attest", requiredArgs: ["runId"], properties: ["runId", "cwd", "workerId", "worker", "actor", "hostEnforced", "env", "note"], description: "Record a host/operator sandbox attestation." },
{ tool: "cw_audit_decision", capability: "audit.decision", requiredArgs: ["runId"], properties: ["runId", "cwd", "workerId", "path", "command", "network", "env", "kind"], description: "Validate and record a sandbox decision." },
{ tool: "cw_dispatch", capability: "dispatch", requiredArgs: ["runId"], properties: ["runId", "cwd", "limit", "sandbox", "sandboxProfile", "sandboxProfileId", "backend", "backendId"], description: "Create a subagent dispatch manifest." },
{ tool: "cw_sandbox_list", capability: "sandbox.list", requiredArgs: [], properties: ["cwd"], description: "List bundled sandbox profiles." },
{ tool: "cw_sandbox_show", capability: "sandbox.show", requiredArgs: ["profileId"], properties: ["cwd", "profileId"], description: "Show a resolved sandbox profile." },
{ tool: "cw_sandbox_validate", capability: "sandbox.validate", requiredArgs: ["profileFile"], properties: ["cwd", "profileFile"], description: "Validate a sandbox profile JSON file." },
{ tool: "cw_sandbox_choose", capability: "sandbox.choose", requiredArgs: [], properties: ["cwd", "profileId", "sandbox", "sandboxProfile", "sandboxProfileId"], description: "Resolve and validate a sandbox profile choice." },
{ tool: "cw_sandbox_resolve", capability: "sandbox.resolve", requiredArgs: [], properties: ["cwd", "profileId", "sandbox", "sandboxProfile", "sandboxProfileId"], description: "Alias of sandbox.choose." },
{ tool: "cw_backend_list", capability: "backend.list", requiredArgs: [], properties: ["cwd"], description: "List available execution backends and their capabilities." },
{ tool: "cw_backend_show", capability: "backend.show", requiredArgs: [], properties: ["cwd", "backendId"], description: "Show one execution backend descriptor." },
{ tool: "cw_backend_probe", capability: "backend.probe", requiredArgs: [], properties: ["cwd", "backendId"], description: "Probe execution backend readiness (live, deterministic)." },
{ tool: "cw_backend_agent_config_show", capability: "backend.agent.config.show", requiredArgs: [], properties: ["cwd", "agentCommand", "agentEndpoint", "agentModel"], description: "Show the effective agent delegation config (flags>env>file, secret-stripped, host-stable)." },
{ tool: "cw_backend_agent_config_set", capability: "backend.agent.config.set", requiredArgs: [], properties: ["cwd", "agentCommand", "agentEndpoint", "agentModel"], description: "Set the durable agent delegation config (command-template/endpoint/model; API keys never written)." },
{ tool: "cw_result", capability: "result", requiredArgs: ["runId"], properties: ["runId", "taskId", "resultPath", "cwd"], description: "Record a subagent result file against a task." },
{ tool: "cw_commit", capability: "commit", requiredArgs: ["runId"], properties: ["runId", "reason", "verifier", "verifierNode", "candidate", "selection", "allowUnverifiedCheckpoint", "cwd"], description: "Create a verifier-gated commit or checkpoint." },
{ tool: "cw_report", capability: "report", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Render a run report and return its canonical descriptor." },
{ tool: "cw_app_list", capability: "app.list", requiredArgs: [], properties: ["cwd"], description: "List CW workflow apps." },
{ tool: "cw_app_show", capability: "app.show", requiredArgs: [], properties: ["cwd", "appId"], description: "Show a CW workflow app contract." },
{ tool: "cw_app_validate", capability: "app.validate", requiredArgs: [], properties: ["cwd", "target"], description: "Validate an app by path or id." },
{ tool: "cw_app_init", capability: "app.init", requiredArgs: [], properties: ["cwd", "appId", "title", "directory"], description: "Create a CW workflow app directory." },
{ tool: "cw_app_package", capability: "app.package", requiredArgs: [], properties: ["cwd", "appId", "output"], description: "Package an app as a JSON artifact." },
{ tool: "cw_worker_list", capability: "worker.list", requiredArgs: ["runId"], properties: ["runId", "cwd", "status"], description: "List worker isolation scopes." },
{ tool: "cw_worker_show", capability: "worker.show", requiredArgs: ["runId, workerId"], properties: ["runId", "cwd", "workerId"], description: "Show one worker isolation scope." },
{ tool: "cw_worker_manifest", capability: "worker.manifest", requiredArgs: ["runId"], properties: ["runId", "cwd", "workerId"], description: "Write and return a worker manifest." },
{ tool: "cw_worker_output", capability: "worker.output", requiredArgs: ["runId"], properties: ["runId", "cwd", "workerId", "resultPath"], description: "Record worker output." },
{ tool: "cw_worker_fail", capability: "worker.fail", requiredArgs: ["runId"], properties: ["runId", "cwd", "workerId", "message", "code", "path", "retryable"], description: "Record a structured worker failure." },
{ tool: "cw_worker_validate", capability: "worker.validate", requiredArgs: ["runId"], properties: ["runId", "cwd", "workerId", "path", "resultPath"], description: "Validate a worker output boundary." },
{ tool: "cw_candidate_list", capability: "candidate.list", requiredArgs: ["runId"], properties: ["runId", "cwd", "status", "kind"], description: "List candidates for a run." },
{ tool: "cw_candidate_show", capability: "candidate.show", requiredArgs: ["runId, candidateId"], properties: ["runId", "cwd", "candidateId"], description: "Show one candidate." },
{ tool: "cw_candidate_register", capability: "candidate.register", requiredArgs: ["runId"], properties: ["runId", "cwd", "id", "kind", "worker", "task", "resultNode", "verifierNode", "resultPath"], description: "Register a candidate from evidence." },
{ tool: "cw_candidate_score", capability: "candidate.score", requiredArgs: ["runId"], properties: ["runId", "cwd", "candidateId", "criteria", "criterion", "evidence", "maxTotal", "max", "verdict", "notes", "scorer"], description: "Score a candidate with criteria/evidence." },
{ tool: "cw_candidate_rank", capability: "candidate.rank", requiredArgs: ["runId"], properties: ["runId", "cwd", "includeRejected", "minNormalized", "requireEvidence", "requireVerifierGate", "tieBreaker"], description: "Rank candidates with gates." },
{ tool: "cw_candidate_select", capability: "candidate.select", requiredArgs: ["runId"], properties: ["runId", "cwd", "candidateId", "reason", "selectedBy", "by", "score", "allowUnverified", "minNormalized", "requireVerifierGate"], description: "Select a candidate with the verifier gate." },
{ tool: "cw_candidate_reject", capability: "candidate.reject", requiredArgs: ["runId"], properties: ["runId", "cwd", "candidateId", "reason"], description: "Reject a candidate with a reason." },
{ tool: "cw_approve", capability: "approve", requiredArgs: ["runId", "targetKind|kind", "targetId|target"], properties: ["runId", "cwd", "targetKind", "targetId", "actor", "actorKind", "role", "displayName", "attested", "attestation", "rationale", "supersedes"], description: "Append a host-attested approval of a candidate/commit/selection." },
{ tool: "cw_reject", capability: "reject", requiredArgs: ["runId", "targetKind|kind", "targetId|target"], properties: ["runId", "cwd", "targetKind", "targetId", "actor", "actorKind", "role", "displayName", "attested", "attestation", "rationale"], description: "Append a host-attested rejection (blocking veto) of a candidate/commit/selection." },
{ tool: "cw_comment_add", capability: "comment.add", requiredArgs: ["runId", "targetKind|kind", "targetId|target", "body|message|text"], properties: ["runId", "cwd", "targetKind", "targetId", "actor", "actorKind", "role", "displayName", "attested", "attestation", "body", "thread", "parent"], description: "Append a comment to a durable target." },
{ tool: "cw_comment_list", capability: "comment.list", requiredArgs: ["runId"], properties: ["runId", "cwd", "targetKind", "target"], description: "List append-only comments for a run (optionally one target)." },
{ tool: "cw_handoff", capability: "handoff", requiredArgs: ["runId", "targetKind|kind", "targetId|target", "to|toActor"], properties: ["runId", "cwd", "targetKind", "targetId", "actor", "actorKind", "role", "displayName", "attested", "attestation", "to", "toRole", "from", "reason"], description: "Record an ownership transfer (from-actor → to-actor) of a run/task." },
{ tool: "cw_ledger_propose", capability: "ledger.propose", requiredArgs: ["from", "to", "title", "rationale"], properties: ["from", "to", "title", "rationale", "files", "diff"], description: "Build a verifiable cross-agent change proposal entry (printed as JSON)." },
{ tool: "cw_ledger_review", capability: "ledger.review", requiredArgs: ["from", "to", "target", "verdict"], properties: ["from", "to", "target", "verdict", "findings"], description: "Build a verifiable cross-agent review verdict entry (printed as JSON)." },
{ tool: "cw_ledger_verify", capability: "ledger.verify", requiredArgs: ["entry"], properties: ["entry"], description: "Verify a ledger entry against its content digest (fail-closed on tampering)." },
{ tool: "cw_ledger_apply", capability: "ledger.apply", requiredArgs: ["entry"], properties: ["entry"], description: "Verify a proposal entry and return its suggestedDiff for `git apply` (fail-closed: no diff unless the entry verifies as a proposal)." },
{ tool: "cw_ledger_list", capability: "ledger.list", requiredArgs: ["dir|dirs"], properties: ["dir", "dirs"], description: "Read + verify every entry in one or more shared ledger directories (fail-closed inbox; 2+ dirs union-verify mirrors)." },
{ tool: "cw_review_status", capability: "review.status", requiredArgs: ["runId"], properties: ["runId", "cwd", "targetKind", "target", "now"], description: "Read the derived per-target review state + collaboration timeline for a run." },
{ tool: "cw_review_policy", capability: "review.policy", requiredArgs: ["runId"], properties: ["runId", "cwd", "requiredApprovals", "authorizedRoles", "allowSelfApproval", "requireAttestedActor", "appliesTo"], description: "Set the run's review-gate policy (required approvals, authorized roles, self-approval rule)." },
{ tool: "cw_feedback_list", capability: "feedback.list", requiredArgs: ["runId"], properties: ["runId", "cwd", "status"], description: "List run feedback records." },
{ tool: "cw_feedback_show", capability: "feedback.show", requiredArgs: ["runId, feedbackId"], properties: ["runId", "feedbackId", "cwd"], description: "Show a run feedback record." },
{ tool: "cw_feedback_collect", capability: "feedback.collect", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Collect feedback from failed nodes." },
{ tool: "cw_feedback_task", capability: "feedback.task", requiredArgs: ["runId"], properties: ["runId", "feedbackId", "cwd", "verify"], description: "Create a correction task for feedback." },
{ tool: "cw_feedback_resolve", capability: "feedback.resolve", requiredArgs: ["runId"], properties: ["runId", "feedbackId", "cwd", "node", "status"], description: "Resolve or reject feedback." },
{ tool: "cw_schedule_create", capability: "schedule.create", requiredArgs: [], properties: ["cwd", "kind", "prompt", "intervalMinutes", "cron", "delayMinutes"], description: "Create a scheduled CW task." },
{ tool: "cw_schedule_list", capability: "schedule.list", requiredArgs: [], properties: ["cwd", "status"], description: "List scheduled CW tasks." },
{ tool: "cw_schedule_due", capability: "schedule.due", requiredArgs: [], properties: ["cwd"], description: "List due scheduled CW tasks." },
{ tool: "cw_schedule_complete", capability: "schedule.complete", requiredArgs: ["id"], properties: ["cwd", "id"], description: "Mark a scheduled task complete." },
{ tool: "cw_schedule_pause", capability: "schedule.pause", requiredArgs: ["id"], properties: ["cwd", "id"], description: "Pause a scheduled CW task." },
{ tool: "cw_schedule_resume", capability: "schedule.resume", requiredArgs: ["id"], properties: ["cwd", "id"], description: "Resume a scheduled CW task." },
{ tool: "cw_schedule_run_now", capability: "schedule.run-now", requiredArgs: ["id"], properties: ["cwd", "id"], description: "Create an immediate scheduled-task run record." },
{ tool: "cw_schedule_history", capability: "schedule.history", requiredArgs: [], properties: ["cwd", "id"], description: "List scheduled-task run history." },
{ tool: "cw_schedule_delete", capability: "schedule.delete", requiredArgs: ["id"], properties: ["cwd", "id"], description: "Delete a scheduled CW task." },
{ tool: "cw_routine_create", capability: "routine.create", requiredArgs: [], properties: ["cwd", "kind", "prompt", "match"], description: "Create a routine-style API/GitHub trigger." },
{ tool: "cw_routine_list", capability: "routine.list", requiredArgs: [], properties: ["cwd", "kind"], description: "List routine-style triggers." },
{ tool: "cw_routine_fire", capability: "routine.fire", requiredArgs: ["kind"], properties: ["cwd", "kind", "payload"], description: "Record an API/GitHub trigger event." },
{ tool: "cw_routine_events", capability: "routine.events", requiredArgs: [], properties: ["cwd", "id"], description: "List routine trigger events." },
{ tool: "cw_routine_delete", capability: "routine.delete", requiredArgs: ["id"], properties: ["cwd", "id"], description: "Delete a routine-style trigger." },
{ tool: "cw_registry_refresh", capability: "registry.refresh", requiredArgs: [], properties: ["cwd", "scope"], description: "Recompute and persist the derived run registry index." },
{ tool: "cw_registry_show", capability: "registry.show", requiredArgs: [], properties: ["cwd", "scope"], description: "Read the run registry index with valid|stale|absent freshness." },
{ tool: "cw_metrics_show", capability: "metrics.show", requiredArgs: ["runId"], properties: ["runId", "cwd", "pricing", "now"], description: "Read the derived per-run observability + attested-cost report (durations, failure/verifier/acceptance rates with sample counts, attested usage, cost, coverage)." },
{ tool: "cw_metrics_summary", capability: "metrics.summary", requiredArgs: [], properties: ["cwd", "scope", "pricing", "now", "limit"], description: "Read the cross-repo observability + cost rollup over the v0.1.28 run registry, with per-app and per-backend breakdowns." },
{ tool: "cw_run_search", capability: "run.search", requiredArgs: [], properties: ["cwd", "scope", "text", "app", "status", "repo", "since", "until", "includeArchived", "limit", "offset"], description: "Search runs by app/status/time/repo/free-text, deterministic + paginated." },
{ tool: "cw_run_list", capability: "run.list", requiredArgs: [], properties: ["cwd", "scope", "includeArchived", "limit", "offset"], description: "List indexed runs across repos (search with no filters)." },
{ tool: "cw_run_show", capability: "run.show", requiredArgs: ["runId"], properties: ["runId", "cwd", "scope"], description: "Resolve one run by id across the registry; fail closed on missing source." },
{ tool: "cw_run_resume", capability: "run.resume", requiredArgs: ["runId"], properties: ["runId", "cwd", "scope", "limit"], description: "Resolve a run by id and return its next runnable tasks/actions (read-only by default; the opt-in --drive/--once mode hands it to the shared agent-drive core, which mutates and is covered by run.drive.step)." },
{ tool: "cw_run_archive", capability: "run.archive", requiredArgs: ["runId|olderThanDays"], properties: ["runId", "cwd", "scope", "reason", "unarchive", "olderThanDays", "state"], description: "Archive/unarchive a run (overlay mark; never deletes source)." },
{ tool: "cw_run_rerun", capability: "run.rerun", requiredArgs: ["runId"], properties: ["runId", "cwd", "scope", "reason"], description: "Re-run a failed run as a NEW run linked to the original by provenance." },
{ tool: "cw_run_export", capability: "run.export", requiredArgs: ["runId"], properties: ["runId", "cwd", "output", "path", "archive", "trustKey", "withTrustKey"], description: "Export a run to a portable archive with run-local files and digest integrity." },
{ tool: "cw_run_import", capability: "run.import", requiredArgs: ["archive|path|file"], properties: ["archive", "path", "file", "target", "repo", "cwd"], description: "Restore a portable run archive into a target repo and verify restored file digests." },
{ tool: "cw_run_verify_import", capability: "run.verify-import", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Verify an imported run against its restore manifest and telemetry chain." },
{ tool: "cw_run_inspect_archive", capability: "run.inspect-archive", requiredArgs: ["archive|path|file"], properties: ["archive", "path", "file", "cwd"], description: "Read-only integrity inspection of a portable run archive without importing it." },
{ tool: "cw_run_restore", capability: "run.restore", requiredArgs: ["archive|path|file"], properties: ["archive", "path", "file", "target", "repo", "cwd"], description: "Fail-closed restore of a portable run archive: integrity-inspect, import, and verify in one step; refuses anything that does not verify." },
{ tool: "cw_report_verify_bundle", capability: "report.verify-bundle", requiredArgs: ["archive|path|file|bundle"], properties: ["archive", "path", "file", "bundle", "pubkey", "extractReport", "strictSignatures", "cwd"], description: "Offline self-contained verify of a portable run bundle: archive bytes + telemetry chain + trust-audit chain + embedded-key signatures." },
{ tool: "cw_report_bundle", capability: "report.bundle", requiredArgs: ["runId"], properties: ["runId", "cwd", "output", "path", "trustKey", "withTrustKey", "extractReport", "strictSignatures"], description: "Produce-and-prove: export a run to a portable bundle sealed with the trust key, then self-verify it offline (fail-closed) so the producer knows it is verifiable before shipping." },
{ tool: "cw_run_drive", capability: "run.drive", requiredArgs: [], properties: ["runId", "cwd"], description: "Preview the next agent-delegation drive step for a run (read-only, deterministic)." },
{ tool: "cw_run_drive_step", capability: "run.drive.step", requiredArgs: [], properties: ["runId", "appId", "repo", "question", "once", "now", "concurrency", "cwd"], description: "Drive a run by delegating each worker to the agent backend (plan->dispatch->fulfill->accept->commit; --once for one step)." },
{ tool: "cw_queue_add", capability: "queue.add", requiredArgs: [], properties: ["cwd", "runId", "appId", "workflowId", "repo", "priority", "note"], description: "Enqueue a pending/planned run with explicit ordering policy." },
{ tool: "cw_queue_list", capability: "queue.list", requiredArgs: [], properties: ["cwd", "status", "repo"], description: "List the durable run queue in policy order." },
{ tool: "cw_queue_drain", capability: "queue.drain", requiredArgs: [], properties: ["cwd", "limit", "repo"], description: "Mark the next ready queue entries drained (the host still executes)." },
{ tool: "cw_queue_show", capability: "queue.show", requiredArgs: ["id"], properties: ["cwd", "id"], description: "Show one durable queue entry." },
{ tool: "cw_sched_plan", capability: "sched.plan", requiredArgs: [], properties: ["cwd"], description: "Read-only control-plane lease plan for the queue+policy+now." },
{ tool: "cw_sched_lease", capability: "sched.lease", requiredArgs: [], properties: ["cwd", "limit"], description: "Claim eligible queue entries as leases (concurrency-bounded)." },
{ tool: "cw_sched_release", capability: "sched.release", requiredArgs: [], properties: ["cwd", "leaseId", "failed", "reason"], description: "Release a held lease (failed -> retry/backoff or park)." },
{ tool: "cw_sched_complete", capability: "sched.complete", requiredArgs: [], properties: ["cwd", "leaseId"], description: "Complete a held lease (terminal success)." },
{ tool: "cw_sched_reclaim", capability: "sched.reclaim", requiredArgs: [], properties: ["cwd"], description: "Reclaim expired leases (each counts a failed attempt)." },
{ tool: "cw_sched_reset", capability: "sched.reset", requiredArgs: [], properties: ["cwd", "id"], description: "Reset a parked entry to ready (operator recovery)." },
{ tool: "cw_sched_policy_show", capability: "sched.policy.show", requiredArgs: [], properties: ["cwd"], description: "Show the scheduling policy (file or default)." },
{ tool: "cw_sched_policy_set", capability: "sched.policy.set", requiredArgs: [], properties: ["cwd", "maxConcurrent", "maxAttempts", "leaseTtlMs", "backoffBaseMs", "backoffFactor", "backoffCapMs"], description: "Set scheduling policy fields (concurrency/attempts/backoff/TTL)." },
{ tool: "cw_gc_plan", capability: "gc.plan", requiredArgs: [], properties: ["cwd", "scope", "runId", "reclaimAfterArchiveDays", "keepScratch", "keepSnapshots"], description: "Dry-run plan of run reclamation (per-kind bytes + capability downgrade); frees nothing." },
{ tool: "cw_gc_run", capability: "gc.run", requiredArgs: [], properties: ["cwd", "scope", "runId", "reclaimAfterArchiveDays", "keepScratch", "keepSnapshots", "limit", "actor"], description: "Execute the write-ahead reclamation transaction (skeleton -> tombstone -> fsync -> free)." },
{ tool: "cw_gc_verify", capability: "gc.verify", requiredArgs: ["runId"], properties: ["cwd", "scope", "runId"], description: "Re-prove a reclaimed run: skeleton-complete, tombstone chain untampered, artifacts reconstructable." },
{ tool: "cw_clones_list", capability: "clones.list", requiredArgs: [], properties: [], description: "List the cached remote-source checkouts that --link/URL reviews populate (origin URL, kind, commit, age, bytes). Read-only." },
{ tool: "cw_clones_gc", capability: "clones.gc", requiredArgs: [], properties: ["olderThanDays", "all"], description: "Reclaim cached remote-source checkouts: a TTL sweep (--older-than-days, default 30) or --all. Deletes only inside the clones cache." },
{ tool: "cw_orphans_list", capability: "orphans.list", requiredArgs: [], properties: ["cwd", "scope"], description: "List run directories under .cw/runs/ that the run registry cannot see (no state.json — a killed/interrupted process never wrote one), with age + bytes. Read-only." },
{ tool: "cw_orphans_gc", capability: "orphans.gc", requiredArgs: [], properties: ["cwd", "scope", "minAgeMinutes", "all"], description: "Reclaim orphan run directories (no state.json): an age sweep (--min-age-minutes, default 60) or --all. Deletes only inside a scanned repo's .cw/runs/, never a run the registry knows about." },
{ tool: "cw_telemetry_verify", capability: "telemetry.verify", requiredArgs: ["runId"], properties: ["cwd", "runId", "pubkey"], description: "Re-prove a run's telemetry attestation ledger offline: chain linkage + independent hash recompute, and (with --pubkey / CW_AGENT_ATTEST_PUBKEY) re-verify each attested hop's ed25519 signature against the public key." },
{ tool: "cw_history", capability: "history", requiredArgs: [], properties: ["cwd", "scope", "app", "status", "limit", "offset"], description: "Read a cross-repo unified run timeline (newest first)." },
// --- post-rebuild additions (appended; see the header note above) ---
{ tool: "cw_audit_head", capability: "audit.head", requiredArgs: ["runId"], properties: ["runId", "cwd"], description: "Read the trust-audit chain head anchor (event count + head hash) for a later truncation-proof audit.verify." },
];
"use strict";
// core/types/execution-backend.ts — plain data shapes for the driver layer.
//
// MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of the shapes
// in the old build's src/types/execution-backend.ts and the sandbox slice of
// src/types/sandbox.ts that this subsystem needs. Types only — no logic —
// so this file lives in core/ (moved here from shell/execution-backend/
// types.ts, which now re-exports it for its 7 existing importers): the
// executor-boundary welds in core/types/boundary.ts need ResultEnvelope
// and ExecutionResultEnvelope, and neither can be cherry-picked out of
// this file alone without also carrying their whole dependency graph
// (ExecutionProvenance, SandboxAttestation, BackendLocality/BackendKind,
// BackendExecutionHandle, ...) — so the file moves as one piece, matching
// its own original header's claim that it was "safe to import from both
// shell/ (impure) and any future core/ caller."
//
// Evidence: SPEC/execution-backend.md.
Object.defineProperty(exports, "__esModule", { value: true });
"use strict";
// core/types/observability.ts — the one type shell/observability.ts's
// executor-boundary weld (core/types/boundary.ts) needs, moved here since
// it is plain data with no dependency on that (impure) file's logic.
// shell/observability.ts re-exports it so its own existing exports stay
// unchanged.
Object.defineProperty(exports, "__esModule", { value: true });
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.stableCompare = stableCompare;
// core/util/collate.ts — THE one string-ordering comparator for anything
// whose order feeds a hash, a cache key, or a byte-pinned output.
//
// Pure. No fs, no child_process, no net, no process.env, no Date.now(), no
// Math.random().
//
// A bare `a.localeCompare(b)` reads the HOST's default locale (LANG/LC_ALL),
// which is not part of CW's replay-determinism story: two machines with
// different locales sorting the SAME string set can walk it in a different
// order, so anything that hashes that order (a cache key) drifts silently
// across hosts, and anything that hashes CONTENT built from that order (an
// eval snapshot) can misreport a determinism regression that is really just
// a locale difference. `stableCompare` pins the locale explicitly to "en" —
// this is the SAME bytes Node's full-ICU build already produces under the
// ICU root locale (which is what a locale-stripped environment, e.g. this
// repo's own conformance harness, always runs under), so switching a bare
// `localeCompare` call to this one changes NO existing output.
function stableCompare(a, b) {
return a.localeCompare(b, "en");
}
"use strict";
// wiring/capability-table/basics.ts — MILESTONE 2's CLI bindings (version,
// list, status, sandbox.list). Split out of core/capability-table.ts,
// byte-for-byte (extracted with sed, not retyped).
Object.defineProperty(exports, "__esModule", { value: true });
const registry_core_1 = require("./registry-core");
// ---------------------------------------------------------------------
// CLI bindings wired at THIS milestone (version, list, status,
// sandbox.list). `version` is cli-only per SPEC/mcp.md's declared
// one-surface list (`help` is handled directly by cli/entry.ts's
// top-level flag redirect, same as milestone 1 — it is not itself a
// dispatchable command row); `list`/`status`/`sandbox.list` reuse the mcp
// row's capability id and get a cli binding layered on top. Every handler
// below returns a `CliHandlerResult`; core/ never touches process.stdout
// or process.exitCode directly (see docs/rebuild/PLAN.md's core/shell split) —
// cli/dispatch.ts's generic executor performs the actual write.
// ---------------------------------------------------------------------
const version_1 = require("../../core/version");
const workflow_app_loader_1 = require("../../shell/workflow-app-loader");
const help_1 = require("../../core/format/help");
(0, registry_core_1.addCliOnlyCapability)("version", "Print the current cool-workflow version.", {
path: ["version"],
jsonMode: "default",
handler: () => ({ text: `${version_1.CURRENT_COOL_WORKFLOW_VERSION}\n` }),
}, "version is a local, no-run-state print; the old build never gave it an MCP peer.");
/** `cw search <keyword>` — filters the SAME real app discovery `cw list`
* shows, by id/title/summary (byte-behavior port of cli/dispatch.ts's
* milestone-1 carry-over `search` arm, moved here so the dispatchLegacy
* switch shrinks per its file header's rule). `hiddenFromHelp` keeps it
* out of the per-verb help listing exactly as before (it never had one —
* `search` only ever appeared in formatHelp's hard-coded "More commands"
* index line, which this row does not touch), so `cw help search` keeps
* its existing "Unknown command: search" text. */
(0, registry_core_1.addCliOnlyCapability)("search", "Search bundled workflows by id/title/summary keyword.", {
path: ["search"],
jsonMode: "flag",
hiddenFromHelp: true,
handler: (args) => {
const keyword = args.positionals.join(" ");
if (!keyword.trim()) {
throw new Error('Missing search keyword.\n Tip: cw search architecture to find workflows about architecture.');
}
const lower = keyword.toLowerCase();
const results = (0, workflow_app_loader_1.listWorkflowApps)()
.filter((a) => String(a.title).toLowerCase().includes(lower) ||
String(a.summary).toLowerCase().includes(lower) ||
String(a.id).toLowerCase().includes(lower))
.map((a) => ({ id: String(a.id), title: String(a.title), summary: String(a.summary) }));
return { json: results, text: (0, help_1.formatSearchResults)(keyword, results) };
},
}, "CLI-only discovery helper over the same real app data cw list shows; no MCP client needs a free-text search tool alongside cw_list's structured output.");
(0, registry_core_1.attachCliBinding)("list", {
path: ["list"],
jsonMode: "default",
handler: () => ({ json: (0, registry_core_1.listBundledWorkflows)() }),
});
(0, registry_core_1.attachCliBinding)("status", {
path: ["status"],
jsonMode: "flag",
handler: (args) => ({ json: (0, registry_core_1.statusPayload)(args.positionals[0]) }),
});
(0, registry_core_1.attachCliBinding)("sandbox.list", {
path: ["sandbox", "list"],
jsonMode: "default",
handler: () => ({ json: (0, registry_core_1.listBundledSandboxProfiles)() }),
});
"use strict";
// wiring/capability-table/exec-backend.ts — MILESTONE 5 (execution backend,
// agent spawn, sandbox) CLI bindings: sandbox.*, backend.*, app.run. Split
// out of core/capability-table.ts, byte-for-byte (extracted with sed, not
// retyped).
Object.defineProperty(exports, "__esModule", { value: true });
const registry_core_1 = require("./registry-core");
// MILESTONE 5 (execution backend, agent spawn, sandbox) CLI bindings:
// sandbox.list|show|validate, backend.list|show|probe,
// backend.agent.config.show|set, doctor, fix. Handler BODIES live in
// shell/exec-backend-cli.ts / shell/doctor.ts (impure — env/fs reads);
// this table only wires argv shape -> handler call, per cli/dispatch.ts's
// generic executor contract. `sandbox.list`/`backend.list` are ALREADY
// declared MCP-only rows from milestone 2 (MCP_TOOL_DATA above) — this
// section layers a `cli` binding onto them (attachCliBinding) and replaces
// their milestone-2 placeholder `mcp.handler` with the real body, exactly
// as milestones 3/4 did for their own rows.
// ---------------------------------------------------------------------
const exec_backend_cli_1 = require("../../shell/exec-backend-cli");
const doctor_1 = require("../../shell/doctor");
const io_1 = require("../../cli/io");
const app_run_cli_1 = require("../../shell/app-run-cli");
(0, registry_core_1.attachCliBinding)("sandbox.list", {
path: ["sandbox", "list"],
jsonMode: "default",
handler: (args) => ({ json: (0, exec_backend_cli_1.listSandboxProfilesCli)(args.options) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("sandbox.list").mcp.handler = (args) => (0, exec_backend_cli_1.listSandboxProfilesCli)(args);
// GAP #24: cw_sandbox_choose / cw_sandbox_resolve + cw_app_run were declared
// MCP-only rows with the notYetImplemented placeholder handler. Wire them to
// the ported shell bodies (both are MCP-only in the old build — no CLI path).
registry_core_1.REGISTRY_BY_CAPABILITY.get("sandbox.choose").mcp.handler = (args) => (0, app_run_cli_1.sandboxChooseCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("sandbox.resolve").mcp.handler = (args) => (0, app_run_cli_1.sandboxChooseCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("app.run").mcp.handler = (args) => (0, app_run_cli_1.appRunCli)(args);
(0, registry_core_1.attachCliBinding)("sandbox.show", {
path: ["sandbox", "show"],
jsonMode: "default",
handler: (args) => ({ json: (0, exec_backend_cli_1.showSandboxProfileCli)((0, io_1.required)(args.positionals[0], "profile id"), args.options) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("sandbox.show").mcp.handler = (args) => (0, exec_backend_cli_1.showSandboxProfileCli)((0, io_1.required)((0, io_1.optionalArg)(args.profileId), "profile id"), args);
(0, registry_core_1.attachCliBinding)("sandbox.validate", {
path: ["sandbox", "validate"],
jsonMode: "default",
handler: (args) => {
const result = (0, exec_backend_cli_1.validateSandboxProfileCli)((0, io_1.required)(args.positionals[0], "profile file"), args.options);
return { json: result, exitCode: result.valid ? undefined : 1 };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("sandbox.validate").mcp.handler = (args) => (0, exec_backend_cli_1.validateSandboxProfileCli)((0, io_1.required)((0, io_1.optionalArg)(args.profileFile), "profile file"), args);
// PARITY: `sandbox.choose`/`sandbox.resolve` are BOTH-surface capabilities
// per SPEC/mcp.md (old build cli.path ["sandbox","choose"]/["sandbox",
// "resolve"]) — they were left MCP-only at GAP #24 (see the comment
// above sandboxChooseCli's mcp.handler wiring). Attach the same, already-
// working shell body as the cli.handler too, so the CLI front door and
// the parity payload probe both reach it (no new business logic, same
// function both surfaces already call over MCP).
(0, registry_core_1.attachCliBinding)("sandbox.choose", {
path: ["sandbox", "choose"],
jsonMode: "default",
handler: (args) => ({ json: (0, app_run_cli_1.sandboxChooseCli)(args.options) }),
});
(0, registry_core_1.attachCliBinding)("sandbox.resolve", {
path: ["sandbox", "resolve"],
jsonMode: "default",
handler: (args) => ({ json: (0, app_run_cli_1.sandboxChooseCli)(args.options) }),
});
(0, registry_core_1.attachCliBinding)("backend.list", {
path: ["backend", "list"],
jsonMode: "default",
handler: () => ({ json: (0, exec_backend_cli_1.listBackendsCli)() }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("backend.list").mcp.handler = () => (0, exec_backend_cli_1.listBackendsCli)();
(0, registry_core_1.attachCliBinding)("backend.show", {
path: ["backend", "show"],
jsonMode: "default",
handler: (args) => ({ json: (0, exec_backend_cli_1.showBackendCli)((0, io_1.required)(args.positionals[0], "backend id")) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("backend.show").mcp.handler = (args) => (0, exec_backend_cli_1.showBackendCli)((0, io_1.required)((0, io_1.optionalArg)(args.backendId), "backend id"));
(0, registry_core_1.attachCliBinding)("backend.probe", {
path: ["backend", "probe"],
jsonMode: "default",
handler: (args) => ({ json: (0, exec_backend_cli_1.probeBackendCli)(args.positionals[0], args.options) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("backend.probe").mcp.handler = (args) => (0, exec_backend_cli_1.probeBackendCli)((0, io_1.optionalArg)(args.backendId), args);
// `backend agent config [show]` = read-only; `backend agent config set
// ...` = mutating. CLI path is ["backend", "agent"] (2 tokens, matching
// dispatchTable's supported path lengths); the remaining positionals
// ("config", "show"/"set") are read inside the handler, byte-exact to the
// old build's handleBackend "agent" case (src/cli/handlers/
// operational.ts:52-62).
(0, registry_core_1.attachCliBinding)("backend.agent.config.show", {
path: ["backend", "agent"],
helpPath: ["backend", "agent", "config"],
jsonMode: "default",
handler: (args) => {
const action = args.positionals[1];
if (action === "set")
return { json: (0, exec_backend_cli_1.backendAgentConfigSet)(args.options) };
return { json: (0, exec_backend_cli_1.backendAgentConfigShow)(args.options) };
},
});
// `backend.agent.config.set` shares the SAME dispatch path/handler as
// `.show` above (dispatchTable only supports 2-token paths, and the
// show-vs-set branch lives inside that one handler on positionals[1] —
// byte-exact to the old build's handleBackend "agent" case). This second
// attachCliBinding call exists ONLY so `cw help backend` lists both rows
// (cliCommandHelpRows iterates cliCapabilities(), one row per capability),
// matching the old registry's two declared rows sharing one caseTokens
// group; dispatchTable itself never reaches this row a second time because
// `backend.agent.config.show`'s row is found first by
// findCapabilityByCliPath's linear scan and its handler already covers
// both actions.
(0, registry_core_1.attachCliBinding)("backend.agent.config.set", {
path: ["backend", "agent"],
helpPath: ["backend", "agent", "config"],
jsonMode: "default",
handler: (args) => {
const action = args.positionals[1];
if (action === "set")
return { json: (0, exec_backend_cli_1.backendAgentConfigSet)(args.options) };
return { json: (0, exec_backend_cli_1.backendAgentConfigShow)(args.options) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("backend.agent.config.show").mcp.handler = (args) => (0, exec_backend_cli_1.backendAgentConfigShow)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("backend.agent.config.set").mcp.handler = (args) => (0, exec_backend_cli_1.backendAgentConfigSet)(args);
// PARITY: `backend.agent.config.set` mutates $CW_HOME/agent-config.json
// (secret-stripped) before returning the effective config; both surfaces
// perform the same write, so it is a documented opt-out from the
// read-payload probe, not an undocumented divergence.
registry_core_1.REGISTRY_BY_CAPABILITY.get("backend.agent.config.set").payloadIdentical = false;
registry_core_1.REGISTRY_BY_CAPABILITY.get("backend.agent.config.set").reason =
"Mutating: persists $CW_HOME/agent-config.json (secret-stripped) before returning the effective config; both surfaces perform the same write — it is a surface-mutating verb, not a read probe.";
(0, registry_core_1.addCliOnlyCapability)("doctor", "Diagnose the host for setup problems (Node version, agent backend, agent binary on PATH, git, writable home/repo state) and print an actionable fix per check.", {
path: ["doctor"],
jsonMode: "flag",
handler: (args) => {
const report = (0, doctor_1.runDoctor)(args.options, process.env, String(args.options.cwd || process.cwd()));
// Byte-exact port of src/cli/command-surface.ts:170-176: both text
// branches are written as `${formatX(report)}\n` UNCONDITIONALLY —
// formatDoctorFixes already ends in its own "\n" (its last joined
// element is ""), so its case needs one MORE explicit "\n" here to
// reproduce that unconditional append; cli/dispatch.ts's generic
// renderer only appends "\n" when the text does NOT already end in
// one, so a bare `formatDoctorFixes(report)` here would silently
// drop the old build's trailing blank line.
const text = (0, io_1.wantsJson)(args.options) ? undefined : args.options.fix ? `${(0, doctor_1.formatDoctorFixes)(report)}\n` : (0, doctor_1.formatDoctorReport)(report);
return { json: report, text, exitCode: report.ok ? undefined : 1 };
},
}, "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`.");
(0, registry_core_1.addCliOnlyCapability)("fix", "Print consolidated fix commands for CW setup issues.", {
path: ["fix"],
jsonMode: "human",
handler: (args) => {
const report = (0, doctor_1.runDoctor)(args.options, process.env, String(args.options.cwd || process.cwd()));
// See the "doctor" handler's comment above: formatDoctorFixes
// already ends in "\n", so one more explicit "\n" here reproduces
// src/cli/command-surface.ts:126-130's unconditional
// `${formatDoctorFixes(report)}\n` write.
return { text: `${(0, doctor_1.formatDoctorFixes)(report)}\n`, exitCode: report.ok ? undefined : 1 };
},
}, "Environment fix commands are local diagnostics, same reasoning as doctor.");
// ---------------------------------------------------------------------
"use strict";
// wiring/capability-table/index.ts — composes the capability table from
// registry-core (the shared machinery) plus every domain slice, in the
// EXACT original source order (REGISTRY array order is a pinned
// behavior: tools/list order, gen-parity-doc's byte-diff gate, cw help
// line order). Each slice is a plain module whose top-level
// attachCliBinding/addCliOnlyCapability/REGISTRY_BY_CAPABILITY calls run
// once, at first import — Node's module system guarantees that happens
// in exactly the order these imports are written below, the same
// guarantee the single original file relied on for its own top-to-bottom
// statement order.
//
// No slice imports another slice; every slice imports only from
// registry-core.ts, core/capability-data.ts, and shell/core as needed —
// so this file is the only place the full domain list is assembled, and
// there is no cross-slice circular dependency to reason about.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./registry-core"), exports);
require("./basics");
require("./state");
require("./exec-backend");
require("./pipeline");
require("./trust-ledger");
require("./multi-agent");
require("./scheduling-registry");
require("./reporting");
require("./workflow-apps");
__exportStar(require("./parity"), exports);
"use strict";
// wiring/capability-table/multi-agent.ts — MILESTONE 9 (multi-agent,
// topology, coordinator/blackboard, candidate scoring, collaboration,
// eval-replay) CLI bindings. Split out of core/capability-table.ts,
// byte-for-byte (extracted with sed, not retyped).
Object.defineProperty(exports, "__esModule", { value: true });
const registry_core_1 = require("./registry-core");
const io_1 = require("../../cli/io");
const operator_ux_text_1 = require("../../shell/operator-ux-text");
const state_explosion_text_1 = require("../../core/format/state-explosion-text");
const eval_text_1 = require("../../shell/eval-text");
// MILESTONE 9 (multi-agent, topology, coordinator/blackboard, candidate
// scoring, collaboration, eval replay) CLI bindings. Handler BODIES live
// in shell/multi-agent-cli.ts (impure — they read/write multi-agent/
// blackboard/candidate/collaboration/eval state on disk); this table
// only wires argv shape -> handler call, per cli/dispatch.ts's generic
// executor contract.
// ---------------------------------------------------------------------
const multi_agent_cli_1 = require("../../shell/multi-agent-cli");
const collaboration_io_1 = require("../../shell/collaboration-io");
const topology_io_1 = require("../../shell/topology-io");
(0, registry_core_1.attachCliBinding)("topology.list", { path: ["topology", "list"], jsonMode: "default", handler: () => ({ json: (0, multi_agent_cli_1.topologyList)() }) });
registry_core_1.REGISTRY_BY_CAPABILITY.get("topology.list").mcp.handler = () => (0, multi_agent_cli_1.topologyList)();
(0, registry_core_1.attachCliBinding)("topology.show", {
path: ["topology", "show"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.topologyShowCli)((0, io_1.required)(args.positionals[0], "topology id")) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("topology.show").mcp.handler = (args) => (0, multi_agent_cli_1.topologyShowCli)((0, io_1.required)((0, io_1.optionalArg)(args.topologyId ?? args.id), "topology id"));
(0, registry_core_1.attachCliBinding)("topology.validate", {
path: ["topology", "validate"],
jsonMode: "default",
handler: (args) => {
const result = (0, multi_agent_cli_1.topologyValidateCli)((0, io_1.required)(args.positionals[0], "topology id"));
return { json: result, exitCode: result.valid ? undefined : 1 };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("topology.validate").mcp.handler = (args) => (0, multi_agent_cli_1.topologyValidateCli)((0, io_1.required)((0, io_1.optionalArg)(args.topologyId ?? args.id), "topology id"));
(0, registry_core_1.attachCliBinding)("topology.apply", {
path: ["topology", "apply"],
jsonMode: "default",
handler: (args) => ({
json: (0, multi_agent_cli_1.topologyApplyCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[0], "run id"), topologyId: (0, io_1.required)(args.positionals[1], "topology id") }),
}),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("topology.apply").mcp.handler = (args) => (0, multi_agent_cli_1.topologyApplyCli)({ ...args, topologyId: args.topologyId ?? args.id });
// jsonMode "flag": human `Topologies` panel by default, canonical JSON
// under --json (old build's topology.summary was flag).
(0, registry_core_1.attachCliBinding)("topology.summary", {
path: ["topology", "summary"],
jsonMode: "flag",
handler: (args) => {
const summary = (0, multi_agent_cli_1.topologySummaryCli)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options });
return { json: summary, text: `${(0, topology_io_1.formatTopologySummaryText)(summary)}\n` };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("topology.summary").mcp.handler = (args) => (0, multi_agent_cli_1.topologySummaryCli)(args);
// jsonMode "flag": human `Run Graph:` render by default, canonical JSON
// under --json (old build's topology.graph was flag).
(0, registry_core_1.attachCliBinding)("topology.graph", {
path: ["topology", "graph"],
jsonMode: "flag",
handler: (args) => {
const runId = (0, io_1.required)(args.positionals[0], "run id");
const graph = (0, multi_agent_cli_1.topologyGraphCli)({ runId, ...args.options });
return { json: graph, text: `${(0, topology_io_1.formatTopologyGraphText)(runId, graph)}\n` };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("topology.graph").mcp.handler = (args) => (0, multi_agent_cli_1.topologyGraphCli)(args);
// ---- multi-agent kernel + host -----------------------------------------
(0, registry_core_1.attachCliBinding)("multi-agent.run", {
path: ["multi-agent", "run"],
jsonMode: "default",
// positionals[1] is the MultiAgentRun entity id for the transition/show
// arms (`cw multi-agent run <run> <id> --status …`); it must NOT collide
// with the create arm's `--id`, so it is forwarded as `multiAgentRunId`
// only when no `--id` create flag was passed (old handler took `id` from
// the 3rd positional token).
handler: (args) => ({
json: (0, multi_agent_cli_1.multiAgentRunCli)({
...args.options,
runId: (0, io_1.required)(args.positionals[0], "run id"),
multiAgentRunId: args.options.id === undefined ? (args.positionals[1] ?? args.options.multiAgentRunId) : args.options.multiAgentRunId,
}),
}),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.run").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentRunCli)(args);
(0, registry_core_1.attachCliBinding)("multi-agent.status", {
path: ["multi-agent", "status"],
jsonMode: "flag",
handler: (args) => ({
json: (0, multi_agent_cli_1.multiAgentStatusCli)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options }),
text: (0, multi_agent_cli_1.multiAgentStatusText)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options }),
}),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.status").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentStatusCli)(args);
(0, registry_core_1.attachCliBinding)("multi-agent.step", {
path: ["multi-agent", "step"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.multiAgentStepCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[0], "run id") }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.step").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentStepCli)(args);
(0, registry_core_1.attachCliBinding)("multi-agent.blackboard", {
path: ["multi-agent", "blackboard"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.multiAgentBlackboardCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[0], "run id") }, args.positionals[1]) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.blackboard").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentBlackboardCli)(args, args.action);
(0, registry_core_1.attachCliBinding)("multi-agent.score", {
path: ["multi-agent", "score"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.multiAgentScoreCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[0], "run id"), candidate: args.options.candidate ?? args.positionals[1] }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.score").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentScoreCli)(args);
(0, registry_core_1.attachCliBinding)("multi-agent.select", {
path: ["multi-agent", "select"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.multiAgentSelectCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[0], "run id"), candidate: args.options.candidate ?? args.positionals[1] }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.select").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentSelectCli)(args);
// jsonMode "flag": human `Multi-Agent` panel by default, canonical JSON
// under --json (old build's multi-agent.summary was flag).
(0, registry_core_1.attachCliBinding)("multi-agent.summary", {
path: ["multi-agent", "summary"],
jsonMode: "flag",
handler: (args) => {
const summary = (0, multi_agent_cli_1.multiAgentSummaryCli)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options });
return { json: summary, text: `${(0, operator_ux_text_1.formatMultiAgentSummaryText)(summary)}\n` };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.summary").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentSummaryCli)(args);
// `cw multi-agent graph <run>` is one dispatch path served by two capability
// rows (multi-agent.graph — the operator graph — and multi-agent.graph.compact
// — the state-explosion view under --view/--focus/--depth), exactly like
// blackboard.message.post/list. One shared handler answers both; the
// second row exists so both capabilities carry a cli binding (the
// both-surface pairing) and `cw help multi-agent` can list both forms.
function multiAgentGraphHandler(args) {
const call = { runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options };
if (args.options.view !== undefined || args.options.focus !== undefined || args.options.depth !== undefined) {
const compact = (0, multi_agent_cli_1.multiAgentGraphCompactCli)(call);
return { json: compact, text: (0, state_explosion_text_1.formatCompactGraph)(compact) };
}
return { json: (0, multi_agent_cli_1.multiAgentGraphCli)(call), text: (0, multi_agent_cli_1.multiAgentGraphText)(call) };
}
(0, registry_core_1.attachCliBinding)("multi-agent.graph", { path: ["multi-agent", "graph"], helpPath: ["multi-agent", "graph"], jsonMode: "flag", handler: multiAgentGraphHandler });
(0, registry_core_1.attachCliBinding)("multi-agent.graph.compact", { path: ["multi-agent", "graph"], helpPath: ["multi-agent", "graph"], jsonMode: "flag", handler: multiAgentGraphHandler });
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.graph").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentGraphCli)(args);
// GAP: `cw multi-agent dependencies|failures|evidence` — the MCP tool rows
// (cw_multi_agent_dependencies/failures/evidence) were declared but had no
// CLI path binding and their mcp.handler was still notYetImplemented. Wire
// both surfaces to the same operator-ux derivation the CLI text render uses
// (port of the old handler's dependencies/failures/evidence arms).
(0, registry_core_1.attachCliBinding)("multi-agent.dependencies", {
path: ["multi-agent", "dependencies"],
jsonMode: "flag",
handler: (args) => ({
json: (0, multi_agent_cli_1.multiAgentDependenciesCli)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options }),
text: (0, multi_agent_cli_1.multiAgentDependenciesText)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options }),
}),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.dependencies").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentDependenciesCli)(args);
(0, registry_core_1.attachCliBinding)("multi-agent.failures", {
path: ["multi-agent", "failures"],
jsonMode: "flag",
handler: (args) => ({
json: (0, multi_agent_cli_1.multiAgentFailuresCli)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options }),
text: (0, multi_agent_cli_1.multiAgentFailuresText)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options }),
}),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.failures").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentFailuresCli)(args);
(0, registry_core_1.attachCliBinding)("multi-agent.evidence", {
path: ["multi-agent", "evidence"],
jsonMode: "flag",
handler: (args) => ({
json: (0, multi_agent_cli_1.multiAgentEvidenceCli)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options }),
text: (0, multi_agent_cli_1.multiAgentEvidenceText)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options }),
}),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.evidence").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentEvidenceCli)(args);
// GAP: `cw multi-agent reasoning <run> [--refresh|--evidence <id>]` — the
// evidence-adoption reasoning chain (cw_evidence_reasoning /
// cw_evidence_reasoning_refresh MCP tools were declared but notYetImplemented,
// and no CLI verb was bound). `--refresh` prints the durable index (JSON only,
// matching the old handler's printJson refresh arm); otherwise it prints the
// report (text, or JSON under --json).
function multiAgentReasoningHandler(args) {
const call = { runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options };
if (args.options.refresh && args.options.evidence === undefined && args.options.evidenceId === undefined) {
return { json: (0, multi_agent_cli_1.multiAgentReasoningRefreshCli)(call) };
}
return { json: (0, multi_agent_cli_1.multiAgentReasoningCli)(call), text: (0, multi_agent_cli_1.multiAgentReasoningText)(call) };
}
(0, registry_core_1.attachCliBinding)("multi-agent.reasoning", { path: ["multi-agent", "reasoning"], helpPath: ["multi-agent", "reasoning"], jsonMode: "flag", handler: multiAgentReasoningHandler });
// `multi-agent.reasoning.refresh` (the durable evidence-adoption index) shares
// the ["multi-agent","reasoning"] dispatch path — `cw multi-agent reasoning
// <run> --refresh` is served by the reasoning binding above (first row wins).
// This row exists so the refresh capability also carries a cli binding (the
// both-surface pairing) and `cw help multi-agent` lists it. Same handler.
(0, registry_core_1.attachCliBinding)("multi-agent.reasoning.refresh", { path: ["multi-agent", "reasoning"], helpPath: ["multi-agent", "reasoning"], jsonMode: "default", handler: multiAgentReasoningHandler });
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.reasoning").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentReasoningCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.reasoning.refresh").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentReasoningRefreshCli)(args);
// GAP: the state-explosion / contract read views (cw_multi_agent_summarize /
// cw_blackboard_summarize / cw_multi_agent_graph_compact / cw_contract_show)
// were declared MCP tools left on notYetImplemented, and their CLI verbs were
// unbound. Wire both surfaces to the ported read fns.
(0, registry_core_1.attachCliBinding)("multi-agent.summarize", {
path: ["multi-agent", "summarize"],
jsonMode: "flag",
handler: (args) => {
const result = (0, multi_agent_cli_1.multiAgentSummarizeCli)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options });
return { json: result, text: (0, state_explosion_text_1.formatStateExplosionReport)(result) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.summarize").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentSummarizeCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.graph.compact").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentGraphCompactCli)(args);
(0, registry_core_1.attachCliBinding)("blackboard.summarize", {
path: ["blackboard", "summarize"],
jsonMode: "flag",
handler: (args) => ({ json: (0, multi_agent_cli_1.blackboardSummarizeCli)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("blackboard.summarize").mcp.handler = (args) => (0, multi_agent_cli_1.blackboardSummarizeCli)(args);
(0, registry_core_1.attachCliBinding)("contract.show", {
path: ["contract", "show"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.contractShowCli)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options }, args.positionals[1]) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("contract.show").mcp.handler = (args) => (0, multi_agent_cli_1.contractShowCli)(args);
(0, registry_core_1.attachCliBinding)("multi-agent.run.create", {
path: ["multi-agent", "role"],
helpPath: ["multi-agent", "role"],
jsonMode: "default",
handler: (args) => ({
json: (0, multi_agent_cli_1.multiAgentRoleCli)({
...args.options,
runId: (0, io_1.required)(args.positionals[0], "run id"),
roleId: args.options.id === undefined && args.positionals.length >= 2 ? args.positionals[1] : args.options.roleId,
}),
}),
});
(0, registry_core_1.attachCliBinding)("multi-agent.group.create", {
path: ["multi-agent", "group"],
helpPath: ["multi-agent", "group"],
jsonMode: "default",
handler: (args) => ({
json: (0, multi_agent_cli_1.multiAgentGroupCli)({
...args.options,
runId: (0, io_1.required)(args.positionals[0], "run id"),
groupId: args.options.id === undefined && args.positionals.length >= 2 ? args.positionals[1] : args.options.groupId,
}),
}),
});
(0, registry_core_1.attachCliBinding)("multi-agent.membership.create", {
path: ["multi-agent", "membership"],
helpPath: ["multi-agent", "membership"],
jsonMode: "default",
handler: (args) => ({
json: (0, multi_agent_cli_1.multiAgentMembershipCli)({
...args.options,
runId: (0, io_1.required)(args.positionals[0], "run id"),
membershipId: args.options.id === undefined && args.positionals.length >= 2 ? args.positionals[1] : args.options.membershipId,
}),
}),
});
(0, registry_core_1.attachCliBinding)("multi-agent.fanout.create", {
path: ["multi-agent", "fanout"],
helpPath: ["multi-agent", "fanout"],
jsonMode: "default",
handler: (args) => ({
json: (0, multi_agent_cli_1.multiAgentFanoutCli)({
...args.options,
runId: (0, io_1.required)(args.positionals[0], "run id"),
fanoutId: args.options.id === undefined && args.positionals.length >= 2 ? args.positionals[1] : args.options.fanoutId,
}),
}),
});
(0, registry_core_1.attachCliBinding)("multi-agent.fanin.collect", {
path: ["multi-agent", "fanin"],
helpPath: ["multi-agent", "fanin"],
jsonMode: "default",
handler: (args) => ({
json: (0, multi_agent_cli_1.multiAgentFaninCli)({
...args.options,
runId: (0, io_1.required)(args.positionals[0], "run id"),
faninId: args.options.id === undefined && args.positionals.length >= 2 ? args.positionals[1] : args.options.faninId,
}),
}),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.run.create").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentRoleCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.role.create").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentRoleCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.group.create").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentGroupCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.membership.create").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentMembershipCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.fanout.create").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentFanoutCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.fanin.collect").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentFaninCli)(args);
// GAP: the *.show MCP tools were declared but left notYetImplemented. Route
// each to its create CLI fn's read arm (id-only args return the record).
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.role.show").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentRoleCli)({ ...args, roleId: args.roleId ?? args.id });
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.group.show").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentGroupCli)({ ...args, groupId: args.groupId ?? args.id });
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.membership.show").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentMembershipCli)({ ...args, membershipId: args.membershipId ?? args.id });
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.fanout.show").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentFanoutCli)({ ...args, fanoutId: args.fanoutId ?? args.id });
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.fanin.show").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentFaninCli)({ ...args, faninId: args.faninId ?? args.id });
// The create/show pairs for role/group/membership/fanout/fanin each SHARE
// one dispatch path (["multi-agent","role"] etc.); `cw multi-agent role
// <run> [id]` is served by the create binding declared above (first row
// wins findCapabilityByCliPath), and an id-only invocation returns the
// existing record (the read arm). These extra rows exist so each show
// capability — and multi-agent.role.create, distinct from the
// multi-agent.run.create binding that already owns the ["multi-agent",
// "role"] path — also carries a cli binding (the both-surface pairing),
// exactly like blackboard.message.post/list. Same handler, same shell fn.
function multiAgentRoleHandler(args) {
return {
json: (0, multi_agent_cli_1.multiAgentRoleCli)({
...args.options,
runId: (0, io_1.required)(args.positionals[0], "run id"),
roleId: args.options.id === undefined && args.positionals.length >= 2 ? args.positionals[1] : args.options.roleId,
}),
};
}
(0, registry_core_1.attachCliBinding)("multi-agent.role.create", { path: ["multi-agent", "role"], helpPath: ["multi-agent", "role"], jsonMode: "default", handler: multiAgentRoleHandler });
(0, registry_core_1.attachCliBinding)("multi-agent.role.show", { path: ["multi-agent", "role"], helpPath: ["multi-agent", "role"], jsonMode: "default", handler: multiAgentRoleHandler });
(0, registry_core_1.attachCliBinding)("multi-agent.group.show", {
path: ["multi-agent", "group"],
helpPath: ["multi-agent", "group"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.multiAgentGroupCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[0], "run id"), groupId: args.options.id === undefined && args.positionals.length >= 2 ? args.positionals[1] : args.options.groupId }) }),
});
(0, registry_core_1.attachCliBinding)("multi-agent.membership.show", {
path: ["multi-agent", "membership"],
helpPath: ["multi-agent", "membership"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.multiAgentMembershipCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[0], "run id"), membershipId: args.options.id === undefined && args.positionals.length >= 2 ? args.positionals[1] : args.options.membershipId }) }),
});
(0, registry_core_1.attachCliBinding)("multi-agent.fanout.show", {
path: ["multi-agent", "fanout"],
helpPath: ["multi-agent", "fanout"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.multiAgentFanoutCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[0], "run id"), fanoutId: args.options.id === undefined && args.positionals.length >= 2 ? args.positionals[1] : args.options.fanoutId }) }),
});
(0, registry_core_1.attachCliBinding)("multi-agent.fanin.show", {
path: ["multi-agent", "fanin"],
helpPath: ["multi-agent", "fanin"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.multiAgentFaninCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[0], "run id"), faninId: args.options.id === undefined && args.positionals.length >= 2 ? args.positionals[1] : args.options.faninId }) }),
});
(0, registry_core_1.attachCliBinding)("multi-agent.run.transition", {
path: ["multi-agent", "transition"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.multiAgentRunCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[0], "run id") }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.run.transition").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentRunCli)(args);
(0, registry_core_1.attachCliBinding)("multi-agent.run.show", {
path: ["multi-agent", "show"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.multiAgentShowCli)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options }, (0, io_1.required)(args.positionals[1], "id")) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("multi-agent.run.show").mcp.handler = (args) => (0, multi_agent_cli_1.multiAgentShowCli)(args, (0, io_1.required)((0, io_1.optionalArg)(args.multiAgentRunId ?? args.id), "id"));
// ---- blackboard / coordinator -------------------------------------------
(0, registry_core_1.attachCliBinding)("blackboard.summary", {
path: ["blackboard", "summary"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.blackboardSummaryCli)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("blackboard.summary").mcp.handler = (args) => (0, multi_agent_cli_1.blackboardSummaryCli)(args);
(0, registry_core_1.attachCliBinding)("blackboard.graph", {
path: ["blackboard", "graph"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.blackboardGraphCli)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("blackboard.graph").mcp.handler = (args) => (0, multi_agent_cli_1.blackboardGraphCli)(args);
(0, registry_core_1.attachCliBinding)("blackboard.resolve", {
path: ["blackboard", "resolve"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.blackboardResolveCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[0], "run id") }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("blackboard.resolve").mcp.handler = (args) => (0, multi_agent_cli_1.blackboardResolveCli)(args);
// GAP: the blackboard write/read verbs accept the sub-verb's ACTION word
// ("create"/"post"/"put"/"add"/"list") in EITHER of two slots around the run
// id, per the smokes' varied spellings:
// blackboard topic <run> (create, run first)
// blackboard topic create <run> (create action FIRST)
// blackboard message <run> (post, run first)
// blackboard message <run> list (list action AFTER run)
// blackboard message list <run> (list action FIRST)
// blackboard message post <run> (post action FIRST)
// dispatchTable already consumed the sub-verb ("topic"/"message"/…), so the
// handler's positionals begin at the token AFTER it. `blackboardRunAndAction`
// strips a leading action word (if present) so the run id is found wherever it
// sits, and reports the effective action word for the post/list split.
const BLACKBOARD_ACTION_WORDS = new Set(["create", "post", "put", "add", "list", "show"]);
function blackboardRunAndAction(args) {
const [first, second] = args.positionals;
if (first !== undefined && BLACKBOARD_ACTION_WORDS.has(first)) {
return { runId: (0, io_1.required)(second, "run id"), action: first };
}
return { runId: (0, io_1.required)(first, "run id"), action: second };
}
(0, registry_core_1.attachCliBinding)("blackboard.topic.create", {
path: ["blackboard", "topic"],
helpPath: ["blackboard", "topic", "create"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.blackboardTopicCreateCli)({ ...args.options, runId: blackboardRunAndAction(args).runId }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("blackboard.topic.create").mcp.handler = (args) => (0, multi_agent_cli_1.blackboardTopicCreateCli)(args);
function blackboardMessageHandler(args) {
const { runId, action } = blackboardRunAndAction(args);
if (action === "list")
return { json: (0, multi_agent_cli_1.blackboardMessageListCli)({ ...args.options, runId }) };
return { json: (0, multi_agent_cli_1.blackboardMessagePostCli)({ ...args.options, runId }) };
}
(0, registry_core_1.attachCliBinding)("blackboard.message.post", { path: ["blackboard", "message"], helpPath: ["blackboard", "message", "post"], jsonMode: "default", handler: blackboardMessageHandler });
(0, registry_core_1.attachCliBinding)("blackboard.message.list", { path: ["blackboard", "message"], helpPath: ["blackboard", "message", "list"], jsonMode: "default", handler: blackboardMessageHandler });
registry_core_1.REGISTRY_BY_CAPABILITY.get("blackboard.message.post").mcp.handler = (args) => (0, multi_agent_cli_1.blackboardMessagePostCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("blackboard.message.list").mcp.handler = (args) => (0, multi_agent_cli_1.blackboardMessageListCli)(args);
(0, registry_core_1.attachCliBinding)("blackboard.context.put", {
path: ["blackboard", "context"],
helpPath: ["blackboard", "context", "put"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.blackboardContextPutCli)({ ...args.options, runId: blackboardRunAndAction(args).runId }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("blackboard.context.put").mcp.handler = (args) => (0, multi_agent_cli_1.blackboardContextPutCli)(args);
function blackboardArtifactHandler(args) {
const { runId, action } = blackboardRunAndAction(args);
if (action === "list")
return { json: (0, multi_agent_cli_1.blackboardArtifactListCli)({ ...args.options, runId }) };
return { json: (0, multi_agent_cli_1.blackboardArtifactAddCli)({ ...args.options, runId }) };
}
(0, registry_core_1.attachCliBinding)("blackboard.artifact.add", { path: ["blackboard", "artifact"], helpPath: ["blackboard", "artifact", "add"], jsonMode: "default", handler: blackboardArtifactHandler });
(0, registry_core_1.attachCliBinding)("blackboard.artifact.list", { path: ["blackboard", "artifact"], helpPath: ["blackboard", "artifact", "list"], jsonMode: "default", handler: blackboardArtifactHandler });
registry_core_1.REGISTRY_BY_CAPABILITY.get("blackboard.artifact.add").mcp.handler = (args) => (0, multi_agent_cli_1.blackboardArtifactAddCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("blackboard.artifact.list").mcp.handler = (args) => (0, multi_agent_cli_1.blackboardArtifactListCli)(args);
(0, registry_core_1.attachCliBinding)("blackboard.snapshot", {
path: ["blackboard", "snapshot"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.blackboardSnapshotCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[0], "run id") }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("blackboard.snapshot").mcp.handler = (args) => (0, multi_agent_cli_1.blackboardSnapshotCli)(args);
(0, registry_core_1.attachCliBinding)("coordinator.summary", {
path: ["coordinator", "summary"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.coordinatorSummaryCli)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("coordinator.summary").mcp.handler = (args) => (0, multi_agent_cli_1.coordinatorSummaryCli)(args);
(0, registry_core_1.attachCliBinding)("coordinator.decision", {
path: ["coordinator", "decision"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.coordinatorDecisionCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[0], "run id") }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("coordinator.decision").mcp.handler = (args) => (0, multi_agent_cli_1.coordinatorDecisionCli)(args);
// ---- candidate scoring ----------------------------------------------------
(0, registry_core_1.attachCliBinding)("candidate.list", {
path: ["candidate", "list"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.candidateListCli)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("candidate.list").mcp.handler = (args) => (0, multi_agent_cli_1.candidateListCli)(args);
(0, registry_core_1.attachCliBinding)("candidate.show", {
path: ["candidate", "show"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.candidateShowCli)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options }, (0, io_1.required)(args.positionals[1], "candidate id")) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("candidate.show").mcp.handler = (args) => (0, multi_agent_cli_1.candidateShowCli)(args, (0, io_1.required)((0, io_1.optionalArg)(args.candidateId), "candidate id"));
(0, registry_core_1.attachCliBinding)("candidate.register", {
path: ["candidate", "register"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.candidateRegisterCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[0], "run id") }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("candidate.register").mcp.handler = (args) => (0, multi_agent_cli_1.candidateRegisterCli)(args);
(0, registry_core_1.attachCliBinding)("candidate.score", {
path: ["candidate", "score"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.candidateScoreCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[0], "run id") }, (0, io_1.required)(args.positionals[1], "candidate id")) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("candidate.score").mcp.handler = (args) => (0, multi_agent_cli_1.candidateScoreCli)(args, (0, io_1.required)((0, io_1.optionalArg)(args.candidateId), "candidate id"));
(0, registry_core_1.attachCliBinding)("candidate.rank", {
path: ["candidate", "rank"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.candidateRankCli)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("candidate.rank").mcp.handler = (args) => (0, multi_agent_cli_1.candidateRankCli)(args);
(0, registry_core_1.attachCliBinding)("candidate.select", {
path: ["candidate", "select"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.candidateSelectCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[0], "run id") }, (0, io_1.required)(args.positionals[1], "candidate id")) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("candidate.select").mcp.handler = (args) => (0, multi_agent_cli_1.candidateSelectCli)(args, (0, io_1.required)((0, io_1.optionalArg)(args.candidateId), "candidate id"));
(0, registry_core_1.attachCliBinding)("candidate.reject", {
path: ["candidate", "reject"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.candidateRejectCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[0], "run id") }, (0, io_1.required)(args.positionals[1], "candidate id")) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("candidate.reject").mcp.handler = (args) => (0, multi_agent_cli_1.candidateRejectCli)(args, (0, io_1.required)((0, io_1.optionalArg)(args.candidateId), "candidate id"));
// jsonMode "flag": human `Candidates` panel by default, canonical JSON under
// --json (old build's candidate.summary was flag).
(0, registry_core_1.attachCliBinding)("candidate.summary", {
path: ["candidate", "summary"],
jsonMode: "flag",
handler: (args) => {
const summary = (0, multi_agent_cli_1.candidateSummaryCli)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options });
return { json: summary, text: `${(0, operator_ux_text_1.formatCandidateSummaryText)(summary)}\n` };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("candidate.summary").mcp.handler = (args) => (0, multi_agent_cli_1.candidateSummaryCli)(args);
// ---- collaboration ---------------------------------------------------------
(0, registry_core_1.attachCliBinding)("approve", {
path: ["approve"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.approveCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[1], "run id"), body: args.positionals[3] }, args.positionals[0], args.positionals[2]) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("approve").mcp.handler = (args) => (0, multi_agent_cli_1.approveCli)(args);
(0, registry_core_1.attachCliBinding)("reject", {
path: ["reject"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.rejectCollabCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[1], "run id") }, args.positionals[0], args.positionals[2]) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("reject").mcp.handler = (args) => (0, multi_agent_cli_1.rejectCollabCli)(args);
(0, registry_core_1.attachCliBinding)("comment.add", {
path: ["comment", "add"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.commentAddCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[1], "run id"), body: args.options.body ?? args.positionals[3] }, args.positionals[0], args.positionals[2]) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("comment.add").mcp.handler = (args) => (0, multi_agent_cli_1.commentAddCli)(args);
// jsonMode "flag": human comment list by default, canonical JSON under
// --json (old build's comment.list was flag).
(0, registry_core_1.attachCliBinding)("comment.list", {
path: ["comment", "list"],
jsonMode: "flag",
handler: (args) => {
const report = (0, multi_agent_cli_1.commentListCli)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options });
return { json: report, text: `${(0, collaboration_io_1.formatCommentList)(report)}\n` };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("comment.list").mcp.handler = (args) => (0, multi_agent_cli_1.commentListCli)(args);
(0, registry_core_1.attachCliBinding)("handoff", {
path: ["handoff"],
jsonMode: "default",
// `cw handoff <kind> <run-id> [target-id]` (byte-behavior port of the old
// build's handleHandoff): the FIRST required() is on the target-kind
// positional, so a bare `cw handoff` fails with "Missing target kind" — not
// "Missing run id". The kind check must fire before the run-id read.
handler: (args) => {
const kind = (0, io_1.required)(args.positionals[0], "target kind");
const runId = (0, io_1.required)(args.positionals[1], "run id");
return { json: (0, multi_agent_cli_1.handoffCli)({ ...args.options, runId }, kind, args.positionals[2]) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("handoff").mcp.handler = (args) => (0, multi_agent_cli_1.handoffCli)(args);
// jsonMode "flag": human review-status report by default, canonical JSON
// under --json (old build's review.status was flag).
(0, registry_core_1.attachCliBinding)("review.status", {
path: ["review", "status"],
jsonMode: "flag",
handler: (args) => {
const report = (0, multi_agent_cli_1.reviewStatusCli)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options });
return { json: report, text: `${(0, collaboration_io_1.formatReviewStatus)(report)}\n` };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("review.status").mcp.handler = (args) => (0, multi_agent_cli_1.reviewStatusCli)(args);
(0, registry_core_1.attachCliBinding)("review.policy", {
path: ["review", "policy"],
jsonMode: "default",
handler: (args) => ({ json: (0, multi_agent_cli_1.reviewPolicyCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[0], "run id") }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("review.policy").mcp.handler = (args) => (0, multi_agent_cli_1.reviewPolicyCli)(args);
// ---- eval replay harness ---------------------------------------------------
// eval snapshot|replay|compare|score|gate|report — `jsonMode: flag` so a bare
// call renders the human eval report (formatMultiAgentEval) and `--json`
// prints the result object, matching the old build's eval handler.
(0, registry_core_1.attachCliBinding)("eval.snapshot", {
path: ["eval", "snapshot"],
jsonMode: "flag",
handler: (args) => {
const result = (0, multi_agent_cli_1.evalSnapshotCli)({ runId: (0, io_1.required)(args.positionals[0], "run id"), ...args.options });
return { json: result, text: (0, eval_text_1.formatMultiAgentEval)(result) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("eval.snapshot").mcp.handler = (args) => (0, multi_agent_cli_1.evalSnapshotCli)(args);
(0, registry_core_1.attachCliBinding)("eval.replay", {
path: ["eval", "replay"],
jsonMode: "flag",
handler: (args) => {
const result = (0, multi_agent_cli_1.evalReplayCli)({ snapshot: args.positionals[0], ...args.options });
return { json: result, text: (0, eval_text_1.formatMultiAgentEval)(result) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("eval.replay").mcp.handler = (args) => (0, multi_agent_cli_1.evalReplayCli)(args);
(0, registry_core_1.attachCliBinding)("eval.compare", {
path: ["eval", "compare"],
jsonMode: "flag",
handler: (args) => {
const result = (0, multi_agent_cli_1.evalCompareCli)({ baseline: args.positionals[0], replay: args.positionals[1], ...args.options });
return { json: result, text: (0, eval_text_1.formatMultiAgentEval)(result) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("eval.compare").mcp.handler = (args) => (0, multi_agent_cli_1.evalCompareCli)(args);
(0, registry_core_1.attachCliBinding)("eval.score", {
path: ["eval", "score"],
jsonMode: "flag",
handler: (args) => {
const result = (0, multi_agent_cli_1.evalScoreCli)({ replay: args.positionals[0], ...args.options });
return { json: result, text: (0, eval_text_1.formatMultiAgentEval)(result) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("eval.score").mcp.handler = (args) => (0, multi_agent_cli_1.evalScoreCli)(args);
(0, registry_core_1.attachCliBinding)("eval.gate", {
path: ["eval", "gate"],
jsonMode: "flag",
handler: (args) => {
const gate = (0, multi_agent_cli_1.evalGateCli)({ suite: args.positionals[0], ...args.options });
return { json: gate, text: (0, eval_text_1.formatMultiAgentEval)(gate), exitCode: gate.verdict === "ship" ? undefined : 1 };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("eval.gate").mcp.handler = (args) => (0, multi_agent_cli_1.evalGateCli)(args);
(0, registry_core_1.attachCliBinding)("eval.report", {
path: ["eval", "report"],
jsonMode: "flag",
handler: (args) => {
const result = (0, multi_agent_cli_1.evalReportCli)({ replay: args.positionals[0], ...args.options });
return { json: result, text: (0, eval_text_1.formatMultiAgentEval)(result) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("eval.report").mcp.handler = (args) => (0, multi_agent_cli_1.evalReportCli)(args);
// ---------------------------------------------------------------------
"use strict";
// wiring/capability-table/parity.ts — CLI <-> MCP parity planning + report.
// Split out of core/capability-table.ts, byte-for-byte (extracted with
// sed, not retyped).
Object.defineProperty(exports, "__esModule", { value: true });
exports.CAPABILITY_REGISTRY = void 0;
exports.declaredMcpToolsList = declaredMcpToolsList;
exports.mcpRequiredArgsForTool = mcpRequiredArgsForTool;
exports.declaredCliTokens = declaredCliTokens;
exports.declaredCliHelpTokens = declaredCliHelpTokens;
exports.requiresReason = requiresReason;
exports.isPayloadProbeOptOut = isPayloadProbeOptOut;
exports.payloadIdenticalCapabilities = payloadIdenticalCapabilities;
exports.payloadProbeTargets = payloadProbeTargets;
exports.deferredPayloadProbeCapabilities = deferredPayloadProbeCapabilities;
exports.buildPayloadProbePlan = buildPayloadProbePlan;
exports.payloadProbePlan = payloadProbePlan;
exports.buildParityReport = buildParityReport;
const registry_core_1 = require("./registry-core");
// CLI <-> MCP parity planning + report. Ported from the old flat build's
// src/capability-registry.ts (its single source of parity data) onto this
// table's row shape. Same rule as that file's header: a capability marked
// `payloadIdentical` (the default for `surface: "both"`) MUST return a
// byte-for-byte equal JSON payload from `cw <cmd> --json` and from the
// `cw_<tool>` MCP result (whitespace aside); any divergence is drift. A
// capability reachable on one surface but absent on the other, or an
// undeclared payload divergence, is a release-blocking fail-closed error —
// see scripts/parity-check.js.
//
// `CAPABILITY_REGISTRY` is an alias so callers written against the old
// name (and the two smokes that import this module) find the same array
// under either name; `REGISTRY` stays the primary export other v2 modules
// already use.
exports.CAPABILITY_REGISTRY = registry_core_1.REGISTRY;
/** Read-only, run-less global reads: safe with just `cwd`. */
const GLOBAL_PAYLOAD_PROBE_CAPABILITIES = [
"list",
"app.list",
"topology.list",
"sandbox.list",
"backend.list",
"backend.agent.config.show",
"metrics.summary",
];
/** Read-only reads that need only a planned run id. */
const RUN_PAYLOAD_PROBE_CAPABILITIES = [
"status",
"operator.status",
"operator.report",
"graph",
"report",
"next",
"state.check",
"contract.show",
"node.list",
"node.graph",
"worker.summary",
"candidate.summary",
"feedback.summary",
"commit.summary",
"audit.summary",
"audit.head",
"multi-agent.summary",
"workbench.view",
"metrics.show",
"review.status",
"comment.list",
"run.drive",
"gc.plan",
"gc.verify",
];
/** Capabilities that need bespoke scenario setup (extra args, seeded state,
* a dispatched worker, ...) beyond a bare `cwd`/`runId` — see
* scripts/parity-check.js's `prepareScenarioCli`/`prepareScenarioMcp` and
* `runScenarioCli`/`runScenarioMcp` for the setup + invocation each one
* actually runs. */
const SCENARIO_PAYLOAD_PROBE_CAPABILITIES = [
"plan",
"app.show",
"app.validate",
"app.package",
"topology.show",
"topology.validate",
"topology.apply",
"topology.summary",
"topology.graph",
"summary.refresh",
"summary.show",
"sandbox.show",
"sandbox.validate",
"sandbox.choose",
"sandbox.resolve",
"approve",
"reject",
"comment.add",
"handoff",
"review.policy",
"worker.list",
"worker.show",
"worker.manifest",
"worker.output",
"worker.fail",
"worker.validate",
"candidate.list",
"candidate.show",
"candidate.register",
"candidate.score",
"candidate.rank",
"candidate.select",
"candidate.reject",
"feedback.list",
"feedback.show",
"feedback.collect",
"feedback.task",
"feedback.resolve",
"node.show",
"node.snapshot",
"node.diff",
"node.replay",
"node.replay.verify",
];
/** Payload-identical, both-surface, dual-bound capabilities that are not
* yet safe for the deterministic bootstrap parity probe — each needs
* extra target ids/files, mutates durable state, depends on external
* state, or needs a dedicated fixture beyond cwd/runId. Every entry here
* must actually be a dual-bound (`cli` + `mcp`) row in `REGISTRY` without
* a declared opt-out, or it never reaches the candidate set this list is
* deferring — `buildPayloadProbePlan`'s `invalidClassifications` fails
* closed on a stale entry left behind after a row later grows a real
* probe target or an explicit opt-out. */
const PAYLOAD_PROBE_DEFERRED_GROUPS = [
{
reason: "Not safe for the deterministic bootstrap parity probe yet: this capability needs extra target ids/files, mutates durable state, depends on external state, or needs a dedicated fixture beyond cwd/runId.",
capabilities: [
"dispatch",
"result",
"app.init",
"migration.list",
"migration.check",
"migration.prove",
"multi-agent.run",
"multi-agent.status",
"multi-agent.step",
"multi-agent.blackboard",
"multi-agent.score",
"multi-agent.select",
"multi-agent.summarize",
"multi-agent.graph",
"multi-agent.dependencies",
"multi-agent.failures",
"multi-agent.evidence",
"multi-agent.reasoning",
"multi-agent.run.create",
"multi-agent.run.transition",
"multi-agent.run.show",
"multi-agent.group.create",
"multi-agent.membership.create",
"multi-agent.fanout.create",
"multi-agent.fanin.collect",
"eval.snapshot",
"eval.replay",
"eval.compare",
"eval.score",
"eval.gate",
"eval.report",
"blackboard.summary",
"blackboard.summarize",
"blackboard.graph",
"blackboard.resolve",
"blackboard.topic.create",
"blackboard.message.post",
"blackboard.message.list",
"blackboard.context.put",
"blackboard.artifact.add",
"blackboard.artifact.list",
"blackboard.snapshot",
"coordinator.summary",
"coordinator.decision",
"audit.verify",
"audit.worker",
"audit.provenance",
"audit.multi-agent",
"audit.policy",
"audit.role",
"audit.blackboard",
"audit.judge",
"audit.attest",
"audit.decision",
"backend.show",
"backend.probe",
"run.search",
"run.list",
"run.show",
"run.resume",
"run.archive",
"run.rerun",
"report.verify-bundle",
"report.bundle",
"telemetry.verify",
"history",
],
},
{
// Phase B: the CLI bindings just layered onto these previously
// MCP-only rows make each a real both-surface dual-bound capability, so
// the payload probe now sees them. Each needs a seeded fixture beyond a
// bare cwd/runId — a scheduled task / routine trigger / durable queue
// entry / lease / portable archive on disk, a target entity id
// (role/group/membership/fanout/fanin/schedule/lease id), a workflow-app
// id to scaffold or drive, or a registry index to refresh — so each is
// deferred until a bootstrap fixture is added, exactly like the
// scenario/deferred split the older batches use. `init` also folds into
// app.init on both surfaces (scaffold), so it defers with the app family.
reason: "Not safe for the deterministic bootstrap parity probe yet: this capability needs a seeded fixture beyond cwd/runId (a scheduled task / routine trigger / queue entry / lease / portable archive on disk, a target entity id, or a workflow-app id to scaffold or drive). Both surfaces route through the same shell fn; each defers until a bootstrap fixture seeds its state.",
capabilities: [
"app.run",
"init",
"registry.refresh",
"registry.show",
"queue.add",
"queue.list",
"queue.drain",
"queue.show",
"clones.list",
"orphans.list",
"schedule.create",
"schedule.list",
"schedule.delete",
"schedule.due",
"schedule.complete",
"schedule.pause",
"schedule.resume",
"schedule.run-now",
"schedule.history",
"routine.create",
"routine.list",
"routine.delete",
"routine.fire",
"routine.events",
"sched.plan",
"sched.lease",
"sched.release",
"sched.complete",
"sched.reclaim",
"sched.reset",
"sched.policy.show",
"sched.policy.set",
"run.export",
"run.import",
"run.verify-import",
"run.inspect-archive",
"run.restore",
"multi-agent.reasoning.refresh",
"multi-agent.graph.compact",
"multi-agent.role.create",
"multi-agent.role.show",
"multi-agent.group.show",
"multi-agent.membership.show",
"multi-agent.fanout.show",
"multi-agent.fanin.show",
],
},
];
/** The MCP tool names this registry declares. */
function declaredMcpToolsList() {
return (0, registry_core_1.declaredMcpTools)();
}
/** Required MCP argument groups for a registry-declared tool. */
function mcpRequiredArgsForTool(tool) {
return (0, registry_core_1.findCapabilityByMcpTool)(tool)?.mcp?.requiredArgs ?? [];
}
/** The CLI `case` tokens this registry declares (deduped). */
function declaredCliTokens() {
const tokens = new Set();
for (const cap of registry_core_1.REGISTRY) {
if (!cap.cli)
continue;
for (const token of cap.cli.caseTokens ?? cap.cli.path)
tokens.add(token);
}
return [...tokens].sort();
}
/** The top-level CLI commands that should be visible in `cw help`.
* Subcommands are collapsed to their first token; alias tokens (e.g.
* `audit-run`) stay visible alongside the verb they alias. */
function declaredCliHelpTokens() {
const tokens = new Set();
for (const cap of registry_core_1.REGISTRY) {
if (!cap.cli)
continue;
const subcommandTokens = new Set(cap.cli.path.slice(1));
tokens.add(cap.cli.path[0]);
for (const token of cap.cli.caseTokens || []) {
if (!subcommandTokens.has(token))
tokens.add(token);
}
}
tokens.delete("help");
// `init` is a help-index-only token: v2 folds the standalone `init`
// capability into `app.init` (its cli.path is ["init"] only so the
// dispatcher can still run `cw init`, but `cw help` lists it in the
// frozen "More commands" index line, never as its own per-command help
// row). Mirrors the parity smoke's HELP_INDEX_ONLY_TOKENS set so the
// help-token parity stays balanced.
tokens.delete("init");
// `search` is likewise help-index-only: its row is hiddenFromHelp (it
// never had its own `cw help search` row — only the frozen "More
// commands" index line, which this function does not build), and unlike
// a family such as `clones` it has no visible sibling row sharing the
// "search" first token to contribute it independently. Mirrors the
// parity smoke's HELP_INDEX_ONLY_TOKENS set so the help-token parity
// stays balanced.
tokens.delete("search");
return [...tokens].sort();
}
/** Whether a row MUST carry a reason (surface-specific or payload-divergent). */
function requiresReason(cap) {
if (cap.surface !== "both")
return true;
if (cap.payloadIdentical === false)
return true;
return false;
}
/**
* Whether a `surface:"both"` capability is DOCUMENTED out of the payload-identity
* probe. The probe defaults capabilities IN (every both-surface, dual-bound verb —
* including write/complex-arg verbs) and requires an EXPLICIT, REASONED opt-out to
* fall out of scope. A capability escapes the probe only when it carries BOTH
* `payloadIdentical: false` AND a non-empty `reason`. A bare `payloadIdentical:
* false` with no recorded reason does NOT silently escape — it stays in the probe
* set so the undocumented divergence trips the gate (FAIL CLOSED).
*/
function isPayloadProbeOptOut(cap) {
return cap.payloadIdentical === false && !!(cap.reason && cap.reason.trim());
}
/** Rows for the payload-identity probe. Defaults to EVERY both-surface,
* dual-bound capability (read OR write); a row is excluded only by a
* documented opt-out (`payloadIdentical: false` + a non-empty `reason`) —
* see `isPayloadProbeOptOut`. Fail-closed: an undocumented `payloadIdentical:
* false` stays in scope so its divergence is caught, not silently excused. */
function payloadIdenticalCapabilities() {
return registry_core_1.REGISTRY.filter((cap) => cap.surface === "both" && cap.cli && cap.mcp && !isPayloadProbeOptOut(cap));
}
function payloadProbeTargets() {
return [
...GLOBAL_PAYLOAD_PROBE_CAPABILITIES.map((capability) => ({ capability, kind: "global" })),
...RUN_PAYLOAD_PROBE_CAPABILITIES.map((capability) => ({ capability, kind: "run" })),
...SCENARIO_PAYLOAD_PROBE_CAPABILITIES.map((capability) => ({ capability, kind: "scenario" })),
];
}
function deferredPayloadProbeCapabilities() {
return PAYLOAD_PROBE_DEFERRED_GROUPS.flatMap((group) => group.capabilities.map((capability) => ({ capability, reason: group.reason })));
}
function buildPayloadProbePlan(targets, deferred) {
const candidateIds = new Set(payloadIdenticalCapabilities().map((cap) => cap.capability));
const counts = new Map();
const classified = [...targets.map((entry) => entry.capability), ...deferred.map((entry) => entry.capability)];
for (const capability of classified)
counts.set(capability, (counts.get(capability) || 0) + 1);
const classifiedIds = new Set(classified);
return {
targets,
deferred,
unclassified: [...candidateIds].filter((capability) => !classifiedIds.has(capability)).sort(),
duplicateClassifications: [...counts.entries()]
.filter(([, count]) => count > 1)
.map(([capability]) => capability)
.sort(),
invalidClassifications: [...classifiedIds].filter((capability) => !candidateIds.has(capability)).sort(),
};
}
function payloadProbePlan() {
return buildPayloadProbePlan(payloadProbeTargets(), deferredPayloadProbeCapabilities());
}
function lintRegistry() {
const issues = [];
const seenCaps = new Set();
const seenTools = new Set();
for (const cap of registry_core_1.REGISTRY) {
if (seenCaps.has(cap.capability))
issues.push(`duplicate capability id: ${cap.capability}`);
seenCaps.add(cap.capability);
if (cap.mcp) {
if (seenTools.has(cap.mcp.tool))
issues.push(`duplicate MCP tool: ${cap.mcp.tool}`);
seenTools.add(cap.mcp.tool);
}
// NOTE (v2 build-order difference from the old flat registry): the old
// build's registry was written all at once, so a "both" row ALWAYS had
// both bindings the moment it was declared. This table is built up
// MILESTONE BY MILESTONE (this file's header note): `REGISTRY` starts
// from the full, literal 196-tool `mcp` surface (SPEC/mcp.md) and each
// milestone LAYERS a `cli` binding onto the rows it wires next — so a
// "both" row with an `mcp` binding but no `cli` binding YET is the
// expected, honest mid-rollout state, not a lint error. `cli` bindings
// are added without ever touching `surface`, so the lint only fails
// closed on what is actually impossible: a `mcp` binding must always
// exist for "both" (every row starts from `MCP_TOOL_DATA`), and
// `cli-only`/`mcp-only` rows must carry exactly the one binding their
// name promises.
if (cap.surface === "both" && !cap.mcp) {
issues.push(`${cap.capability}: surface "both" requires an mcp binding`);
}
if (cap.surface === "cli-only" && (cap.mcp || !cap.cli)) {
issues.push(`${cap.capability}: surface "cli-only" requires a cli binding and no mcp binding`);
}
if (cap.surface === "mcp-only" && (cap.cli || !cap.mcp)) {
issues.push(`${cap.capability}: surface "mcp-only" requires an mcp binding and no cli binding`);
}
}
return issues;
}
/**
* Compare the declared registry against the ACTUAL surfaces and report every
* fail-closed gap. `mcpTools` is the live `tools/list` result; `cliTokens` is the
* set of `case "<token>"` strings parsed from the CLI source.
*/
function buildParityReport(input) {
const declaredTools = new Set((0, registry_core_1.declaredMcpTools)());
const actualTools = new Set(input.mcpTools);
const declaredTokens = new Set(declaredCliTokens());
const actualTokens = new Set(input.cliTokens);
const declaredHelpTokens = new Set(declaredCliHelpTokens());
const actualHelpTokens = new Set(input.helpTokens || []);
const missingMcpTools = [...declaredTools].filter((tool) => !actualTools.has(tool)).sort();
const undeclaredMcpTools = [...actualTools].filter((tool) => !declaredTools.has(tool)).sort();
const missingCliTokens = [...declaredTokens].filter((token) => !actualTokens.has(token)).sort();
const undeclaredCliTokens = [...actualTokens].filter((token) => !declaredTokens.has(token)).sort();
const helpMissingCliTokens = input.helpTokens
? [...declaredHelpTokens].filter((token) => !actualHelpTokens.has(token)).sort()
: [];
const helpUndeclaredCliTokens = input.helpTokens
? [...actualHelpTokens].filter((token) => !declaredHelpTokens.has(token)).sort()
: [];
const reasonlessExceptions = registry_core_1.REGISTRY.filter((cap) => requiresReason(cap) && !(cap.reason && cap.reason.trim()))
.map((cap) => cap.capability)
.sort();
const payloadPlan = payloadProbePlan();
const registryLint = lintRegistry();
const ok = missingMcpTools.length === 0 &&
undeclaredMcpTools.length === 0 &&
missingCliTokens.length === 0 &&
undeclaredCliTokens.length === 0 &&
helpMissingCliTokens.length === 0 &&
helpUndeclaredCliTokens.length === 0 &&
reasonlessExceptions.length === 0 &&
payloadPlan.unclassified.length === 0 &&
payloadPlan.duplicateClassifications.length === 0 &&
payloadPlan.invalidClassifications.length === 0 &&
registryLint.length === 0;
return {
ok,
registrySize: registry_core_1.REGISTRY.length,
missingMcpTools,
undeclaredMcpTools,
missingCliTokens,
undeclaredCliTokens,
helpMissingCliTokens,
helpUndeclaredCliTokens,
reasonlessExceptions,
payloadProbeUnclassified: payloadPlan.unclassified,
payloadProbeDuplicateClassifications: payloadPlan.duplicateClassifications,
payloadProbeInvalidClassifications: payloadPlan.invalidClassifications,
registryLint,
};
}
"use strict";
// wiring/capability-table/pipeline.ts — MILESTONE 6+7 (plan, run.drive*,
// dispatch, result, commit, commit.summary) + MILESTONE 11's run.export/
// import/verify-import/inspect-archive/restore CLI bindings. Split out of
// core/capability-table.ts, byte-for-byte (extracted with sed, not
// retyped).
Object.defineProperty(exports, "__esModule", { value: true });
const registry_core_1 = require("./registry-core");
const io_1 = require("../../cli/io");
const io_2 = require("../../cli/io");
// MILESTONE 6+7 (combined; see docs/rebuild/PLAN.md Open risk 10) CLI bindings:
// plan, quickstart, run --drive, run drive (preview), dispatch, result,
// commit. Handler BODIES live in shell/pipeline-cli.ts (impure — they
// plan/drive/dispatch/commit real run state on disk); this table only
// wires argv shape -> handler call, per cli/dispatch.ts's generic
// executor contract.
// ---------------------------------------------------------------------
const pipeline_cli_1 = require("../../shell/pipeline-cli");
const commit_summary_1 = require("../../shell/commit-summary");
(0, registry_core_1.attachCliBinding)("plan", {
path: ["plan"],
jsonMode: "default",
handler: (args) => {
const workflowId = (0, io_2.optionalArg)(args.positionals[0]);
if (!workflowId) {
throw new Error('Missing workflow id.\n Tip: plan an architecture review with "cw plan architecture-review"');
}
return { json: (0, pipeline_cli_1.planRun)({ ...args.options, workflowId }) };
},
});
// `cw run <app> --drive [--once]` and `cw run drive <run-id> [--step]`
// share the single `run` dispatch path (byte-exact to the old build's
// handleRun: a run-REGISTRY subcommand keyword is never hijacked by the
// bare `--drive` intercept just because it carries its own --drive/--step
// flag). Both rows below dispatch on `["run"]`; the FIRST one registered
// (run.drive.step) is found first by findCapabilityByCliPath's linear
// scan, so its handler carries the full branch — the second row exists
// only so `cw help run` lists both capabilities.
(0, registry_core_1.attachCliBinding)("run.drive.step", {
path: ["run"],
helpPath: ["run", "drive"],
jsonMode: "default",
handler: (args) => {
const registrySubcommands = new Set(["drive", "search", "list", "show", "resume", "archive", "rerun", "export", "import", "verify-import", "inspect-archive", "restore"]);
const target = args.positionals[0];
if (args.options.drive && !registrySubcommands.has(String(target || ""))) {
const runId = (0, io_2.optionalArg)(args.options.run) || (0, io_2.optionalArg)(args.options.runId);
if (args.options.preview)
return { json: (0, pipeline_cli_1.runDrivePreview)({ ...args.options, runId: runId || target }) };
const driveArgs = { ...args.options };
if (runId)
driveArgs.runId = runId;
else
driveArgs.appId = target;
return { json: (0, pipeline_cli_1.runDriveStep)(driveArgs) };
}
const [subcommand, id] = args.positionals;
if (subcommand === "drive") {
if (args.options.step) {
const driveArgs = { ...args.options };
if (id)
driveArgs.runId = id;
return { json: (0, pipeline_cli_1.runDriveStep)(driveArgs) };
}
return { json: (0, pipeline_cli_1.runDrivePreview)({ ...args.options, runId: (0, io_1.required)(id, "run id") }) };
}
// MILESTONE 11 (reporting/run-export) — the archive family. Handler
// bodies live in shell/run-export-cli.ts; this arm only wires argv
// shape -> handler call.
if (subcommand === "export") {
const result = (0, run_export_cli_1.runExportCli)((0, io_1.required)(id, "run id"), args.options);
return { json: result };
}
if (subcommand === "import") {
const result = (0, run_export_cli_1.runImportCli)((0, io_1.required)(id, "archive path"), args.options);
return { json: result };
}
if (subcommand === "verify-import") {
const result = (0, run_export_cli_1.runVerifyImportCli)((0, io_1.required)(id, "run id"), args.options);
return { json: result, exitCode: args.options.strict && !result.ok ? 1 : undefined };
}
if (subcommand === "inspect-archive") {
const result = (0, run_export_cli_1.runInspectArchiveCli)((0, io_1.required)(id, "archive path"), args.options);
return { json: result, exitCode: result.ok ? undefined : 1 };
}
if (subcommand === "restore") {
const result = (0, run_export_cli_1.runRestoreCli)((0, io_1.required)(id, "archive path"), args.options);
return { json: result, exitCode: result.ok ? undefined : 1 };
}
throw new Error("Usage: cw.js run search|list|show|resume|archive|rerun|drive|export|import|verify-import|inspect-archive|restore [run-id|archive] [--scope repo|home] [--json] | cw.js run <app> --drive [--once] [--incremental] [--repo R --question Q]");
},
});
// PARITY: `run.drive` (the read-only MCP preview tool) now ALSO carries
// its own two-token `cli.path` ["run","drive"], same as the old build's
// registry row (cli.path ["run","drive"]) and the same reversed-
// candidate-order pattern already used for run.search/run.list/etc.
// (see those rows' own comment below): findCapabilityByCliPath tries the
// 2-token candidate BEFORE the 1-token ["run"] row, so this row — not
// run.drive.step's combined switch — now serves `cw run drive <run-id>`
// [--step]. Behavior is unchanged (same runDrivePreview/runDriveStep
// calls, same --step branch run.drive.step's switch already used); only
// WHICH row answers the dispatch changes, so run.drive becomes a real
// both-surface, dual-bound capability for the payload-identity probe.
(0, registry_core_1.attachCliBinding)("run.drive", {
path: ["run", "drive"],
jsonMode: "default",
handler: (args) => {
const id = args.positionals[0];
if (args.options.step) {
const driveArgs = { ...args.options };
if (id)
driveArgs.runId = id;
return { json: (0, pipeline_cli_1.runDriveStep)(driveArgs) };
}
return { json: (0, pipeline_cli_1.runDrivePreview)({ ...args.options, runId: (0, io_1.required)(id, "run id") }) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("run.drive").mcp.handler = (args) => (0, pipeline_cli_1.runDrivePreview)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("run.drive.step").mcp.handler = (args) => (0, pipeline_cli_1.runDriveStep)(args);
// PARITY: `run.drive.step` advances the run by spawning the external
// agent per worker and recording attested output — not a read probe.
// CLI (--drive/--step) and MCP route through the same drive() core; the
// opt-out is the documented divergence, not undocumented drift.
registry_core_1.REGISTRY_BY_CAPABILITY.get("run.drive.step").payloadIdentical = false;
registry_core_1.REGISTRY_BY_CAPABILITY.get("run.drive.step").reason =
"Mutating: advances the run by spawning the external agent per worker and recording attested output — not a read probe. CLI (--drive/--step) and MCP route through the same drive() core.";
registry_core_1.REGISTRY_BY_CAPABILITY.get("plan").mcp.handler = (args) => (0, pipeline_cli_1.planRun)(args);
// GAP #24: dispatchRun reads the sandbox profile from `args.sandbox` only
// (the CLI's --sandbox flag). The cw_dispatch MCP tool also accepts the
// `sandboxProfile`/`sandboxProfileId` aliases (its declared properties), so
// normalize them onto `sandbox` here — mirrors the old build's
// sandboxProfileIdFrom() alias set — before handing off.
registry_core_1.REGISTRY_BY_CAPABILITY.get("dispatch").mcp.handler = (args) => (0, pipeline_cli_1.dispatchRun)({ ...args, sandbox: args.sandbox ?? args.sandboxProfile ?? args.sandboxProfileId });
registry_core_1.REGISTRY_BY_CAPABILITY.get("result").mcp.handler = (args) => (0, pipeline_cli_1.recordResultRun)(args);
// `cw_commit` returns the FLAT commit envelope (verifierGated/checkpoint/
// selectionId/… at the top level, plus a nested `commit`), matching the old
// build's commitEnvelope. `commitRun` (CLI shape) returns `{ runId, commit }`;
// lift the commit's key fields to the top for the MCP surface.
registry_core_1.REGISTRY_BY_CAPABILITY.get("commit").mcp.handler = (args) => {
const result = (0, pipeline_cli_1.commitRun)(args);
const commit = result.commit || {};
return {
runId: result.runId,
commitId: commit.id,
verifierGated: commit.verifierGated,
checkpoint: commit.checkpoint,
verifierNodeId: commit.verifierNodeId,
candidateId: commit.candidateId,
selectionId: commit.selectionId,
evidenceCount: Array.isArray(commit.evidence) ? commit.evidence.length : 0,
snapshotPath: commit.snapshotPath,
commit,
};
};
// PARITY: `commit` is the one declared payload projection (byte-compat
// item 5 above). Both surfaces route through the single core entry
// runner.commit (commitRun); the CLI keeps the raw StateCommitResult for
// scripting while cw_commit lifts an operator-facing envelope on top (see
// the mcp.handler just above). Marked here, not silently let drift, so
// the parity payload probe (core/capability-table.ts's
// payloadIdenticalCapabilities) skips it with a paper trail instead of
// tripping on an undocumented divergence.
registry_core_1.REGISTRY_BY_CAPABILITY.get("commit").payloadIdentical = false;
registry_core_1.REGISTRY_BY_CAPABILITY.get("commit").reason =
"Both surfaces route through the single core entry runner.commit. The CLI emits the raw StateCommitResult for scripting (commit.id, commit.evidence, commit.gate); cw_commit emits the operator commit envelope (commitId, verifierGated, checkpoint, evidenceCount, snapshotPath, nextActions, plus the raw result under `commit`). Declared projection, not drift.";
// MILESTONE 11 — run.export/import/verify-import/inspect-archive/restore
// MCP handlers (the CLI side is served by run.drive.step's combined
// handler above; these tools are called directly by name over MCP, so
// each needs its own mcp.handler per byte-compat item 5's two-field
// row shape).
const run_export_cli_1 = require("../../shell/run-export-cli");
registry_core_1.REGISTRY_BY_CAPABILITY.get("run.export").mcp.handler = (args) => (0, run_export_cli_1.runExportCli)((0, io_1.required)((0, io_2.optionalArg)(args.runId), "run id"), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("run.import").mcp.handler = (args) => (0, run_export_cli_1.runImportCli)((0, io_1.required)((0, io_2.optionalArg)(args.archive || args.path || args.file), "archive path"), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("run.verify-import").mcp.handler = (args) => (0, run_export_cli_1.runVerifyImportCli)((0, io_1.required)((0, io_2.optionalArg)(args.runId), "run id"), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("run.inspect-archive").mcp.handler = (args) => (0, run_export_cli_1.runInspectArchiveCli)((0, io_1.required)((0, io_2.optionalArg)(args.archive || args.path || args.file), "archive path"), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("run.restore").mcp.handler = (args) => (0, run_export_cli_1.runRestoreCli)((0, io_1.required)((0, io_2.optionalArg)(args.archive || args.path || args.file), "archive path"), args);
// `run export|import|verify-import|inspect-archive|restore` each carry their
// own two-token cli.path (found before the ["run"] run.drive.step catch-all
// per the reversed candidate order), calling the same shell fns with the
// same [subcommand, id] positional mapping and exit-code shape the catch-all
// switch already used. `hiddenFromHelp` keeps the byte-pinned `cw help run`
// fixture's rows coming from the single literal COMMAND_HELP_ROWS.run block.
(0, registry_core_1.attachCliBinding)("run.export", {
path: ["run", "export"],
jsonMode: "default",
hiddenFromHelp: true,
handler: (args) => ({ json: (0, run_export_cli_1.runExportCli)((0, io_1.required)(args.positionals[0], "run id"), args.options) }),
});
(0, registry_core_1.attachCliBinding)("run.import", {
path: ["run", "import"],
jsonMode: "default",
hiddenFromHelp: true,
handler: (args) => ({ json: (0, run_export_cli_1.runImportCli)((0, io_1.required)(args.positionals[0], "archive path"), args.options) }),
});
(0, registry_core_1.attachCliBinding)("run.verify-import", {
path: ["run", "verify-import"],
jsonMode: "default",
hiddenFromHelp: true,
handler: (args) => {
const result = (0, run_export_cli_1.runVerifyImportCli)((0, io_1.required)(args.positionals[0], "run id"), args.options);
return { json: result, exitCode: args.options.strict && !result.ok ? 1 : undefined };
},
});
(0, registry_core_1.attachCliBinding)("run.inspect-archive", {
path: ["run", "inspect-archive"],
jsonMode: "default",
hiddenFromHelp: true,
handler: (args) => {
const result = (0, run_export_cli_1.runInspectArchiveCli)((0, io_1.required)(args.positionals[0], "archive path"), args.options);
return { json: result, exitCode: result.ok ? undefined : 1 };
},
});
(0, registry_core_1.attachCliBinding)("run.restore", {
path: ["run", "restore"],
jsonMode: "default",
hiddenFromHelp: true,
handler: (args) => {
const result = (0, run_export_cli_1.runRestoreCli)((0, io_1.required)(args.positionals[0], "archive path"), args.options);
return { json: result, exitCode: result.ok ? undefined : 1 };
},
});
(0, registry_core_1.addCliOnlyCapability)("quickstart", "ONE-COMMAND quickstart: --check preflights without writes; otherwise plan(app, default architecture-review) -> run --drive -> report in a single invocation (--preview for a read-only dry run; --bundle [--with-trust-key K] seals a completed run into a self-verified portable bundle).", {
path: ["quickstart"],
// `audit-run` is a CLI-only alias that dispatches to the same quickstart
// wrapper (byte-behavior port of the old build's caseTokens).
caseTokens: ["quickstart", "audit-run"],
jsonMode: "default",
handler: (args) => {
const appId = (0, io_2.optionalArg)(args.positionals[0]);
const result = (0, pipeline_cli_1.quickstartRun)({ ...args.options, appId });
// Fail closed on both known bad outcomes: a --check preflight that
// found a blocking gap, OR a --bundle that did not self-verify.
const bundle = result.bundle;
const bundleFailed = Boolean(bundle && bundle.ok === false);
const exitCode = (result.mode === "check" && result.ok === false) || bundleFailed ? 1 : undefined;
return { json: result, exitCode };
},
}, "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");
(0, registry_core_1.attachCliBinding)("dispatch", {
path: ["dispatch"],
jsonMode: "default",
handler: (args) => {
const runId = (0, io_1.required)((0, io_2.optionalArg)(args.positionals[0]), "run id");
return { json: (0, pipeline_cli_1.dispatchRun)({ ...args.options, runId }) };
},
});
(0, registry_core_1.attachCliBinding)("result", {
path: ["result"],
jsonMode: "default",
handler: (args) => {
const runId = (0, io_1.required)((0, io_2.optionalArg)(args.positionals[0]), "run id");
const taskId = (0, io_1.required)((0, io_2.optionalArg)(args.positionals[1]), "task id");
const resultPath = (0, io_1.required)((0, io_2.optionalArg)(args.positionals[2]), "result file path");
return { json: (0, pipeline_cli_1.recordResultRun)({ ...args.options, runId, taskId, resultPath }) };
},
});
(0, registry_core_1.attachCliBinding)("commit", {
path: ["commit"],
jsonMode: "default",
handler: (args) => {
const runId = (0, io_1.required)((0, io_2.optionalArg)(args.positionals[0]), "run id");
return { json: (0, pipeline_cli_1.commitRun)({ ...args.options, runId }) };
},
});
// GAP #26: restore `cw commit summary <run-id>` (CLI + help row). The old
// build had commit.summary with cli path ["commit","summary"] surface "both"
// (capability-registry.ts:260-266); v2 kept only the cw_commit_summary MCP
// tool and dropped both the CLI binding and the COMMAND_HELP_ROWS entry, so
// `cw commit summary` mis-read "summary" as the run id. Path ["commit","summary"]
// is 2 tokens; dispatch consumes 1, so positionals[0] is the run id.
(0, registry_core_1.attachCliBinding)("commit.summary", {
path: ["commit", "summary"],
jsonMode: "flag",
handler: (args) => {
const summary = (0, commit_summary_1.commitSummaryCli)({ ...args.options, runId: (0, io_1.required)(args.positionals[0], "run id") });
return { json: summary, text: `${(0, commit_summary_1.formatCommitSummaryText)(summary)}\n` };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("commit.summary").mcp.handler = (args) => (0, commit_summary_1.commitSummaryCli)(args);
// ---------------------------------------------------------------------
"use strict";
// wiring/capability-table/registry-core.ts — the shared machinery every
// domain slice registers into: REGISTRY, REGISTRY_BY_CAPABILITY,
// attachCliBinding, addCliOnlyCapability, and the read-only query
// functions (findCapability*, cliCapabilities, mcpToolDefinitions,
// declaredMcpTools). Also owns the small set of capability BODIES that
// must be in scope when MCP_TOOL_DATA.map() builds REGISTRY at module
// load (MCP_REAL_HANDLERS below) — kept here, not in a domain slice, to
// avoid a circular import (a slice needing attachCliBinding from this
// file, while this file would need a handler body FROM that slice).
//
// No slice file imports another slice file; every slice imports ONLY
// from this file, core/capability-data.ts, and shell/core as needed.
// index.ts imports this file plus every slice and composes them in the
// exact original source order (REGISTRY order is a pinned behavior —
// tools/list order, gen-parity-doc's byte-diff gate, cw help line order).
//
// Split out of core/capability-table.ts's "Public table-derived API"
// section, byte-for-byte (this file's body is the ORIGINAL file's own
// text, extracted with sed line ranges, not retyped).
Object.defineProperty(exports, "__esModule", { value: true });
exports.REGISTRY_BY_CAPABILITY = exports.REGISTRY = void 0;
exports.listBundledWorkflows = listBundledWorkflows;
exports.listBundledSandboxProfiles = listBundledSandboxProfiles;
exports.statusPayload = statusPayload;
exports.attachCliBinding = attachCliBinding;
exports.addCliOnlyCapability = addCliOnlyCapability;
exports.findCapability = findCapability;
exports.findCapabilityByCliPath = findCapabilityByCliPath;
exports.cliCapabilities = cliCapabilities;
exports.mcpToolDefinitions = mcpToolDefinitions;
exports.declaredMcpTools = declaredMcpTools;
exports.findCapabilityByMcpTool = findCapabilityByMcpTool;
const capability_data_1 = require("../../core/capability-data");
const io_1 = require("../../cli/io");
const run_store_1 = require("../../shell/run-store");
const operator_ux_1 = require("../../shell/operator-ux");
const workflow_app_loader_1 = require("../../shell/workflow-app-loader");
const state_explosion_cli_1 = require("../../shell/state-explosion-cli");
/** Real handlers implemented at THIS milestone, keyed by capability id.
* Every tool row not listed here gets `notYetImplemented`. Kept as a
* small side table (rather than inlined into MCP_TOOL_DATA above) so the
* 196-row literal above stays a pure, mechanically-checkable transcript
* of the spec table — handler wiring is a separate, obviously-later-
* editable concern. */
const MCP_REAL_HANDLERS = {
list: () => listBundledWorkflows(),
"sandbox.list": () => listBundledSandboxProfiles(),
status: (args) => statusPayload(optionalString(args.runId)),
"summary.refresh": (args) => (0, state_explosion_cli_1.summaryRefreshCli)((0, io_1.required)(optionalString(args.runId), "run id"), args),
"summary.show": (args) => (0, state_explosion_cli_1.summaryShowCli)((0, io_1.required)(optionalString(args.runId), "run id"), args),
};
function optionalString(value) {
return typeof value === "string" && value.trim() ? value.trim() : undefined;
}
/** `cw list` / `cw_list` (MILESTONE 12) — the real discovery over every
* `apps/*\/app.json` + legacy `workflows/*.workflow.js` on disk, per
* `listWorkflowsShallow` (shell/workflow-app-loader.ts). */
function listBundledWorkflows() {
return (0, workflow_app_loader_1.listWorkflowsShallow)();
}
/** PLACEHOLDER (milestone 5, execution-backend/sandbox) — the real
* `sandbox.list` resolves and stamps each of the 4 bundled profiles
* (default/readonly/workspace-write/locked-down) with real path lists
* via `resolveSandboxProfile` (SPEC/execution-backend.md). This
* milestone reproduces only the id/title/schemaVersion subset that
* mcp-basic.case.js checks. */
function listBundledSandboxProfiles() {
return [
{ schemaVersion: 1, id: "default", title: "Default Worker Boundary" },
{ schemaVersion: 1, id: "readonly", title: "Readonly Workspace" },
{ schemaVersion: 1, id: "workspace-write", title: "Workspace Write" },
{ schemaVersion: 1, id: "locked-down", title: "Locked Down" },
];
}
/** `cw status` / `cw_status` — SPEC/cli-surface.md pins the no-id JSON
* shape exactly (`{runId:null, nextActions}`); a real run id resolves to
* `summarizeRun`'s payload (MILESTONE 11, reporting/observability). */
function statusPayload(runId, cwd) {
if (!runId) {
return { runId: null, nextActions: (0, operator_ux_1.adviseNoRun)() };
}
const run = (0, run_store_1.loadRunFromCwd)(runId, cwd || process.cwd());
return (0, operator_ux_1.summarizeRun)(run);
}
// ---------------------------------------------------------------------
// Public table-derived API
// ---------------------------------------------------------------------
function buildMcpBinding(row) {
const handler = MCP_REAL_HANDLERS[row.capability] ?? (0, capability_data_1.notYetImplemented)(row.capability);
// A transcript entry like "runId, workerId" is TWO AND-required args (the
// spec table's comma form), while "topicId|id" is one OR-group. mcp/dispatch's
// validator only splits on `|`, so expand each comma-joined transcript entry
// into its separate AND-groups here (the one place the row shape is turned
// into the runtime McpBinding.requiredArgs contract).
const requiredArgs = row.requiredArgs.flatMap((group) => group.split(",").map((entry) => entry.trim()).filter(Boolean));
return {
tool: row.tool,
requiredArgs: requiredArgs.length ? requiredArgs : undefined,
properties: row.properties,
description: row.description,
handler,
};
}
/** The full capability table: one row per MCP tool (196, per SPEC/mcp.md),
* in the exact source order `tools/list` must report. CLI bindings are
* layered on top for the small set of capabilities this milestone also
* exposes on the CLI front door (see `CLI_ROWS` below); every other row
* is MCP-only AT THIS MILESTONE (not a permanent `mcp-only` declaration —
* just not yet CLI-wired; later milestones add the `cli` binding without
* touching this array's mcp side). */
exports.REGISTRY = capability_data_1.MCP_TOOL_DATA.map((row) => ({
capability: row.capability,
summary: row.description,
surface: "both",
mcp: buildMcpBinding(row),
}));
exports.REGISTRY_BY_CAPABILITY = new Map(exports.REGISTRY.map((row) => [row.capability, row]));
/** Attach (or replace) a CLI binding for an already-declared MCP capability.
* Used once below to wire `list`/`status`/`sandbox.list` onto the CLI
* front door too, without duplicating their row data. */
function attachCliBinding(capability, cli) {
const row = exports.REGISTRY_BY_CAPABILITY.get(capability);
if (!row)
throw new Error(`capability-table: cannot attach cli binding to undeclared capability ${capability}`);
row.cli = cli;
}
/** Declare a capability that is CLI-only at this milestone (`help`,
* `version` — both are permanently `cli-only` per SPEC/mcp.md's
* declared one-surface list, so no mcp row is created for them). */
function addCliOnlyCapability(capability, summary, cli, reason, entry) {
const row = { capability, summary, surface: "cli-only", cli, reason, ...(entry ? { entry } : {}) };
exports.REGISTRY.push(row);
exports.REGISTRY_BY_CAPABILITY.set(capability, row);
}
/** Returns the declared row for a capability id, or undefined. */
function findCapability(capability) {
return exports.REGISTRY_BY_CAPABILITY.get(capability);
}
/** Returns the declared row whose `cli.path` matches `path` exactly
* (path[0] is the verb). Used by cli/dispatch.ts's generic executor.
* A single-token command also matches a row's `caseTokens` alias list, so
* an alias (e.g. `audit-run`) dispatches to the same handler as its verb. */
function findCapabilityByCliPath(path) {
for (const row of exports.REGISTRY) {
if (row.cli && row.cli.path.length === path.length && row.cli.path.every((p, i) => p === path[i])) {
return row;
}
}
if (path.length === 1) {
for (const row of exports.REGISTRY) {
if (row.cli && row.cli.caseTokens && row.cli.caseTokens.includes(path[0]))
return row;
}
}
return undefined;
}
/** Every capability row that declares a `cli` binding, in registry order.
* Used to derive `formatCommandHelp`'s per-verb subcommand rows. */
function cliCapabilities() {
return exports.REGISTRY.filter((row) => Boolean(row.cli));
}
/** `tools/list`'s exact array, in the pinned source order. */
function mcpToolDefinitions() {
const definitions = [];
for (const row of exports.REGISTRY) {
if (!row.mcp)
continue;
const overrides = capability_data_1.PROPERTY_OVERRIDES[row.mcp.tool] ?? {};
const properties = {};
for (const propName of row.mcp.properties) {
properties[propName] = overrides[propName] ?? (0, capability_data_1.stringProperty)(propName);
}
definitions.push({
name: row.mcp.tool,
description: row.mcp.description,
inputSchema: { type: "object", properties, additionalProperties: true },
});
}
return definitions;
}
/** Every declared MCP tool name, in `tools/list` order. */
function declaredMcpTools() {
return exports.REGISTRY.filter((row) => row.mcp).map((row) => row.mcp.tool);
}
/** Look up a capability row by its MCP tool name. */
function findCapabilityByMcpTool(tool) {
return exports.REGISTRY.find((row) => row.mcp && row.mcp.tool === tool);
}
"use strict";
// wiring/capability-table/reporting.ts — MILESTONE 11 (reporting,
// observability, doctor/fix, workbench, run summary/status/operator,
// worker.*, feedback.*, audit.*) CLI bindings. Split out of
// core/capability-table.ts, byte-for-byte (extracted with sed, not
// retyped).
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const registry_core_1 = require("./registry-core");
const io_1 = require("../../cli/io");
const state_explosion_text_1 = require("../../core/format/state-explosion-text");
const state_explosion_cli_1 = require("../../shell/state-explosion-cli");
const run_store_1 = require("../../shell/run-store");
// MILESTONE 11 (reporting, observability, doctor/fix, workbench, run
// export/bundle) CLI bindings: report, status (real run id), graph,
// operator.status|report|graph. Handler bodies live in shell/report-
// view-cli.ts (impure — they load run state and, for `report`/`operator
// report`, re-write report.md); this table only wires argv shape ->
// handler call, per cli/dispatch.ts's generic executor contract.
// ---------------------------------------------------------------------
const report_view_cli_1 = require("../../shell/report-view-cli");
const operator_ux_text_1 = require("../../shell/operator-ux-text");
(0, registry_core_1.attachCliBinding)("report", {
path: ["report"],
jsonMode: "flag",
handler: (args) => {
const runId = (0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]), "run id");
const result = (0, report_view_cli_1.reportWriteCli)(runId, args.options);
if (args.options.show || args.options.summary) {
const stateExplosion = (0, state_explosion_text_1.formatStateExplosionReport)((0, state_explosion_cli_1.summaryShowCli)(runId, args.options));
return { json: result, text: `${(0, report_view_cli_1.operatorReportText)(runId, args.options)}\n\n${stateExplosion}\n` };
}
return { json: result, text: `${result.path}\n` };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("report").mcp.handler = (args) => (0, report_view_cli_1.reportWriteCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
// `status` already carries a milestone-2 CLI binding (`attachCliBinding("status", ...)`
// above); replace its handler here with the real run-id-aware body while
// keeping the same row/path (no reshape needed — see byte-compat item 5).
registry_core_1.REGISTRY_BY_CAPABILITY.get("status").cli = {
path: ["status"],
jsonMode: "flag",
handler: (args) => {
const runId = (0, io_1.optionalArg)(args.positionals[0]);
if (!runId)
return { json: (0, report_view_cli_1.statusCli)(undefined, args.options), text: `No run selected\n\nNext Action\n${adviseNoRunLines()}` };
if (args.options.summary || args.options.brief) {
return { json: (0, report_view_cli_1.statusCli)(runId, args.options), text: `${(0, report_view_cli_1.statusSummaryText)(runId, args.options)}\n` };
}
return { json: (0, report_view_cli_1.statusCli)(runId, args.options), text: `${(0, report_view_cli_1.statusFullText)(runId, args.options)}\n` };
},
};
registry_core_1.REGISTRY_BY_CAPABILITY.get("status").mcp.handler = (args) => (0, report_view_cli_1.statusCli)((0, io_1.optionalArg)(args.runId), args);
function adviseNoRunLines() {
return " node scripts/cw.js plan <workflow-id> --repo <path>\n reason: No run id is available yet; create a workflow run before dispatching or recording evidence.\n";
}
(0, registry_core_1.attachCliBinding)("graph", {
path: ["graph"],
jsonMode: "flag",
handler: (args) => {
const runId = (0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]), "run id");
return { json: (0, report_view_cli_1.graphCli)(runId, args.options), text: `${(0, report_view_cli_1.graphText)(runId, args.options)}\n` };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("graph").mcp.handler = (args) => (0, report_view_cli_1.graphCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
(0, registry_core_1.attachCliBinding)("operator.status", {
path: ["operator", "status"],
jsonMode: "flag",
handler: (args) => {
const runId = (0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]), "run id");
if (args.options.summary || args.options.brief) {
return { json: (0, report_view_cli_1.operatorStatusCli)(runId, args.options), text: `${(0, report_view_cli_1.statusSummaryText)(runId, args.options)}\n` };
}
return { json: (0, report_view_cli_1.operatorStatusCli)(runId, args.options), text: `${(0, report_view_cli_1.statusFullText)(runId, args.options)}\n` };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("operator.status").mcp.handler = (args) => (0, report_view_cli_1.operatorStatusCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
(0, registry_core_1.attachCliBinding)("operator.report", {
path: ["operator", "report"],
jsonMode: "flag",
handler: (args) => {
const runId = (0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]), "run id");
return { json: (0, report_view_cli_1.operatorReportCli)(runId, args.options), text: `${(0, report_view_cli_1.operatorReportText)(runId, args.options)}\n` };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("operator.report").mcp.handler = (args) => (0, report_view_cli_1.operatorReportCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
// ---- metrics.show / metrics.summary -----------------------------------
const metrics_cli_1 = require("../../shell/metrics-cli");
const observability_1 = require("../../shell/observability");
(0, registry_core_1.attachCliBinding)("metrics.show", {
path: ["metrics", "show"],
jsonMode: "flag",
handler: (args) => {
const runId = (0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]), "run id");
const report = (0, metrics_cli_1.metricsShowCli)(runId, args.options);
return { json: report, text: `${(0, observability_1.formatMetricsReport)(report)}\n` };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("metrics.show").mcp.handler = (args) => (0, metrics_cli_1.metricsShowCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
(0, registry_core_1.attachCliBinding)("metrics.summary", {
path: ["metrics", "summary"],
jsonMode: "flag",
handler: (args) => {
const report = (0, metrics_cli_1.metricsSummaryCli)(args.options);
return { json: report, text: `${(0, observability_1.formatMetricsSummary)(report)}\n` };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("metrics.summary").mcp.handler = (args) => (0, metrics_cli_1.metricsSummaryCli)(args);
// ---- worker.summary (the workbench worker panel + `cw worker summary`) ----
const worker_isolation_1 = require("../../shell/worker-isolation");
const workerPath = __importStar(require("node:path"));
function workerSummaryCli(args) {
const runId = (0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id");
const run = (0, run_store_1.loadRunFromCwd)(runId, invocationCwdFor(args));
return (0, worker_isolation_1.summarizeWorkers)(run);
}
function workerSummaryText(args) {
const runId = (0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id");
const run = (0, run_store_1.loadRunFromCwd)(runId, invocationCwdFor(args));
return (0, worker_isolation_1.formatWorkerSummaryText)(run);
}
function invocationCwdFor(args) {
return typeof args.cwd === "string" && args.cwd.trim() ? workerPath.resolve(args.cwd) : process.cwd();
}
// jsonMode "flag": human `Workers` panel by default, canonical JSON under
// --json (old build's worker.summary was flag, cli/handlers/worker.ts).
(0, registry_core_1.attachCliBinding)("worker.summary", {
path: ["worker", "summary"],
jsonMode: "flag",
handler: (args) => ({
json: workerSummaryCli({ ...args.options, runId: args.positionals[0] }),
text: `${workerSummaryText({ ...args.options, runId: args.positionals[0] })}\n`,
}),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("worker.summary").mcp.handler = (args) => workerSummaryCli(args);
// ---- worker list|show|manifest|output|fail|validate (CLI + MCP) ----------
// Each worker lifecycle verb over a run. The old build routed all of these
// via src/cli/handlers/worker.ts; v2 shipped only worker.summary bound, so the
// rest fell through to the worker.usage error. positionals: [runId, workerId,
// resultFile].
const worker_cli_1 = require("../../shell/worker-cli");
(0, registry_core_1.attachCliBinding)("worker.list", {
path: ["worker", "list"],
jsonMode: "default",
handler: (args) => ({ json: (0, worker_cli_1.workerListCli)({ ...args.options, runId: args.positionals[0] }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("worker.list").mcp.handler = (args) => (0, worker_cli_1.workerListCli)(args);
(0, registry_core_1.attachCliBinding)("worker.show", {
path: ["worker", "show"],
jsonMode: "default",
handler: (args) => ({ json: (0, worker_cli_1.workerShowCli)({ ...args.options, runId: args.positionals[0], workerId: args.positionals[1] }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("worker.show").mcp.handler = (args) => (0, worker_cli_1.workerShowCli)(args);
(0, registry_core_1.attachCliBinding)("worker.manifest", {
path: ["worker", "manifest"],
jsonMode: "default",
handler: (args) => ({ json: (0, worker_cli_1.workerManifestCli)({ ...args.options, runId: args.positionals[0], workerId: args.positionals[1] }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("worker.manifest").mcp.handler = (args) => (0, worker_cli_1.workerManifestCli)(args);
(0, registry_core_1.attachCliBinding)("worker.output", {
path: ["worker", "output"],
jsonMode: "default",
handler: (args) => ({ json: (0, worker_cli_1.workerOutputCli)({ ...args.options, runId: args.positionals[0], workerId: args.positionals[1], resultPath: args.positionals[2] }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("worker.output").mcp.handler = (args) => (0, worker_cli_1.workerOutputCli)(args);
(0, registry_core_1.attachCliBinding)("worker.fail", {
path: ["worker", "fail"],
jsonMode: "default",
handler: (args) => ({ json: (0, worker_cli_1.workerFailCli)({ ...args.options, runId: args.positionals[0], workerId: args.positionals[1], resultPath: args.positionals[2] }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("worker.fail").mcp.handler = (args) => (0, worker_cli_1.workerFailCli)(args);
(0, registry_core_1.attachCliBinding)("worker.validate", {
path: ["worker", "validate"],
jsonMode: "default",
handler: (args) => {
const { violation, exitCode } = (0, worker_cli_1.workerValidateCli)({ ...args.options, runId: args.positionals[0], workerId: args.positionals[1], resultPath: args.positionals[2] });
return { json: violation, exitCode };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("worker.validate").mcp.handler = (args) => (0, worker_cli_1.workerValidateCli)(args).violation;
// ---- feedback list|show|summary|collect|task|resolve (CLI + MCP) ---------
// The operator feedback lifecycle. MCP rows were declared but stubbed
// (notYetImplemented) and no CLI verb was bound; the old build routed all of
// these. positionals: [runId, feedbackId].
const feedback_cli_1 = require("../../shell/feedback-cli");
// jsonMode "flag": human `Feedback` panel by default, canonical JSON under
// --json (old build's feedback.summary was flag).
(0, registry_core_1.attachCliBinding)("feedback.summary", {
path: ["feedback", "summary"],
jsonMode: "flag",
handler: (args) => {
const summary = (0, feedback_cli_1.feedbackSummaryCli)({ ...args.options, runId: args.positionals[0] });
return { json: summary, text: `${(0, operator_ux_text_1.formatFeedbackSummaryText)(summary)}\n` };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("feedback.summary").mcp.handler = (args) => (0, feedback_cli_1.feedbackSummaryCli)(args);
(0, registry_core_1.attachCliBinding)("feedback.list", {
path: ["feedback", "list"],
jsonMode: "default",
handler: (args) => ({ json: (0, feedback_cli_1.feedbackListCli)({ ...args.options, runId: args.positionals[0] }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("feedback.list").mcp.handler = (args) => (0, feedback_cli_1.feedbackListCli)(args);
(0, registry_core_1.attachCliBinding)("feedback.show", {
path: ["feedback", "show"],
jsonMode: "default",
handler: (args) => ({ json: (0, feedback_cli_1.feedbackShowCli)({ ...args.options, runId: args.positionals[0], feedbackId: args.positionals[1] }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("feedback.show").mcp.handler = (args) => (0, feedback_cli_1.feedbackShowCli)(args);
(0, registry_core_1.attachCliBinding)("feedback.collect", {
path: ["feedback", "collect"],
jsonMode: "default",
handler: (args) => ({ json: (0, feedback_cli_1.feedbackCollectCli)({ ...args.options, runId: args.positionals[0] }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("feedback.collect").mcp.handler = (args) => (0, feedback_cli_1.feedbackCollectCli)(args);
(0, registry_core_1.attachCliBinding)("feedback.task", {
path: ["feedback", "task"],
jsonMode: "default",
handler: (args) => ({ json: (0, feedback_cli_1.feedbackTaskCli)({ ...args.options, runId: args.positionals[0], feedbackId: args.positionals[1] }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("feedback.task").mcp.handler = (args) => (0, feedback_cli_1.feedbackTaskCli)(args);
(0, registry_core_1.attachCliBinding)("feedback.resolve", {
path: ["feedback", "resolve"],
jsonMode: "default",
handler: (args) => ({ json: (0, feedback_cli_1.feedbackResolveCli)({ ...args.options, runId: args.positionals[0], feedbackId: args.positionals[1] }) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("feedback.resolve").mcp.handler = (args) => (0, feedback_cli_1.feedbackResolveCli)(args);
// ---- workbench.view / workbench.serve ---------------------------------
const workbench_1 = require("../../shell/workbench");
const workbench_text_1 = require("../../shell/workbench-text");
const workbench_host_1 = require("../../shell/workbench-host");
(0, registry_core_1.attachCliBinding)("workbench.view", {
path: ["workbench", "view"],
jsonMode: "flag",
handler: (args) => {
const runId = (0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]), "run id");
const view = (0, workbench_1.buildWorkbenchRunView)(runId, args.options);
return { json: view, text: `${(0, workbench_text_1.formatWorkbenchView)(view)}\n` };
},
});
// The MCP path is CLI-facing byte-identical (buildWorkbenchRunView takes
// the same args shape either way) — required here since `.cli` and
// `.mcp` never share a handler object per byte-compat item 5.
registry_core_1.REGISTRY_BY_CAPABILITY.get("workbench.view").mcp.handler = (args) => (0, workbench_1.buildWorkbenchRunView)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
(0, registry_core_1.attachCliBinding)("workbench.serve", {
path: ["workbench", "serve"],
jsonMode: "flag",
handler: (args) => {
const host = new workbench_host_1.WorkbenchHost(args.options);
if (args.options.once || (0, io_1.wantsJson)(args.options)) {
return { json: host.descriptor(true) };
}
// The default (no --once, no --json) actually binds and blocks — this
// returns a promise the generic dispatcher does not await today, so
// instead we run it directly here and never return (matching the old
// build's own blocking `serve` behavior). See cli/dispatch.ts's
// renderCliResult: it is synchronous, so a genuinely blocking serve
// must perform its own stdout write and keep the event loop alive
// rather than returning a CliHandlerResult at all.
void host.run();
return { json: undefined };
},
});
// `cw_workbench_serve` NEVER starts the server — the MCP path forces
// `once: true` unconditionally, per SPEC/reporting-ux.md's "one declared
// divergence": an MCP client must never be able to make the server
// process open a persistent listening socket.
registry_core_1.REGISTRY_BY_CAPABILITY.get("workbench.serve").mcp.handler = (args) => new workbench_host_1.WorkbenchHost(args).descriptor(true);
// PARITY: both surfaces route through the single core entry
// buildWorkbenchServeDescriptor and return the IDENTICAL serve
// descriptor under `cw workbench serve --json`/`--once` and
// `cw_workbench_serve`. They diverge only in side effect, not payload:
// the CLI's default `cw workbench serve` (no --once) additionally
// STARTS the blocking localhost host, which an MCP stdio host cannot do,
// so cw_workbench_serve only ever returns the descriptor. Declared
// divergence, not drift.
registry_core_1.REGISTRY_BY_CAPABILITY.get("workbench.serve").payloadIdentical = false;
registry_core_1.REGISTRY_BY_CAPABILITY.get("workbench.serve").reason =
"Both surfaces route through the single core entry buildWorkbenchServeDescriptor and return the IDENTICAL serve descriptor under `cw workbench serve --json`/`--once` and `cw_workbench_serve`. They diverge only in side effect, not payload: the CLI's default `cw workbench serve` (no --once) additionally STARTS the blocking localhost host, which an MCP stdio host cannot do, so cw_workbench_serve only ever returns the descriptor. Declared divergence, not drift.";
// ---- audit.summary / audit.multi-agent / audit.policy / audit.judge ----
const audit_cli_1 = require("../../shell/audit-cli");
const operator_ux_text_2 = require("../../shell/operator-ux-text");
(0, registry_core_1.attachCliBinding)("audit.summary", {
path: ["audit", "summary"],
jsonMode: "default",
handler: (args) => ({ json: (0, audit_cli_1.auditSummaryCli)((0, io_1.required)(args.positionals[0], "run id"), args.options) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("audit.summary").mcp.handler = (args) => (0, audit_cli_1.auditSummaryCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
(0, registry_core_1.attachCliBinding)("audit.multi-agent", {
path: ["audit", "multi-agent"],
jsonMode: "flag",
handler: (args) => {
const view = (0, audit_cli_1.auditMultiAgentCli)((0, io_1.required)(args.positionals[0], "run id"), args.options);
return { json: view, text: (0, operator_ux_text_2.formatMultiAgentTrustAudit)(view) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("audit.multi-agent").mcp.handler = (args) => (0, audit_cli_1.auditMultiAgentCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
(0, registry_core_1.attachCliBinding)("audit.policy", {
path: ["audit", "policy"],
jsonMode: "flag",
handler: (args) => {
const view = (0, audit_cli_1.auditPolicyCli)((0, io_1.required)(args.positionals[0], "run id"), args.options);
return { json: view, text: (0, operator_ux_text_2.formatMultiAgentTrustAudit)(view) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("audit.policy").mcp.handler = (args) => (0, audit_cli_1.auditPolicyCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
(0, registry_core_1.attachCliBinding)("audit.judge", {
path: ["audit", "judge"],
jsonMode: "flag",
handler: (args) => {
const view = (0, audit_cli_1.auditJudgeCli)((0, io_1.required)(args.positionals[0], "run id"), args.options);
return { json: view, text: (0, operator_ux_text_2.formatMultiAgentTrustAudit)(view) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("audit.judge").mcp.handler = (args) => (0, audit_cli_1.auditJudgeCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
// GAP: `cw audit worker|provenance|role|blackboard|attest|decision` — the MCP
// tool rows (cw_audit_worker/provenance/role/blackboard/attest/decision) were
// declared but had no CLI path binding and their mcp.handler was still
// notYetImplemented. Wire both surfaces (port of the old cli/handlers/audit.ts
// arms). `audit worker`/`role`/`decision` read positionals[1] as the entity id.
(0, registry_core_1.attachCliBinding)("audit.worker", {
path: ["audit", "worker"],
jsonMode: "default",
handler: (args) => ({ json: (0, audit_cli_1.auditWorkerCli)((0, io_1.required)(args.positionals[0], "run id"), (0, io_1.required)(args.positionals[1], "worker id"), args.options) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("audit.worker").mcp.handler = (args) => (0, audit_cli_1.auditWorkerCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), (0, io_1.required)((0, io_1.optionalArg)(args.workerId ?? args.worker), "worker id"), args);
(0, registry_core_1.attachCliBinding)("audit.provenance", {
path: ["audit", "provenance"],
jsonMode: "default",
handler: (args) => ({ json: (0, audit_cli_1.auditProvenanceCli)((0, io_1.required)(args.positionals[0], "run id"), args.options) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("audit.provenance").mcp.handler = (args) => (0, audit_cli_1.auditProvenanceCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
(0, registry_core_1.attachCliBinding)("audit.role", {
path: ["audit", "role"],
jsonMode: "flag",
handler: (args) => {
const view = (0, audit_cli_1.auditRoleCli)((0, io_1.required)(args.positionals[0], "run id"), (0, io_1.required)(args.positionals[1], "role id"), args.options);
return { json: view, text: (0, operator_ux_text_2.formatMultiAgentTrustAudit)(view) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("audit.role").mcp.handler = (args) => (0, audit_cli_1.auditRoleCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), (0, io_1.required)((0, io_1.optionalArg)(args.roleId ?? args.id), "role id"), args);
(0, registry_core_1.attachCliBinding)("audit.blackboard", {
path: ["audit", "blackboard"],
jsonMode: "flag",
handler: (args) => {
const view = (0, audit_cli_1.auditBlackboardCli)((0, io_1.required)(args.positionals[0], "run id"), args.options);
return { json: view, text: (0, operator_ux_text_2.formatMultiAgentTrustAudit)(view) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("audit.blackboard").mcp.handler = (args) => (0, audit_cli_1.auditBlackboardCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
(0, registry_core_1.attachCliBinding)("audit.attest", {
path: ["audit", "attest"],
jsonMode: "default",
handler: (args) => ({ json: (0, audit_cli_1.auditAttestCli)((0, io_1.required)(args.positionals[0], "run id"), args.options) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("audit.attest").mcp.handler = (args) => (0, audit_cli_1.auditAttestCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
(0, registry_core_1.attachCliBinding)("audit.decision", {
path: ["audit", "decision"],
jsonMode: "default",
handler: (args) => ({ json: (0, audit_cli_1.auditDecisionCli)((0, io_1.required)(args.positionals[0], "run id"), (0, io_1.required)(args.positionals[1], "worker id"), args.options) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("audit.decision").mcp.handler = (args) => (0, audit_cli_1.auditDecisionCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), (0, io_1.required)((0, io_1.optionalArg)(args.workerId), "worker id"), args);
// ---- app.list / app.show / app.validate / app.init / app.package -------
"use strict";
// wiring/capability-table/scheduling-registry.ts — MILESTONE 10
// (scheduling, registry, gc/reclamation, orphans, clones) CLI bindings:
// schedule *, cw loop, routine *, sched *, registry *, queue *, run *
// (list/show/search/rerun/resume/archive), history, gc *, orphans *,
// clones *. Split out of core/capability-table.ts, byte-for-byte
// (extracted with sed, not retyped).
Object.defineProperty(exports, "__esModule", { value: true });
const registry_core_1 = require("./registry-core");
const io_1 = require("../../cli/io");
// MILESTONE 10 (scheduling, registry, gc/reclamation, orphans, clones)
// CLI bindings: schedule *, cw loop, routine *, sched *, registry *,
// queue *, gc *, orphans *, clones *, run search|list|show|resume|
// archive|rerun, history. Handler BODIES live in shell/registry-cli.ts,
// shell/scheduler-io.ts, shell/scheduling-io.ts, shell/reclamation-io.ts,
// shell/run-registry-io.ts (impure — disk-scanning IO); this table only
// wires argv shape -> handler call, per cli/dispatch.ts's generic
// executor contract. Usage-error strings are copied byte-for-byte from
// the old build's handlers/{scheduling,registry,maintenance,orphans,
// clones}.ts.
// ---------------------------------------------------------------------
const registry_cli_1 = require("../../shell/registry-cli");
const reclamation_io_1 = require("../../shell/reclamation-io");
const run_registry_io_1 = require("../../shell/run-registry-io");
const scheduling_io_1 = require("../../shell/scheduling-io");
function firstPositionalArg(args, index = 0) {
return args.positionals[index];
}
// ---- schedule (+ cw loop) ----------------------------------------------
(0, registry_core_1.addCliOnlyCapability)("loop", 'cw loop — sugar for "schedule create --kind loop".', {
path: ["loop"],
jsonMode: "default",
handler: (args) => ({ json: (0, registry_cli_1.scheduleCreateCli)({ ...args.options, kind: "loop" }) }),
}, "loop is CLI-only sugar over schedule.create; the old build never gave it an MCP tool of its own (SPEC/scheduling-registry.md section I).");
(0, registry_core_1.addCliOnlyCapability)("schedule", "cw schedule create|list|delete|due|complete|pause|resume|run-now|history|daemon — the wall-clock scheduler.", {
path: ["schedule"],
jsonMode: "default",
hiddenFromHelp: true,
handler: (args) => {
const [subcommand, id] = args.positionals;
switch (subcommand) {
case "create":
return { json: (0, registry_cli_1.scheduleCreateCli)(args.options) };
case "list":
return { json: (0, registry_cli_1.scheduleListCli)(args.options) };
case "delete":
return { json: (0, registry_cli_1.scheduleDeleteCli)((0, io_1.required)(id, "schedule id"), args.options) };
case "due":
return { json: (0, registry_cli_1.scheduleDueCli)(args.options) };
case "complete":
return { json: (0, registry_cli_1.scheduleCompleteCli)((0, io_1.required)(id, "schedule id"), args.options) };
case "pause":
return { json: (0, registry_cli_1.schedulePauseCli)((0, io_1.required)(id, "schedule id"), args.options) };
case "resume":
return { json: (0, registry_cli_1.scheduleResumeCli)((0, io_1.required)(id, "schedule id"), args.options) };
case "run-now":
return { json: (0, registry_cli_1.scheduleRunNowCli)((0, io_1.required)(id, "schedule id"), args.options) };
case "history":
return { json: (0, registry_cli_1.scheduleHistoryCli)(id, args.options) };
case "daemon": {
if (args.options.once)
return { json: (0, registry_cli_1.scheduleDaemonTickCli)(args.options) };
// Never returns (matches the old build's forever daemon loop);
// the process stays alive via the DesktopSchedulerDaemon's own
// setInterval, printing one tick line per interval.
void (0, registry_cli_1.scheduleDaemonRunForever)(args.options);
return {};
}
default:
throw new Error("Usage: cw.js schedule create|list|delete|due|complete|pause|resume|run-now|history|daemon");
}
},
}, "cw schedule is the desktop wall-clock scheduler; SPEC/mcp.md declares its MCP peers per verb (cw_schedule_*), each wired below.");
// GAP #24: the cw_schedule_* MCP peers were declared but left on the
// notYetImplemented placeholder (the "each wired below" comment was never
// satisfied). Mirror the CLI switch's shell fns; arg-name reads (id/status)
// copied from the old build's mcp/tool-call.ts scheduler arms.
registry_core_1.REGISTRY_BY_CAPABILITY.get("schedule.create").mcp.handler = (args) => (0, registry_cli_1.scheduleCreateCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("schedule.list").mcp.handler = (args) => (0, registry_cli_1.scheduleListCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("schedule.due").mcp.handler = (args) => (0, registry_cli_1.scheduleDueCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("schedule.complete").mcp.handler = (args) => (0, registry_cli_1.scheduleCompleteCli)((0, io_1.required)((0, io_1.optionalArg)(args.id), "schedule id"), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("schedule.pause").mcp.handler = (args) => (0, registry_cli_1.schedulePauseCli)((0, io_1.required)((0, io_1.optionalArg)(args.id), "schedule id"), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("schedule.resume").mcp.handler = (args) => (0, registry_cli_1.scheduleResumeCli)((0, io_1.required)((0, io_1.optionalArg)(args.id), "schedule id"), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("schedule.run-now").mcp.handler = (args) => (0, registry_cli_1.scheduleRunNowCli)((0, io_1.required)((0, io_1.optionalArg)(args.id), "schedule id"), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("schedule.history").mcp.handler = (args) => (0, registry_cli_1.scheduleHistoryCli)((0, io_1.optionalArg)(args.id), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("schedule.delete").mcp.handler = (args) => (0, registry_cli_1.scheduleDeleteCli)((0, io_1.required)((0, io_1.optionalArg)(args.id), "schedule id"), args);
// Each `schedule <verb>` sub-action is its own two-token cli row (found
// before the ["schedule"] catch-all per the reversed candidate order), so
// each capability is a real both-surface dual-bound row. Same shell fns and
// [subcommand, id] positional mapping as the catch-all switch above.
// `hiddenFromHelp` keeps `cw help schedule`'s rows coming from the single
// literal COMMAND_HELP_ROWS.schedule block.
(0, registry_core_1.attachCliBinding)("schedule.create", { path: ["schedule", "create"], jsonMode: "default", hiddenFromHelp: true, handler: (args) => ({ json: (0, registry_cli_1.scheduleCreateCli)(args.options) }) });
(0, registry_core_1.attachCliBinding)("schedule.list", { path: ["schedule", "list"], jsonMode: "default", hiddenFromHelp: true, handler: (args) => ({ json: (0, registry_cli_1.scheduleListCli)(args.options) }) });
(0, registry_core_1.attachCliBinding)("schedule.delete", { path: ["schedule", "delete"], jsonMode: "default", hiddenFromHelp: true, handler: (args) => ({ json: (0, registry_cli_1.scheduleDeleteCli)((0, io_1.required)(args.positionals[0], "schedule id"), args.options) }) });
(0, registry_core_1.attachCliBinding)("schedule.due", { path: ["schedule", "due"], jsonMode: "default", hiddenFromHelp: true, handler: (args) => ({ json: (0, registry_cli_1.scheduleDueCli)(args.options) }) });
(0, registry_core_1.attachCliBinding)("schedule.complete", { path: ["schedule", "complete"], jsonMode: "default", hiddenFromHelp: true, handler: (args) => ({ json: (0, registry_cli_1.scheduleCompleteCli)((0, io_1.required)(args.positionals[0], "schedule id"), args.options) }) });
(0, registry_core_1.attachCliBinding)("schedule.pause", { path: ["schedule", "pause"], jsonMode: "default", hiddenFromHelp: true, handler: (args) => ({ json: (0, registry_cli_1.schedulePauseCli)((0, io_1.required)(args.positionals[0], "schedule id"), args.options) }) });
(0, registry_core_1.attachCliBinding)("schedule.resume", { path: ["schedule", "resume"], jsonMode: "default", hiddenFromHelp: true, handler: (args) => ({ json: (0, registry_cli_1.scheduleResumeCli)((0, io_1.required)(args.positionals[0], "schedule id"), args.options) }) });
(0, registry_core_1.attachCliBinding)("schedule.run-now", { path: ["schedule", "run-now"], jsonMode: "default", hiddenFromHelp: true, handler: (args) => ({ json: (0, registry_cli_1.scheduleRunNowCli)((0, io_1.required)(args.positionals[0], "schedule id"), args.options) }) });
(0, registry_core_1.attachCliBinding)("schedule.history", { path: ["schedule", "history"], jsonMode: "default", hiddenFromHelp: true, handler: (args) => ({ json: (0, registry_cli_1.scheduleHistoryCli)(args.positionals[0], args.options) }) });
// ---- routine ------------------------------------------------------------
(0, registry_core_1.addCliOnlyCapability)("routine", "cw routine create|list|delete|fire|events — API/GitHub-style triggers.", {
path: ["routine"],
jsonMode: "default",
hiddenFromHelp: true,
handler: (args) => {
const [subcommand, idOrKind, payloadPath] = args.positionals;
switch (subcommand) {
case "create":
return { json: (0, registry_cli_1.routineCreateCli)(args.options) };
case "list":
return { json: (0, registry_cli_1.routineListCli)(args.options) };
case "delete":
return { json: (0, registry_cli_1.routineDeleteCli)((0, io_1.required)(idOrKind, "trigger id"), args.options) };
case "fire": {
const kind = (0, io_1.required)(idOrKind, "trigger kind");
const payload = (0, registry_cli_1.resolveRoutineFirePayload)(payloadPath, args.options);
return { json: (0, registry_cli_1.routineFireCli)(kind, payload, args.options) };
}
case "events":
return { json: (0, registry_cli_1.routineEventsCli)(idOrKind, args.options) };
default:
throw new Error("Usage: cw.js routine create|list|delete|fire|events");
}
},
}, "cw routine is the API/GitHub-style trigger bridge; SPEC/mcp.md declares its MCP peers per verb (cw_routine_*), each wired below.");
registry_core_1.REGISTRY_BY_CAPABILITY.get("routine.create").mcp.handler = (args) => (0, registry_cli_1.routineCreateCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("routine.list").mcp.handler = (args) => (0, registry_cli_1.routineListCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("routine.delete").mcp.handler = (args) => (0, registry_cli_1.routineDeleteCli)((0, io_1.required)((0, io_1.optionalArg)(args.id), "trigger id"), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("routine.fire").mcp.handler = (args) => (0, registry_cli_1.routineFireCli)((0, io_1.required)((0, io_1.optionalArg)(args.kind), "trigger kind"), args.payload, args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("routine.events").mcp.handler = (args) => (0, registry_cli_1.routineEventsCli)((0, io_1.optionalArg)(args.id), args);
// Each `routine <verb>` sub-action is its own two-token cli row. The
// catch-all read [subcommand, idOrKind, payloadPath], so after the
// dispatcher consumes the sub-verb positionals[0]=idOrKind,
// positionals[1]=payloadPath. `hiddenFromHelp` keeps `cw help routine`'s
// rows coming from the single literal COMMAND_HELP_ROWS.routine block.
(0, registry_core_1.attachCliBinding)("routine.create", { path: ["routine", "create"], jsonMode: "default", hiddenFromHelp: true, handler: (args) => ({ json: (0, registry_cli_1.routineCreateCli)(args.options) }) });
(0, registry_core_1.attachCliBinding)("routine.list", { path: ["routine", "list"], jsonMode: "default", hiddenFromHelp: true, handler: (args) => ({ json: (0, registry_cli_1.routineListCli)(args.options) }) });
(0, registry_core_1.attachCliBinding)("routine.delete", { path: ["routine", "delete"], jsonMode: "default", hiddenFromHelp: true, handler: (args) => ({ json: (0, registry_cli_1.routineDeleteCli)((0, io_1.required)(args.positionals[0], "trigger id"), args.options) }) });
(0, registry_core_1.attachCliBinding)("routine.fire", {
path: ["routine", "fire"],
jsonMode: "default",
hiddenFromHelp: true,
handler: (args) => {
const kind = (0, io_1.required)(args.positionals[0], "trigger kind");
const payloadPath = args.positionals[1];
const payload = (0, registry_cli_1.resolveRoutineFirePayload)(payloadPath, args.options);
return { json: (0, registry_cli_1.routineFireCli)(kind, payload, args.options) };
},
});
(0, registry_core_1.attachCliBinding)("routine.events", { path: ["routine", "events"], jsonMode: "default", hiddenFromHelp: true, handler: (args) => ({ json: (0, registry_cli_1.routineEventsCli)(args.positionals[0], args.options) }) });
// ---- sched (control-plane leases over the durable queue) ---------------
(0, registry_core_1.addCliOnlyCapability)("sched", "cw sched plan|lease|release|complete|reclaim|reset|policy [show|set] — control-plane lease scheduling over the durable queue.", {
path: ["sched"],
jsonMode: "default",
hiddenFromHelp: true,
handler: (args) => {
const [subcommand, idArg] = args.positionals;
switch (subcommand) {
case "plan":
return { json: (0, scheduling_io_1.schedPlanCli)(args.options) };
case "lease":
return { json: (0, scheduling_io_1.schedLeaseCli)(args.options) };
case "release":
return { json: (0, scheduling_io_1.schedReleaseCli)(String(args.options.leaseId || idArg || ""), args.options) };
case "complete":
return { json: (0, scheduling_io_1.schedCompleteCli)(String(args.options.leaseId || idArg || ""), args.options) };
case "reclaim":
return { json: (0, scheduling_io_1.schedReclaimCli)(args.options) };
case "reset":
return { json: (0, scheduling_io_1.schedResetCli)(String(args.options.id || idArg || ""), args.options) };
case "policy": {
const action = args.positionals[1];
if (action === "set")
return { json: (0, scheduling_io_1.schedPolicySetCli)(args.options) };
return { json: (0, scheduling_io_1.schedPolicyShowCli)(args.options) };
}
default:
throw new Error("Usage: cw.js sched plan|lease|release|complete|reclaim|reset|policy [show|set] [id] [--maxConcurrent N --maxAttempts N ...]");
}
},
}, "cw sched is the durable-queue lease scheduler; SPEC/mcp.md declares its MCP peers per verb (cw_sched_*), each wired below.");
registry_core_1.REGISTRY_BY_CAPABILITY.get("sched.plan").mcp.handler = (args) => (0, scheduling_io_1.schedPlanCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("sched.lease").mcp.handler = (args) => (0, scheduling_io_1.schedLeaseCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("sched.release").mcp.handler = (args) => (0, scheduling_io_1.schedReleaseCli)(String(args.leaseId || ""), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("sched.complete").mcp.handler = (args) => (0, scheduling_io_1.schedCompleteCli)(String(args.leaseId || ""), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("sched.reclaim").mcp.handler = (args) => (0, scheduling_io_1.schedReclaimCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("sched.reset").mcp.handler = (args) => (0, scheduling_io_1.schedResetCli)(String(args.id || ""), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("sched.policy.show").mcp.handler = (args) => (0, scheduling_io_1.schedPolicyShowCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("sched.policy.set").mcp.handler = (args) => (0, scheduling_io_1.schedPolicySetCli)(args);
// Each `sched <verb>` sub-action is its own two-token cli row. The
// catch-all read [subcommand, idArg]; after the dispatcher consumes the
// sub-verb, positionals[0]=idArg (release/complete read --leaseId or that
// positional; reset reads --id or that positional). `sched.policy.show`/
// `.set` share the ["sched","policy"] path with the [show|set] action read
// from the first positional (like blackboard.message.post/list).
// `hiddenFromHelp` keeps `cw help sched`'s rows coming from the single
// literal COMMAND_HELP_ROWS.sched block.
(0, registry_core_1.attachCliBinding)("sched.plan", { path: ["sched", "plan"], jsonMode: "default", hiddenFromHelp: true, handler: (args) => ({ json: (0, scheduling_io_1.schedPlanCli)(args.options) }) });
(0, registry_core_1.attachCliBinding)("sched.lease", { path: ["sched", "lease"], jsonMode: "default", hiddenFromHelp: true, handler: (args) => ({ json: (0, scheduling_io_1.schedLeaseCli)(args.options) }) });
(0, registry_core_1.attachCliBinding)("sched.release", { path: ["sched", "release"], jsonMode: "default", hiddenFromHelp: true, handler: (args) => ({ json: (0, scheduling_io_1.schedReleaseCli)(String(args.options.leaseId || args.positionals[0] || ""), args.options) }) });
(0, registry_core_1.attachCliBinding)("sched.complete", { path: ["sched", "complete"], jsonMode: "default", hiddenFromHelp: true, handler: (args) => ({ json: (0, scheduling_io_1.schedCompleteCli)(String(args.options.leaseId || args.positionals[0] || ""), args.options) }) });
(0, registry_core_1.attachCliBinding)("sched.reclaim", { path: ["sched", "reclaim"], jsonMode: "default", hiddenFromHelp: true, handler: (args) => ({ json: (0, scheduling_io_1.schedReclaimCli)(args.options) }) });
(0, registry_core_1.attachCliBinding)("sched.reset", { path: ["sched", "reset"], jsonMode: "default", hiddenFromHelp: true, handler: (args) => ({ json: (0, scheduling_io_1.schedResetCli)(String(args.options.id || args.positionals[0] || ""), args.options) }) });
function schedPolicyHandler(args) {
const action = args.positionals[0];
if (action === "set")
return { json: (0, scheduling_io_1.schedPolicySetCli)(args.options) };
return { json: (0, scheduling_io_1.schedPolicyShowCli)(args.options) };
}
(0, registry_core_1.attachCliBinding)("sched.policy.show", { path: ["sched", "policy"], helpPath: ["sched", "policy"], jsonMode: "default", hiddenFromHelp: true, handler: schedPolicyHandler });
(0, registry_core_1.attachCliBinding)("sched.policy.set", { path: ["sched", "policy"], helpPath: ["sched", "policy"], jsonMode: "default", hiddenFromHelp: true, handler: schedPolicyHandler });
// ---- registry (refresh|show) --------------------------------------------
(0, registry_core_1.addCliOnlyCapability)("registry", "cw registry refresh|show [--scope repo|home] [--json] — the derived run registry index.", {
path: ["registry"],
jsonMode: "flag",
hiddenFromHelp: true,
handler: (args) => {
const subcommand = firstPositionalArg(args);
let report;
if (subcommand === "refresh")
report = (0, registry_cli_1.registryRefreshCli)(args.options);
else if (subcommand === "show")
report = (0, registry_cli_1.registryShowCli)(args.options);
else
throw new Error("Usage: cw.js registry refresh|show [--scope repo|home] [--json]");
return { json: report, text: (0, run_registry_io_1.formatRegistryReport)(report) };
},
}, "cw registry is the derived run-registry index; SPEC/mcp.md declares its MCP peers (cw_registry_refresh|show), each wired below.");
registry_core_1.REGISTRY_BY_CAPABILITY.get("registry.refresh").mcp.handler = (args) => (0, registry_cli_1.registryRefreshCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("registry.show").mcp.handler = (args) => (0, registry_cli_1.registryShowCli)(args);
// `registry.refresh`/`registry.show` each carry their own two-token
// cli.path (found before the ["registry"] catch-all). `hiddenFromHelp`
// keeps `cw help registry`'s rows coming from the single literal
// COMMAND_HELP_ROWS.registry block. Both are read/derive verbs, so they
// stay in the payload-identity probe (classified deferred until a
// bootstrap fixture seeds a registry index).
(0, registry_core_1.attachCliBinding)("registry.refresh", {
path: ["registry", "refresh"],
jsonMode: "flag",
hiddenFromHelp: true,
handler: (args) => {
const report = (0, registry_cli_1.registryRefreshCli)(args.options);
return (0, io_1.wantsJson)(args.options) ? { json: report } : { json: report, text: (0, run_registry_io_1.formatRegistryReport)(report) };
},
});
(0, registry_core_1.attachCliBinding)("registry.show", {
path: ["registry", "show"],
jsonMode: "flag",
hiddenFromHelp: true,
handler: (args) => {
const report = (0, registry_cli_1.registryShowCli)(args.options);
return (0, io_1.wantsJson)(args.options) ? { json: report } : { json: report, text: (0, run_registry_io_1.formatRegistryReport)(report) };
},
});
// ---- queue (add|list|drain|show) ----------------------------------------
(0, registry_core_1.addCliOnlyCapability)("queue", "cw queue add|list|drain|show [queue-id] [--repo PATH] [--priority N] — the durable run queue.", {
path: ["queue"],
jsonMode: "flag",
hiddenFromHelp: true,
handler: (args) => {
const [subcommand, id] = args.positionals;
switch (subcommand) {
case "add":
return { json: (0, registry_cli_1.queueAddCli)(args.options) };
case "list": {
const result = (0, registry_cli_1.queueListCli)(args.options);
return (0, io_1.wantsJson)(args.options) ? { json: result } : { json: result, text: (0, run_registry_io_1.formatQueueList)(result) };
}
case "drain":
return { json: (0, registry_cli_1.queueDrainCli)(args.options) };
case "show":
return { json: (0, registry_cli_1.queueShowCli)((0, io_1.required)(id, "queue id"), args.options) };
default:
throw new Error("Usage: cw.js queue add|list|drain|show [queue-id] [--repo PATH] [--priority N]");
}
},
}, "cw queue is the durable run queue; SPEC/mcp.md declares its MCP peers (cw_queue_add|list|drain|show), each wired below.");
registry_core_1.REGISTRY_BY_CAPABILITY.get("queue.add").mcp.handler = (args) => (0, registry_cli_1.queueAddCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("queue.list").mcp.handler = (args) => (0, registry_cli_1.queueListCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("queue.drain").mcp.handler = (args) => (0, registry_cli_1.queueDrainCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("queue.show").mcp.handler = (args) => (0, registry_cli_1.queueShowCli)((0, io_1.required)((0, io_1.optionalArg)(args.id), "queue id"), args);
// `queue add|list|drain|show` each carry their own two-token cli.path
// (found before the ["queue"] catch-all). `hiddenFromHelp` keeps `cw help
// queue`'s rows coming from the single literal COMMAND_HELP_ROWS.queue
// block. `queue.list` is jsonMode "flag" (human table by default); the
// others are always-JSON "default", matching the old build's registry.
(0, registry_core_1.attachCliBinding)("queue.add", {
path: ["queue", "add"],
jsonMode: "default",
hiddenFromHelp: true,
handler: (args) => ({ json: (0, registry_cli_1.queueAddCli)(args.options) }),
});
(0, registry_core_1.attachCliBinding)("queue.list", {
path: ["queue", "list"],
jsonMode: "flag",
hiddenFromHelp: true,
handler: (args) => {
const result = (0, registry_cli_1.queueListCli)(args.options);
return (0, io_1.wantsJson)(args.options) ? { json: result } : { json: result, text: (0, run_registry_io_1.formatQueueList)(result) };
},
});
(0, registry_core_1.attachCliBinding)("queue.drain", {
path: ["queue", "drain"],
jsonMode: "default",
hiddenFromHelp: true,
handler: (args) => ({ json: (0, registry_cli_1.queueDrainCli)(args.options) }),
});
(0, registry_core_1.attachCliBinding)("queue.show", {
path: ["queue", "show"],
jsonMode: "default",
hiddenFromHelp: true,
handler: (args) => ({ json: (0, registry_cli_1.queueShowCli)((0, io_1.required)(args.positionals[0], "queue id"), args.options) }),
});
// ---- gc (plan|run|verify) ------------------------------------------------
(0, registry_core_1.addCliOnlyCapability)("gc", "cw gc plan|run|verify [run-id] [--reclaimAfterArchiveDays N] [--keep-scratch] [--keep-snapshots] [--limit N] [--json] — run retention & provable reclamation.", {
path: ["gc"],
jsonMode: "flag",
hiddenFromHelp: true,
handler: (args) => {
const [subcommand, id] = args.positionals;
switch (subcommand) {
case "plan": {
const result = (0, registry_cli_1.gcPlanCli)(id, args.options);
return (0, io_1.wantsJson)(args.options) ? { json: result } : { json: result, text: (0, reclamation_io_1.formatGcPlan)(result) };
}
case "run": {
const result = (0, registry_cli_1.gcRunCli)(id, args.options);
return (0, io_1.wantsJson)(args.options) ? { json: result } : { json: result, text: (0, reclamation_io_1.formatGcRun)(result) };
}
case "verify": {
const result = (0, registry_cli_1.gcVerifyCli)((0, io_1.required)(id, "run id"), args.options);
const text = (0, reclamation_io_1.formatGcVerify)(result);
return { json: result, text, exitCode: result.reclaimed && !result.verified ? 1 : undefined };
}
default:
throw new Error("Usage: cw.js gc plan|run|verify [run-id] [--reclaimAfterArchiveDays N] [--keep-scratch] [--keep-snapshots] [--limit N] [--json]");
}
},
}, "cw gc is run retention & provable reclamation; SPEC/mcp.md declares its MCP peers (cw_gc_plan|run|verify), each wired below.");
registry_core_1.REGISTRY_BY_CAPABILITY.get("gc.plan").mcp.handler = (args) => (0, registry_cli_1.gcPlanCli)((0, io_1.optionalArg)(args.runId), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("gc.run").mcp.handler = (args) => (0, registry_cli_1.gcRunCli)((0, io_1.optionalArg)(args.runId), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("gc.verify").mcp.handler = (args) => (0, registry_cli_1.gcVerifyCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
// PARITY: `gc.run` frees disk and appends a tombstone; both surfaces run
// the identical transaction but the payload reports now-derived
// bytesFreed/tombstone, so it is a documented opt-out, not drift.
registry_core_1.REGISTRY_BY_CAPABILITY.get("gc.run").payloadIdentical = false;
registry_core_1.REGISTRY_BY_CAPABILITY.get("gc.run").reason =
"Mutating: frees disk and appends a tombstone; both surfaces perform the identical transaction but the payload reports now-derived bytesFreed/tombstone.";
// PARITY: `gc.plan`/`gc.verify` now ALSO carry their own two-token
// cli.path ["gc","plan"]/["gc","verify"], same as the old build's
// registry rows and the same reversed-candidate-order pattern used for
// run.drive/run.search above: findCapabilityByCliPath tries the 2-token
// candidate before the 1-token ["gc"] catch-all row, so these rows — not
// the "gc" capability's combined switch — now serve `cw gc plan`/`cw gc
// verify`. Same functions, same output; only which row answers the
// dispatch changes, so both become real both-surface, dual-bound
// capabilities for the payload-identity probe. `gc.run` stays reachable
// only via the ["gc"] catch-all (it is a documented payload-probe
// opt-out above, so it does not need its own dual-bound row).
(0, registry_core_1.attachCliBinding)("gc.plan", {
path: ["gc", "plan"],
jsonMode: "flag",
handler: (args) => {
const result = (0, registry_cli_1.gcPlanCli)(args.positionals[0], args.options);
return (0, io_1.wantsJson)(args.options) ? { json: result } : { json: result, text: (0, reclamation_io_1.formatGcPlan)(result) };
},
});
(0, registry_core_1.attachCliBinding)("gc.verify", {
path: ["gc", "verify"],
jsonMode: "flag",
handler: (args) => {
const result = (0, registry_cli_1.gcVerifyCli)((0, io_1.required)(args.positionals[0], "run id"), args.options);
const text = (0, reclamation_io_1.formatGcVerify)(result);
return { json: result, text, exitCode: result.reclaimed && !result.verified ? 1 : undefined };
},
});
// `gc.run` also carries its own two-token cli.path ["gc","run"], same
// reversed-candidate-order pattern as gc.plan/gc.verify. It stays a
// documented payload-probe opt-out (mutating reclamation), so this row
// only satisfies the both-surface "cli + mcp" pairing — it does not join
// the payload-identity probe. `hiddenFromHelp` keeps `cw help gc`'s row
// coming from the single literal COMMAND_HELP_ROWS.gc entry.
(0, registry_core_1.attachCliBinding)("gc.run", {
path: ["gc", "run"],
jsonMode: "flag",
hiddenFromHelp: true,
handler: (args) => {
const result = (0, registry_cli_1.gcRunCli)(args.positionals[0], args.options);
return (0, io_1.wantsJson)(args.options) ? { json: result } : { json: result, text: (0, reclamation_io_1.formatGcRun)(result) };
},
});
// ---- orphans (list|gc) ---------------------------------------------------
(0, registry_core_1.addCliOnlyCapability)("orphans", "cw orphans list|gc — reclaim run directories a killed process never registered (no state.json).", {
path: ["orphans"],
jsonMode: "flag",
hiddenFromHelp: true,
handler: (args) => {
const subcommand = firstPositionalArg(args);
switch (subcommand) {
case "list": {
const result = (0, registry_cli_1.orphansListCli)(args.options);
return (0, io_1.wantsJson)(args.options) ? { json: result } : { json: result, text: (0, reclamation_io_1.formatOrphanRunsList)(result) };
}
case "gc": {
const result = (0, registry_cli_1.orphansGcCli)(args.options);
return (0, io_1.wantsJson)(args.options) ? { json: result } : { json: result, text: (0, reclamation_io_1.formatOrphanRunsGc)(result) };
}
default:
throw new Error("Usage: cw.js orphans list [--scope repo|home] [--json] | orphans gc [--scope repo|home] [--min-age-minutes N] [--all] [--json] (scope defaults to home: every registered repo)");
}
},
}, "cw orphans reclaims killed-process run dirs with no state.json; SPEC/mcp.md declares its MCP peers (cw_orphans_list|gc), each wired below.");
registry_core_1.REGISTRY_BY_CAPABILITY.get("orphans.list").mcp.handler = (args) => (0, registry_cli_1.orphansListCli)(args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("orphans.gc").mcp.handler = (args) => (0, registry_cli_1.orphansGcCli)(args);
// `orphans.list`/`orphans.gc` each carry their own two-token cli.path
// (found before the ["orphans"] catch-all per the reversed-candidate
// order), so both are real both-surface dual-bound rows. `hiddenFromHelp`
// keeps `cw help orphans`'s rows coming from the single literal
// COMMAND_HELP_ROWS.orphans block. `orphans.gc` is a documented
// payload-probe opt-out (mutating sweep, now-derived freedBytes/removed),
// same as gc.run/clones.gc.
(0, registry_core_1.attachCliBinding)("orphans.list", {
path: ["orphans", "list"],
jsonMode: "flag",
hiddenFromHelp: true,
handler: (args) => {
const result = (0, registry_cli_1.orphansListCli)(args.options);
return (0, io_1.wantsJson)(args.options) ? { json: result } : { json: result, text: (0, reclamation_io_1.formatOrphanRunsList)(result) };
},
});
(0, registry_core_1.attachCliBinding)("orphans.gc", {
path: ["orphans", "gc"],
jsonMode: "flag",
hiddenFromHelp: true,
handler: (args) => {
const result = (0, registry_cli_1.orphansGcCli)(args.options);
return (0, io_1.wantsJson)(args.options) ? { json: result } : { json: result, text: (0, reclamation_io_1.formatOrphanRunsGc)(result) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("orphans.gc").payloadIdentical = false;
registry_core_1.REGISTRY_BY_CAPABILITY.get("orphans.gc").reason =
"Mutating: removes orphan run directories and reports now-derived freedBytes/removed; both surfaces perform the identical sweep.";
// ---- clones (list|gc) ------------------------------------------------------
(0, registry_core_1.addCliOnlyCapability)("clones", "cw clones list|gc [--older-than-days N] [--all] — the cached remote-source checkout cache.", {
path: ["clones"],
jsonMode: "flag",
hiddenFromHelp: true,
handler: (args) => {
const subcommand = firstPositionalArg(args);
switch (subcommand) {
case "list": {
const result = (0, registry_cli_1.clonesListCli)();
return (0, io_1.wantsJson)(args.options) ? { json: result } : { json: result, text: (0, reclamation_io_1.formatClonesList)(result) };
}
case "gc": {
const result = (0, registry_cli_1.clonesGcCli)(args.options);
return (0, io_1.wantsJson)(args.options) ? { json: result } : { json: result, text: (0, reclamation_io_1.formatClonesGc)(result) };
}
default:
throw new Error("Usage: cw.js clones list [--json] | clones gc [--older-than-days N] [--all] [--json]");
}
},
}, "cw clones is the cached remote-source checkout cache; SPEC/mcp.md declares its MCP peers (cw_clones_list|gc), each wired below.");
registry_core_1.REGISTRY_BY_CAPABILITY.get("clones.list").mcp.handler = () => (0, registry_cli_1.clonesListCli)();
registry_core_1.REGISTRY_BY_CAPABILITY.get("clones.gc").mcp.handler = (args) => (0, registry_cli_1.clonesGcCli)(args);
// `clones.list`/`clones.gc` each carry their own two-token cli.path (found
// before the ["clones"] catch-all). `hiddenFromHelp` keeps the byte-pinned
// `cw help clones` fixture's rows coming from the single literal
// COMMAND_HELP_ROWS.clones block. `clones.gc` is a documented payload-probe
// opt-out (mutating sweep), same as gc.run/orphans.gc.
(0, registry_core_1.attachCliBinding)("clones.list", {
path: ["clones", "list"],
jsonMode: "flag",
hiddenFromHelp: true,
handler: (args) => {
const result = (0, registry_cli_1.clonesListCli)();
return (0, io_1.wantsJson)(args.options) ? { json: result } : { json: result, text: (0, reclamation_io_1.formatClonesList)(result) };
},
});
(0, registry_core_1.attachCliBinding)("clones.gc", {
path: ["clones", "gc"],
jsonMode: "flag",
hiddenFromHelp: true,
handler: (args) => {
const result = (0, registry_cli_1.clonesGcCli)(args.options);
return (0, io_1.wantsJson)(args.options) ? { json: result } : { json: result, text: (0, reclamation_io_1.formatClonesGc)(result) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("clones.gc").payloadIdentical = false;
registry_core_1.REGISTRY_BY_CAPABILITY.get("clones.gc").reason =
"Mutating: removes cache directories and reports now-derived freedBytes/removed; both surfaces perform the identical reclamation.";
// ---- run search|list|show|resume|archive|rerun (2-token rows, found
// BEFORE the 1-token run.drive.step row per dispatchTable's reversed
// candidate order — see that row's own comment for why the run-registry
// keyword guard set still lists these words) --------------------------
(0, registry_core_1.attachCliBinding)("run.search", {
path: ["run", "search"],
jsonMode: "flag",
handler: (args) => {
const result = (0, registry_cli_1.runSearchCli)(args.options);
return { json: result, text: (0, run_registry_io_1.formatRunSearch)(result) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("run.search").mcp.handler = (args) => (0, registry_cli_1.runSearchCli)(args);
(0, registry_core_1.attachCliBinding)("run.list", {
path: ["run", "list"],
jsonMode: "flag",
handler: (args) => {
const result = (0, registry_cli_1.runListCli)(args.options);
return { json: result, text: (0, run_registry_io_1.formatRunSearch)(result) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("run.list").mcp.handler = (args) => (0, registry_cli_1.runListCli)(args);
(0, registry_core_1.attachCliBinding)("run.show", {
path: ["run", "show"],
jsonMode: "flag",
handler: (args) => {
const runId = (0, io_1.required)(args.positionals[0], "run id");
const result = (0, registry_cli_1.runShowCli)(runId, args.options);
return { json: result, text: (0, run_registry_io_1.formatRunShow)(result) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("run.show").mcp.handler = (args) => (0, registry_cli_1.runShowCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
(0, registry_core_1.attachCliBinding)("run.resume", {
path: ["run", "resume"],
jsonMode: "flag",
handler: (args) => {
const runId = (0, io_1.required)(args.positionals[0], "run id");
const result = (0, registry_cli_1.runResumeCli)(runId, args.options);
return { json: result, text: (0, run_registry_io_1.formatResume)(result) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("run.resume").mcp.handler = (args) => (0, registry_cli_1.runResumeCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
(0, registry_core_1.attachCliBinding)("run.archive", {
path: ["run", "archive"],
jsonMode: "default",
handler: (args) => ({ json: (0, registry_cli_1.runArchiveCli)((0, io_1.optionalArg)(args.positionals[0]), args.options) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("run.archive").mcp.handler = (args) => (0, registry_cli_1.runArchiveCli)((0, io_1.optionalArg)(args.runId), args);
(0, registry_core_1.attachCliBinding)("run.rerun", {
path: ["run", "rerun"],
jsonMode: "default",
handler: (args) => ({ json: (0, registry_cli_1.runRerunCli)((0, io_1.required)(args.positionals[0], "run id"), args.options) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("run.rerun").mcp.handler = (args) => (0, registry_cli_1.runRerunCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
// ---- history ---------------------------------------------------------
(0, registry_core_1.attachCliBinding)("history", {
path: ["history"],
jsonMode: "flag",
handler: (args) => {
const result = (0, registry_cli_1.historyCli)(args.options);
return { json: result, text: (0, run_registry_io_1.formatHistory)(result) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("history").mcp.handler = (args) => (0, registry_cli_1.historyCli)(args);
// ---------------------------------------------------------------------
"use strict";
// wiring/capability-table/state.ts — MILESTONE 3 (state kernel) + MILESTONE 4
// (state-explosion summaries) CLI bindings: state.check, migration.list|
// check|prove, node.list|show|graph|snapshot|diff|replay|replay.verify,
// summary.refresh|show. Split out of core/capability-table.ts, byte-for-byte
// (extracted with sed, not retyped).
Object.defineProperty(exports, "__esModule", { value: true });
const registry_core_1 = require("./registry-core");
const io_1 = require("../../cli/io");
const report_view_cli_1 = require("../../shell/report-view-cli");
// MILESTONE 3 (state kernel) CLI bindings: state.check, migration.list|
// check|prove, node.list|show|graph|snapshot|diff|replay|replay.verify.
// Handler BODIES live in shell/state-cli.ts (impure — they read/write
// run state on disk); this table only wires argv shape -> handler call
// and the row's own exit-code rule, per cli/dispatch.ts's generic
// executor contract. `required`/`optionalArg` are cli/io.ts's shared
// coercion helpers, imported here so the wiring stays a thin adapter
// (Usage-error strings copied byte-for-byte from the old build's
// handlers/*.ts).
// ---------------------------------------------------------------------
const io_2 = require("../../cli/io");
const state_cli_1 = require("../../shell/state-cli");
(0, registry_core_1.attachCliBinding)("state.check", {
path: ["state", "check"],
jsonMode: "default",
handler: (args) => {
const runId = (0, io_2.required)(args.positionals[0], "run id");
const report = (0, state_cli_1.checkState)(runId, args.options);
return { json: report, exitCode: report.status === "unsupported" ? 1 : undefined };
},
});
(0, registry_core_1.attachCliBinding)("migration.list", {
path: ["migration", "list"],
jsonMode: "default",
handler: () => ({ json: (0, state_cli_1.migrationList)() }),
});
(0, registry_core_1.attachCliBinding)("migration.check", {
path: ["migration", "check"],
jsonMode: "default",
handler: (args) => {
const target = (0, io_2.required)(args.positionals[0], "target (run-id or state/app file)");
const report = (0, state_cli_1.migrationCheck)(target, args.options);
return { json: report, exitCode: report.status === "unsupported" ? 1 : undefined };
},
});
(0, registry_core_1.attachCliBinding)("migration.prove", {
path: ["migration", "prove"],
jsonMode: "default",
handler: (args) => {
const target = (0, io_2.required)(args.positionals[0], "target (run-id or state/app file)");
const proof = (0, state_cli_1.migrationProve)(target, args.options);
return { json: proof, exitCode: proof.pass ? undefined : 1 };
},
});
(0, registry_core_1.attachCliBinding)("node.list", {
path: ["node", "list"],
jsonMode: "default",
handler: (args) => ({ json: (0, state_cli_1.listNodes)((0, io_2.required)(args.positionals[0], "run id"), args.options) }),
});
(0, registry_core_1.attachCliBinding)("node.show", {
path: ["node", "show"],
jsonMode: "default",
handler: (args) => {
const runId = (0, io_2.required)(args.positionals[0], "run id");
const nodeId = (0, io_2.required)(args.positionals[1], "node id");
return { json: (0, state_cli_1.showNode)(runId, nodeId, args.options) };
},
});
// jsonMode "flag": `--json` prints the node array (graphNodes); the bare
// verb prints the operator run-graph text, byte-for-byte the old build's
// `node graph` render (formatOperatorGraph over runner.operatorGraph).
(0, registry_core_1.attachCliBinding)("node.graph", {
path: ["node", "graph"],
jsonMode: "flag",
handler: (args) => {
const runId = (0, io_2.required)(args.positionals[0], "run id");
return { json: (0, state_cli_1.graphNodes)(runId, args.options), text: `${(0, report_view_cli_1.graphText)(runId, args.options)}\n` };
},
});
(0, registry_core_1.attachCliBinding)("node.snapshot", {
path: ["node", "snapshot"],
jsonMode: "default",
handler: (args) => {
const runId = (0, io_2.required)(args.positionals[0], "run id");
const nodeId = (0, io_2.required)(args.positionals[1], "node id");
return { json: (0, state_cli_1.nodeSnapshotCli)(runId, nodeId, args.options) };
},
});
(0, registry_core_1.attachCliBinding)("node.diff", {
path: ["node", "diff"],
jsonMode: "default",
handler: (args) => {
const runId = (0, io_2.required)(args.positionals[0], "run id");
const baselineSnapshotId = (0, io_2.required)(args.positionals[1], "baseline snapshot id");
const candidateSnapshotId = (0, io_2.required)(args.positionals[2], "candidate snapshot id");
return { json: (0, state_cli_1.nodeDiffCli)(runId, baselineSnapshotId, candidateSnapshotId, args.options) };
},
});
(0, registry_core_1.attachCliBinding)("node.replay", {
path: ["node", "replay"],
jsonMode: "default",
handler: (args) => {
const runId = (0, io_2.required)(args.positionals[0], "run id");
const snapshotId = (0, io_2.required)(args.positionals[1], "snapshot id");
return { json: (0, state_cli_1.nodeReplayCli)(runId, snapshotId, args.options) };
},
});
(0, registry_core_1.attachCliBinding)("node.replay.verify", {
path: ["node", "verify"],
jsonMode: "default",
handler: (args) => {
const runId = (0, io_2.required)(args.positionals[0], "run id");
const replayId = (0, io_2.required)(args.positionals[1], "replay id");
const verdict = (0, state_cli_1.nodeReplayVerifyCli)(runId, replayId, args.options);
return { json: verdict, exitCode: verdict.pass ? undefined : 1 };
},
});
// GAP #24: mirror the state-kernel CLI shell fns as MCP handlers (they were
// declared MCP tool rows but left on notYetImplemented). Arg-name reads copied
// byte-for-byte from the old build's mcp/tool-call.ts switch arms.
registry_core_1.REGISTRY_BY_CAPABILITY.get("state.check").mcp.handler = (args) => (0, state_cli_1.checkState)((0, io_2.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("migration.list").mcp.handler = () => (0, state_cli_1.migrationList)();
registry_core_1.REGISTRY_BY_CAPABILITY.get("migration.check").mcp.handler = (args) => (0, state_cli_1.migrationCheck)((0, io_2.required)((0, io_1.optionalArg)(args.target ?? args.runId), "target (run-id or state/app file)"), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("migration.prove").mcp.handler = (args) => (0, state_cli_1.migrationProve)((0, io_2.required)((0, io_1.optionalArg)(args.target ?? args.runId), "target (run-id or state/app file)"), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("node.list").mcp.handler = (args) => (0, state_cli_1.listNodes)((0, io_2.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("node.show").mcp.handler = (args) => (0, state_cli_1.showNode)((0, io_2.required)((0, io_1.optionalArg)(args.runId), "run id"), (0, io_2.required)((0, io_1.optionalArg)(args.nodeId), "node id"), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("node.graph").mcp.handler = (args) => (0, state_cli_1.graphNodes)((0, io_2.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("node.snapshot").mcp.handler = (args) => (0, state_cli_1.nodeSnapshotCli)((0, io_2.required)((0, io_1.optionalArg)(args.runId), "run id"), (0, io_2.required)((0, io_1.optionalArg)(args.nodeId), "node id"), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("node.diff").mcp.handler = (args) => (0, state_cli_1.nodeDiffCli)((0, io_2.required)((0, io_1.optionalArg)(args.runId), "run id"), (0, io_2.required)((0, io_1.optionalArg)(args.baselineSnapshotId ?? args.baseline), "baseline snapshot id"), (0, io_2.required)((0, io_1.optionalArg)(args.candidateSnapshotId ?? args.candidate), "candidate snapshot id"), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("node.replay").mcp.handler = (args) => (0, state_cli_1.nodeReplayCli)((0, io_2.required)((0, io_1.optionalArg)(args.runId), "run id"), (0, io_2.required)((0, io_1.optionalArg)(args.snapshotId), "snapshot id"), args);
registry_core_1.REGISTRY_BY_CAPABILITY.get("node.replay.verify").mcp.handler = (args) => (0, state_cli_1.nodeReplayVerifyCli)((0, io_2.required)((0, io_1.optionalArg)(args.runId), "run id"), (0, io_2.required)((0, io_1.optionalArg)(args.replayId), "replay id"), args);
// `contract.show` is not yet a declared MCP_TOOL_DATA row with a CLI peer
// wired here (it IS in MCP_TOOL_DATA already); no milestone-3 conformance
// case reaches it, so it is intentionally left on its placeholder handler
// until a case demands it — avoids speculative, untested wiring.
// ---------------------------------------------------------------------
// MILESTONE 4 (state-explosion summaries) CLI bindings: summary.refresh,
// summary.show. Handler BODIES live in shell/state-explosion-cli.ts
// (impure — disk reads/writes summaries under the run dir); this table
// only wires argv shape -> handler call, per cli/dispatch.ts's generic
// executor contract. Per SPEC/state-core.md's CLI verbs section: without
// `--json` both print `formatStateExplosionReport` text (jsonMode
// "flag" — text by default, JSON under --json/--format json).
// ---------------------------------------------------------------------
const state_explosion_text_1 = require("../../core/format/state-explosion-text");
const state_explosion_cli_1 = require("../../shell/state-explosion-cli");
const io_3 = require("../../cli/io");
(0, registry_core_1.attachCliBinding)("summary.refresh", {
path: ["summary", "refresh"],
jsonMode: "flag",
handler: (args) => {
const runId = (0, io_2.required)(args.positionals[0], "run id");
const index = (0, state_explosion_cli_1.summaryRefreshCli)(runId, args.options);
// Byte-exact port of the old build's handleSummary "refresh": the
// human-text branch re-reads via a fresh summaryShow call rather than
// formatting the refresh's own index record (src/cli/handlers/
// operator.ts:118-127); only computed when actually needed, so a
// --json call does exactly the one read the old build's if/else did.
if ((0, io_3.wantsJson)(args.options))
return { json: index };
return { json: index, text: (0, state_explosion_text_1.formatStateExplosionReport)((0, state_explosion_cli_1.summaryShowCli)(runId, args.options)) };
},
});
(0, registry_core_1.attachCliBinding)("summary.show", {
path: ["summary", "show"],
jsonMode: "flag",
handler: (args) => {
const runId = (0, io_2.required)(args.positionals[0], "run id");
const report = (0, state_explosion_cli_1.summaryShowCli)(runId, args.options);
return { json: report, text: (0, state_explosion_text_1.formatStateExplosionReport)(report) };
},
});
// ---------------------------------------------------------------------
"use strict";
// wiring/capability-table/trust-ledger.ts — MILESTONE 8 (ledger, telemetry,
// trust-audit, tamper/bundle demos) CLI bindings: ledger.*, telemetry.verify,
// audit.verify, audit.head, demo.*, report.bundle/verify-bundle. Split out
// of core/capability-table.ts, byte-for-byte (extracted with sed, not
// retyped).
Object.defineProperty(exports, "__esModule", { value: true });
const registry_core_1 = require("./registry-core");
const io_1 = require("../../cli/io");
// MILESTONE 8 (ledger, telemetry, trust-audit, tamper/bundle demos) CLI
// bindings: ledger propose|review|verify|apply|list, telemetry verify,
// audit verify, demo tamper|bundle, report bundle|verify-bundle. Handler
// BODIES live in shell/ledger-cli.ts, shell/telemetry-cli.ts, shell/
// audit-cli.ts, shell/demo-cli.ts, shell/report-cli.ts (impure — file/
// stdin reads, run-state loads, archive IO); this table only wires argv
// shape -> handler call, per cli/dispatch.ts's generic executor
// contract. `ledger` is intentionally absent from KNOWN_COMMANDS (see
// cli/parseargv.ts) even though dispatchTable now handles it as a real
// row — a known, preserved wart.
// ---------------------------------------------------------------------
const ledger_cli_1 = require("../../shell/ledger-cli");
const telemetry_cli_1 = require("../../shell/telemetry-cli");
const audit_cli_1 = require("../../shell/audit-cli");
const demo_cli_1 = require("../../shell/demo-cli");
const telemetry_demo_1 = require("../../shell/telemetry-demo");
const report_cli_1 = require("../../shell/report-cli");
(0, registry_core_1.attachCliBinding)("ledger.propose", {
path: ["ledger", "propose"],
jsonMode: "default",
handler: (args) => ({ json: (0, ledger_cli_1.ledgerProposeCli)(args.options) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.propose").mcp.handler = (args) => (0, ledger_cli_1.ledgerProposeMcp)(args);
(0, registry_core_1.attachCliBinding)("ledger.review", {
path: ["ledger", "review"],
jsonMode: "default",
handler: (args) => ({ json: (0, ledger_cli_1.ledgerReviewCli)(args.options) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.review").mcp.handler = (args) => (0, ledger_cli_1.ledgerReviewMcp)(args);
(0, registry_core_1.attachCliBinding)("ledger.verify", {
path: ["ledger", "verify"],
jsonMode: "default",
handler: (args) => {
const result = (0, ledger_cli_1.ledgerVerifyCli)(args.options);
return { json: result, exitCode: result.ok ? undefined : 1 };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.verify").mcp.handler = (args) => (0, ledger_cli_1.ledgerVerifyEntry)(args.entry);
(0, registry_core_1.attachCliBinding)("ledger.apply", {
path: ["ledger", "apply"],
jsonMode: "default",
handler: (args) => {
const result = (0, ledger_cli_1.ledgerApplyCli)(args.options);
return { json: result, exitCode: result.ok ? undefined : 1 };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.apply").mcp.handler = (args) => (0, ledger_cli_1.ledgerApplyEntry)(args.entry);
(0, registry_core_1.attachCliBinding)("ledger.list", {
path: ["ledger", "list"],
jsonMode: "default",
handler: (args) => {
const result = (0, ledger_cli_1.ledgerListCli)(args.options);
return { json: result, exitCode: result.allOk ? undefined : 1 };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.list").mcp.handler = (args) => (0, ledger_cli_1.ledgerListMcp)(args);
(0, registry_core_1.attachCliBinding)("telemetry.verify", {
path: ["telemetry", "verify"],
jsonMode: "flag",
handler: (args) => {
const runId = (0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]) || (0, io_1.optionalArg)(args.options.runId) || (0, io_1.optionalArg)(args.options.run), "run id");
const result = (0, telemetry_cli_1.telemetryVerifyCli)(runId, args.options);
return { json: result, text: (0, telemetry_demo_1.formatTelemetryVerify)(result), exitCode: result.verified ? undefined : 1 };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("telemetry.verify").mcp.handler = (args) => (0, telemetry_cli_1.telemetryVerifyCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
(0, registry_core_1.attachCliBinding)("audit.verify", {
path: ["audit", "verify"],
jsonMode: "default",
handler: (args) => {
const runId = (0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]), "run id");
const result = (0, audit_cli_1.auditVerifyCli)(runId, args.options);
return { json: result, exitCode: result.verified ? undefined : 1 };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("audit.verify").mcp.handler = (args) => (0, audit_cli_1.auditVerifyCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
(0, registry_core_1.attachCliBinding)("audit.head", {
path: ["audit", "head"],
jsonMode: "default",
handler: (args) => {
const runId = (0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]), "run id");
return { json: (0, audit_cli_1.auditHeadCli)(runId, args.options) };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("audit.head").mcp.handler = (args) => (0, audit_cli_1.auditHeadCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
(0, registry_core_1.addCliOnlyCapability)("demo.tamper", "Prove tamper-evidence: build a signed telemetry ledger, forge it, watch verification fail offline.", {
path: ["demo", "tamper"],
jsonMode: "flag",
handler: (args) => {
const result = (0, demo_cli_1.demoTamperCli)();
return { json: result, text: (0, telemetry_demo_1.formatTamperDemo)(result), exitCode: result.proven ? undefined : 1 };
},
}, "Human-facing demonstration (operator/newcomer onboarding); the underlying integrity check is exposed programmatically as the both-surface telemetry.verify. No agent or MCP client needs to invoke a demo.");
(0, registry_core_1.addCliOnlyCapability)("demo.bundle", "Prove portable-bundle verification: export a sealed report bundle, forge it two ways, watch report verify-bundle catch both offline with only the embedded public key.", {
path: ["demo", "bundle"],
jsonMode: "flag",
handler: (args) => {
const result = (0, demo_cli_1.demoBundleCli)();
return { json: result, text: (0, telemetry_demo_1.formatBundleDemo)(result), exitCode: result.proven ? undefined : 1 };
},
}, "Human-facing demonstration (operator/newcomer onboarding); the underlying integrity check is exposed programmatically as the both-surface report.verify-bundle. No agent or MCP client needs to invoke a demo.");
(0, registry_core_1.attachCliBinding)("report.bundle", {
path: ["report", "bundle"],
jsonMode: "default",
handler: (args) => {
const runId = (0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]), "run id");
const result = (0, report_cli_1.reportBundleCli)(runId, args.options);
return { json: result, exitCode: result.ok ? undefined : 1 };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("report.bundle").mcp.handler = (args) => (0, report_cli_1.reportBundleCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
(0, registry_core_1.attachCliBinding)("report.verify-bundle", {
path: ["report", "verify-bundle"],
jsonMode: "default",
handler: (args) => {
const archivePath = (0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]), "bundle path");
const result = (0, report_cli_1.reportVerifyBundleCli)({ ...args.options, archive: archivePath });
return { json: result, exitCode: result.ok ? undefined : 1 };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("report.verify-bundle").mcp.handler = (args) => {
const result = (0, report_cli_1.reportVerifyBundleCli)(args);
return result;
};
// ---------------------------------------------------------------------
"use strict";
// wiring/capability-table/workflow-apps.ts — MILESTONE 12 (workflow-apps:
// app.*, info, man) CLI bindings, plus the `next` capability (a milestone
// 3/6 placeholder folded in alongside app.* per the file's own history).
// Split out of core/capability-table.ts, byte-for-byte (extracted with
// sed, not retyped).
Object.defineProperty(exports, "__esModule", { value: true });
const registry_core_1 = require("./registry-core");
const io_1 = require("../../cli/io");
const state_cli_1 = require("../../shell/state-cli");
const app_run_cli_1 = require("../../shell/app-run-cli");
// MILESTONE 12 (workflow-apps). Handler BODIES live in
// shell/workflow-app-loader.ts (impure — they scan apps/*/app.json +
// workflows/*.workflow.js on disk and `require()` each entrypoint); this
// table only wires argv/tool-args shape -> handler call, per SPEC/
// workflow-apps.md's "Exact outputs". `app.validate` is ALWAYS JSON
// (jsonMode "default") even without --json, and its handler sets
// exitCode 1 on `valid:false` — both the "not found" id case and a
// structurally-broken manifest case fail this same way.
const workflow_app_loader_1 = require("../../shell/workflow-app-loader");
const help_1 = require("../../core/format/help");
const man_cli_1 = require("../../shell/man-cli");
(0, registry_core_1.attachCliBinding)("app.list", {
path: ["app", "list"],
jsonMode: "default",
handler: () => ({ json: (0, workflow_app_loader_1.listWorkflowApps)() }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("app.list").mcp.handler = () => (0, workflow_app_loader_1.listWorkflowApps)();
(0, registry_core_1.attachCliBinding)("app.show", {
path: ["app", "show"],
jsonMode: "default",
handler: (args) => ({ json: (0, workflow_app_loader_1.showWorkflowApp)((0, io_1.required)(args.positionals[0], "workflow app id")) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("app.show").mcp.handler = (args) => (0, workflow_app_loader_1.showWorkflowApp)((0, io_1.required)((0, io_1.optionalArg)(args.appId), "workflow app id"));
(0, registry_core_1.attachCliBinding)("app.validate", {
path: ["app", "validate"],
jsonMode: "default",
handler: (args) => {
const result = (0, workflow_app_loader_1.validateWorkflowAppTarget)((0, io_1.required)(args.positionals[0], "workflow app path or id"));
return { json: result, exitCode: result.valid ? undefined : 1 };
},
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("app.validate").mcp.handler = (args) => (0, workflow_app_loader_1.validateWorkflowAppTarget)((0, io_1.required)((0, io_1.optionalArg)(args.target ?? args.appId), "workflow app path or id"));
(0, registry_core_1.attachCliBinding)("app.init", {
path: ["app", "init"],
jsonMode: "default",
handler: (args) => ({ json: (0, workflow_app_loader_1.initWorkflowApp)((0, io_1.required)(args.positionals[0], "app id"), args.options) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("app.init").mcp.handler = (args) => (0, workflow_app_loader_1.initWorkflowApp)((0, io_1.required)((0, io_1.optionalArg)(args.appId), "app id"), args);
// `cw init <id>` — the standalone scaffold verb. v2 folds `init` into
// `app.init` (the old build's legacy `.workflow.js` scaffold is gone), so
// both surfaces route through initWorkflowApp, same as `cw app init`. The
// `init` help token is folded away (declaredCliHelpTokens) — it stays in
// the frozen "More commands" index line only, matching the parity smoke's
// HELP_INDEX_ONLY_TOKENS treatment. `workflowId` is the old init arg name.
(0, registry_core_1.attachCliBinding)("init", {
path: ["init"],
jsonMode: "default",
handler: (args) => ({ json: (0, workflow_app_loader_1.initWorkflowApp)((0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]), "workflow id"), args.options) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("init").mcp.handler = (args) => (0, workflow_app_loader_1.initWorkflowApp)((0, io_1.required)((0, io_1.optionalArg)(args.workflowId ?? args.appId), "workflow id"), args);
(0, registry_core_1.attachCliBinding)("app.package", {
path: ["app", "package"],
jsonMode: "default",
handler: (args) => ({ json: (0, workflow_app_loader_1.packageWorkflowApp)((0, io_1.required)(args.positionals[0], "app id"), args.options) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("app.package").mcp.handler = (args) => (0, workflow_app_loader_1.packageWorkflowApp)((0, io_1.required)((0, io_1.optionalArg)(args.appId), "app id"), args);
// `cw app run <app-id>` — plan+drive+report an app in one call. 2-token
// cli.path found before the ["app"] usage catch-all; `appRunCli` reads the
// app id from `appId`, so the first positional after "run" is forwarded as
// appId (old build: appRun(runner, { ...options, appId: <positional> })).
(0, registry_core_1.attachCliBinding)("app.run", {
path: ["app", "run"],
jsonMode: "default",
handler: (args) => ({ json: (0, app_run_cli_1.appRunCli)({ ...args.options, appId: (0, io_1.required)(args.positionals[0], "app id") }) }),
});
// A 1-token `["app"]` row that exists ONLY to own the fixed usage string
// for an unrecognized `app` subcommand (`app run` is not yet CLI-wired at
// this milestone — cw_app_run stays MCP-only — so a bogus or `run`
// subcommand both fall through to this same usage throw, matching
// SPEC/cli-surface.md's "Usage strings" table byte-for-byte). Per
// dispatchTable's reversed-candidate-order contract (cli/dispatch.ts),
// this 1-token row is only ever reached when no 2-token `app.*` row
// above matched. `hiddenFromHelp` keeps it off `cw help app`'s own line
// (see CliBinding.hiddenFromHelp's doc comment).
(0, registry_core_1.addCliOnlyCapability)("app.usage", "cw app list|show|validate|init|package|run [app-id|path] — the workflow-app framework.", {
path: ["app"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js app list|show|validate|init|package|run [app-id|path]");
},
}, "app.usage exists only to own the fixed usage-error text for an unrecognized app subcommand; every real app.* action is its own capability row above.");
// ---------------------------------------------------------------------
// 1-token usage-fallback rows: one per multi-verb family, each existing
// ONLY to own the fixed usage string for an unrecognized subcommand,
// same pattern and reasoning as app.usage above (SPEC/cli-surface.md's
// "Usage strings" table, byte-for-byte). Per dispatchTable's reversed-
// candidate-order contract (cli/dispatch.ts), each 1-token row here is
// only ever reached when no 2-token real row for that family matched.
// `hiddenFromHelp` keeps each off its own `cw help <verb>` line.
// ---------------------------------------------------------------------
(0, registry_core_1.addCliOnlyCapability)("sandbox.usage", "cw.js sandbox list|show|validate|choose|resolve [profile-id|profile-file]", {
path: ["sandbox"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js sandbox list|show|validate|choose|resolve [profile-id|profile-file]");
},
}, "sandbox.usage exists only to own the fixed usage-error text for an unrecognized sandbox subcommand; every real sandbox.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("state.usage", "cw.js state check <run-id> [--state PATH] [--write]", {
path: ["state"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js state check <run-id> [--state PATH] [--write]");
},
}, "state.usage exists only to own the fixed usage-error text for an unrecognized state subcommand; every real state.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("audit.usage", "cw.js audit summary|worker|provenance|multi-agent|policy|role|blackboard|judge|attest|decision <run-id> [worker-id|role-id]", {
path: ["audit"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js audit summary|worker|provenance|multi-agent|policy|role|blackboard|judge|attest|decision <run-id> [worker-id|role-id]");
},
}, "audit.usage exists only to own the fixed usage-error text for an unrecognized audit subcommand; every real audit.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("blackboard.usage", "cw.js blackboard summary|summarize|graph|resolve <run-id> | topic create <run-id> | message post|list <run-id> | context put <run-id> | artifact add|list <run-id> | snapshot <run-id>", {
path: ["blackboard"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js blackboard summary|summarize|graph|resolve <run-id> | topic create <run-id> | message post|list <run-id> | context put <run-id> | artifact add|list <run-id> | snapshot <run-id>");
},
}, "blackboard.usage exists only to own the fixed usage-error text for an unrecognized blackboard subcommand; every real blackboard.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("candidate.usage", "cw.js candidate list|show|register|score|rank|select|reject|summary <run-id> [candidate-id]", {
path: ["candidate"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js candidate list|show|register|score|rank|select|reject|summary <run-id> [candidate-id]");
},
}, "candidate.usage exists only to own the fixed usage-error text for an unrecognized candidate subcommand; every real candidate.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("comment.usage", "cw.js comment add <kind> <run-id> <target-id> --body <text> | comment list <run-id> [--json]", {
path: ["comment"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js comment add <kind> <run-id> <target-id> --body <text> | comment list <run-id> [--json]");
},
}, "comment.usage exists only to own the fixed usage-error text for an unrecognized comment subcommand; every real comment.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("eval.usage", "cw.js eval snapshot <run-id> --id <snapshot-id> | replay <snapshot-id-or-path> | compare <baseline-id-or-path> <replay-id-or-path> | score <replay-id-or-path> | gate <suite-id-or-path> | report <replay-id-or-path>", {
path: ["eval"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js eval snapshot <run-id> --id <snapshot-id> | replay <snapshot-id-or-path> | compare <baseline-id-or-path> <replay-id-or-path> | score <replay-id-or-path> | gate <suite-id-or-path> | report <replay-id-or-path>");
},
}, "eval.usage exists only to own the fixed usage-error text for an unrecognized eval subcommand; every real eval.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("telemetry.usage", "cw.js telemetry verify <run-id> [--pubkey <pem-or-path>] [--json]", {
path: ["telemetry"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js telemetry verify <run-id> [--pubkey <pem-or-path>] [--json]");
},
}, "telemetry.usage exists only to own the fixed usage-error text for an unrecognized telemetry subcommand; every real telemetry.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("demo.usage", "cw.js demo tamper|bundle [--json]", {
path: ["demo"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js demo tamper|bundle [--json]");
},
}, "demo.usage exists only to own the fixed usage-error text for an unrecognized demo subcommand; every real demo.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("multi-agent.usage", "cw.js multi-agent run|status|step|blackboard|score|select|summary|summarize|graph|dependencies|failures|evidence|reasoning|show|role|group|membership|fanout|fanin <run-id> [id]", {
path: ["multi-agent"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js multi-agent run|status|step|blackboard|score|select|summary|summarize|graph|dependencies|failures|evidence|reasoning|show|role|group|membership|fanout|fanin <run-id> [id]");
},
}, "multi-agent.usage exists only to own the fixed usage-error text for an unrecognized multi-agent subcommand; every real multi-agent.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("node.usage", "cw.js node list|show|graph|snapshot|diff|replay|verify <run-id> [node-id|snapshot-id|replay-id]", {
path: ["node"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js node list|show|graph|snapshot|diff|replay|verify <run-id> [node-id|snapshot-id|replay-id]");
},
}, "node.usage exists only to own the fixed usage-error text for an unrecognized node subcommand; every real node.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("backend.usage", "cw.js backend list|show|probe [backend-id] | cw.js backend agent config [show|set] [--agent-command ... --agent-endpoint ... --agent-model ...]", {
path: ["backend"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js backend list|show|probe [backend-id] | cw.js backend agent config [show|set] [--agent-command ... --agent-endpoint ... --agent-model ...]");
},
}, "backend.usage exists only to own the fixed usage-error text for an unrecognized backend subcommand; every real backend.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("contract.usage", "cw.js contract show <run-id> [contract-id]", {
path: ["contract"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js contract show <run-id> [contract-id]");
},
}, "contract.usage exists only to own the fixed usage-error text for an unrecognized contract subcommand; every real contract.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("migration.usage", "cw.js migration list|check|prove [target] [--contract run-state|workflow-app]", {
path: ["migration"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js migration list|check|prove [target] [--contract run-state|workflow-app]");
},
}, "migration.usage exists only to own the fixed usage-error text for an unrecognized migration subcommand; every real migration.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("feedback.usage", "cw.js feedback list|show|summary|collect|task|resolve <run-id> [feedback-id]", {
path: ["feedback"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js feedback list|show|summary|collect|task|resolve <run-id> [feedback-id]");
},
}, "feedback.usage exists only to own the fixed usage-error text for an unrecognized feedback subcommand; every real feedback.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("metrics.usage", "cw.js metrics show <run-id> | metrics summary [--scope repo|home] [--pricing <path>|default] [--limit N] [--json]", {
path: ["metrics"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js metrics show <run-id> | metrics summary [--scope repo|home] [--pricing <path>|default] [--limit N] [--json]");
},
}, "metrics.usage exists only to own the fixed usage-error text for an unrecognized metrics subcommand; every real metrics.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("operator.usage", "cw.js operator status|report <run-id> [--json]", {
path: ["operator"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js operator status|report <run-id> [--json]");
},
}, "operator.usage exists only to own the fixed usage-error text for an unrecognized operator subcommand; every real operator.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("topology.usage", "cw.js topology list|show <topology-id>|show <run-id> <topology-run-id>|validate <topology-id>|apply <run-id> <topology-id>|summary <run-id>|graph <run-id>", {
path: ["topology"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js topology list|show <topology-id>|show <run-id> <topology-run-id>|validate <topology-id>|apply <run-id> <topology-id>|summary <run-id>|graph <run-id>");
},
}, "topology.usage exists only to own the fixed usage-error text for an unrecognized topology subcommand; every real topology.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("summary.usage", "cw.js summary refresh|show <run-id> [--json]", {
path: ["summary"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js summary refresh|show <run-id> [--json]");
},
}, "summary.usage exists only to own the fixed usage-error text for an unrecognized summary subcommand; every real summary.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("workbench.usage", "cw.js workbench serve [--port N] [--once] [--require-token] | view <run-id> [--json]", {
path: ["workbench"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js workbench serve [--port N] [--once] [--require-token] | view <run-id> [--json]");
},
}, "workbench.usage exists only to own the fixed usage-error text for an unrecognized workbench subcommand; every real workbench.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("worker.usage", "cw.js worker list|summary|show|manifest|output|fail|validate <run-id> [worker-id] [result-file]", {
path: ["worker"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js worker list|summary|show|manifest|output|fail|validate <run-id> [worker-id] [result-file]");
},
}, "worker.usage exists only to own the fixed usage-error text for an unrecognized worker subcommand; every real worker.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("review.usage", "cw.js review status <run-id> [--json] | review policy <run-id> --required-approvals N --authorized-roles a,b --applies-to commit,selection", {
path: ["review"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js review status <run-id> [--json] | review policy <run-id> --required-approvals N --authorized-roles a,b --applies-to commit,selection");
},
}, "review.usage exists only to own the fixed usage-error text for an unrecognized review subcommand; every real review.* action is its own capability row above.");
(0, registry_core_1.addCliOnlyCapability)("coordinator.usage", "cw.js coordinator summary <run-id> | coordinator decision <run-id> --kind <kind> --outcome <outcome> --reason TEXT", {
path: ["coordinator"],
jsonMode: "default",
hiddenFromHelp: true,
handler: () => {
throw new Error("Usage: cw.js coordinator summary <run-id> | coordinator decision <run-id> --kind <kind> --outcome <outcome> --reason TEXT");
},
}, "coordinator.usage exists only to own the fixed usage-error text for an unrecognized coordinator subcommand; every real coordinator.* action is its own capability row above.");
// ---- man (CLI-only; raw manual-page bytes to stdout, no MCP peer) -----
//
// Writes the resolved doc file's raw bytes directly to stdout and
// returns an empty result — the generic renderCliResult (cli/dispatch.ts)
// always appends "\n" to `result.text` when it is missing one, which
// would violate "no added trailing newline" for any manual page that
// does not already end in one. A handler performing its own stdout write
// and returning `{}` is the established escape hatch (see
// workbench.serve's handler above for the same pattern/reasoning).
(0, registry_core_1.addCliOnlyCapability)("man", "cw man <topic> — read a manual page from docs/ (raw bytes, no added newline).", {
path: ["man"],
jsonMode: "human",
// core/format/help.ts's COMMAND_HELP_ROWS.man already owns the
// human-facing "cw man" help line (byte-ported from the old build's
// orchestrator.ts help table); hiddenFromHelp avoids a duplicate row.
hiddenFromHelp: true,
handler: (args) => {
const topic = args.positionals[0];
if (!topic) {
throw new Error("Missing topic.\n Tip: cw man release-tooling for the release tooling manual.");
}
process.stdout.write((0, man_cli_1.readManPage)(topic));
return {};
},
}, "man is a CLI-only raw-file reader over docs/; the old build never gave it an MCP peer.");
// ---- info (CLI-only; mirrors app.show with a human card by default) ----
(0, registry_core_1.addCliOnlyCapability)("info", "Show a workflow app's contract as a human card (or JSON with --json).", {
path: ["info"],
jsonMode: "flag",
handler: (args) => {
const appId = (0, io_1.required)(args.positionals[0], "workflow app id");
const data = (0, workflow_app_loader_1.showWorkflowApp)(appId);
return { json: data, text: `${(0, help_1.formatInfo)(appId, data)}\n` };
},
}, "info is a CLI-only convenience card over app.show; the old build never gave it an MCP peer.");
// ---- PARITY WIRING -------------------------------------------------------
//
// `next` had a raw `case "next"` arm in cli/dispatch.ts (a milestone 3/6
// PLACEHOLDER that always throws "not implemented in this milestone") but
// no row in this table's cli binding, so it had no cli-mcp-parity-smoke /
// cli-jsonmode-parity-smoke coverage. This row makes `next` a real,
// dual-bound capability (matching the old build's `cli: { path: ["next"],
// jsonMode: "default" }`) with the SAME placeholder body as the dispatch.ts
// arm — no new capability logic, just giving the existing placeholder a
// home in the one data table. dispatchTable() in cli/dispatch.ts tries this
// row before the switch statement is reached, so the old `case "next"` arm
// is now dead code for the CLI path (left in place, like the other
// superseded arms in that file, each with its own "dispatchTable() above
// always matches first" note).
(0, registry_core_1.attachCliBinding)("next", {
path: ["next"],
jsonMode: "default",
handler: (args) => ({ json: (0, state_cli_1.nextCli)((0, io_1.required)((0, io_1.optionalArg)(args.positionals[0]), "run id"), args.options) }),
});
registry_core_1.REGISTRY_BY_CAPABILITY.get("next").mcp.handler = (args) => (0, state_cli_1.nextCli)((0, io_1.required)((0, io_1.optionalArg)(args.runId), "run id"), args);
// `ledger.propose`/`.review`/`.verify`/`.apply`/`.list` are documented
// payload-probe opt-outs in the old build (each mints a fresh timestamped/
// digested entry, or reads args that arrive by --file/stdin on the CLI vs
// a plain `entry` argument over MCP, or reads an on-disk ledger directory
// the generic probe does not populate) — same reasoning applies here
// unchanged, since both surfaces still route through the same
// buildLedgerProposal/buildLedgerReview/verifyLedgerEntry/
// applyLedgerProposal/listLedgerEntries core. Ported so these rows do not
// sit unclassified in the payload-identity probe.
registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.propose").payloadIdentical = false;
registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.propose").reason =
"Mints a fresh entry each call: createdAt is the wall-clock instant and the id/digest are derived from it, so the output is inherently non-deterministic and a byte-identity probe does not apply. Both surfaces call the same buildLedgerProposal core; round-trip + fail-closed behavior is covered by ledger-verify-smoke.";
registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.review").payloadIdentical = false;
registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.review").reason =
"Mints a fresh timestamped/digested verdict each call — non-deterministic output, same reasoning as ledger.propose. Both surfaces call the same buildLedgerReview core.";
registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.verify").payloadIdentical = false;
registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.verify").reason =
"The entry arrives by --file/stdin on the CLI and as an `entry` argument over MCP; there is no shared arg-bag the byte-identity probe can feed both. Both surfaces call the same verifyLedgerEntry core; ledger-verify-smoke proves the fail-closed contract.";
registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.apply").payloadIdentical = false;
registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.apply").reason =
"The entry arrives by --file/stdin on the CLI and as an `entry` argument over MCP; there is no shared arg-bag the byte-identity probe can feed both. Both surfaces call the same applyLedgerProposal core (a fail-closed wrapper over verifyLedgerEntry); ledger-apply-smoke proves the diff only escapes a verified proposal.";
registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.list").payloadIdentical = false;
registry_core_1.REGISTRY_BY_CAPABILITY.get("ledger.list").reason =
"Output depends on the on-disk contents of the named ledger directory/directories, which the generic payload probe does not populate. Both surfaces call the same listLedgerEntries/unionLedgerEntries core; ledger-verify-smoke covers the fail-closed inbox and the multi-mirror union.";
// ---------------------------------------------------------------------------
# Trust Audit Anchor
CW v0.2.1 adds the Trust Audit Anchor: a way to see when the END of a run's
trust-audit log was cut off. The hash chain in `audit/events.jsonl` lets
`cw audit verify` see an edited event, a removed middle event, a bad line, and
a mixed-era forgery. But one tamper shape gets past a pure chain walk: take
the last N lines off the file, and what is left is a shorter but fully
consistent chain — verify stays green. The anchor closes that hole, and it
does so without changing any old output byte.
## Design Discipline
- Mechanism, not policy: the kernel gives you two small parts — a read of the
chain head, and a check against a head you saved earlier. WHEN you save a
head (after a run, before you publish, at export time) is your policy.
- Fail-closed: a saved head that is not on the chain, or an event count that
comes up short, makes verify exit non-zero with the distinct check code
`trust-audit-truncated`. A bad `--expect-head` / `--expect-count` value is
an error, never a check silently made weaker.
- POLA: with no anchor flags, `cw audit verify` output is byte-for-byte what
it was before this feature (no `anchor` key, same checks, same exit rules).
- Reuse: the anchor rides on the existing eventHash chain — no new file, no
new state, no new hash form. `cw audit head` is a read-only projection.
- Parity: `audit.head` is on both front doors (`cw audit head` and the MCP
tool `cw_audit_head`); the anchor args are on both `cw audit verify` and
`cw_audit_verify` (`expectHead` / `expectCount`).
## CLI
```text
node dist/cli.js audit head <run-id>
# -> { "schemaVersion": 1, "runId": "...", "eventCount": 87,
# "headHash": "sha256:..." }
node dist/cli.js audit verify <run-id> --expect-head <hash> --expect-count <n>
# green: verified true, "anchor": { ..., "satisfied": true }, exit 0
# cut tail: verified false, checks carry trust-audit-truncated, exit 1
```
The head is the hash the NEXT appended event will link from: the last event's
`eventHash`, or the run's genesis hash when the log is empty. Save the pair
`{headHash, eventCount}` somewhere the log's writer cannot reach — a CI
variable, a note in your PR, the output of `cw run export` (the export
manifest hashes the event log bytes, so an export IS an anchor in file form).
## How the check works
`verifyTrustAudit` walks the chain as before, and keeps the trail of head
hashes it saw (genesis, then the hash after each event). With an anchor:
- `expectCount`: the walked log must have at least that many events. Fewer =
`trust-audit-truncated` (check name `anchor-count`).
- `expectHead`: the saved head must be ON the trail. A log that was cut and
then padded back with new events reaches the old count, but the new events
link from an earlier point — the old head is no longer on the trail, so
this still fails (check name `anchor-head`).
## Compatibility
Trust Audit Anchor is introduced in CW v0.2.1. Fields are additive and
optional; older run state loads unchanged. A plain `cw audit verify` keeps
its exact old output. The `anchor` key appears in the JSON only when the
caller passed an anchor flag.
## See Also
security-trust-hardening(7), cli-mcp-parity(7), report-verifiable-bundle(7)
0.2.2
{
"builtinViolations": {
"core/state/run-paths.ts": [
"node:fs"
],
"core/trust/telemetry-attestation.ts": [
"node:fs"
]
},
"clockEnvCounts": {
"core/multi-agent/candidate-scoring.ts": {
"new Date()": 1
},
"core/pipeline/runner.ts": {
"new Date()": 1
},
"core/state/migrations.ts": {
"process.cwd()": 1
},
"core/state/state-explosion/digest.ts": {
"new Date()": 1
},
"core/state/state-explosion/graph.ts": {
"new Date()": 1
},
"core/state/state-explosion/report.ts": {
"new Date()": 1
},
"core/state/state-node.ts": {
"new Date()": 5
},
"core/trust/evidence-grounding.ts": {
"process.env": 3
}
},
"layerViolations": {
"core/capability-table.ts": [
"../wiring/capability-table"
],
"wiring/capability-table/exec-backend.ts": [
"../../cli/io"
],
"wiring/capability-table/multi-agent.ts": [
"../../cli/io"
],
"wiring/capability-table/pipeline.ts": [
"../../cli/io"
],
"wiring/capability-table/registry-core.ts": [
"../../cli/io"
],
"wiring/capability-table/reporting.ts": [
"../../cli/io"
],
"wiring/capability-table/scheduling-registry.ts": [
"../../cli/io"
],
"wiring/capability-table/state.ts": [
"../../cli/io"
],
"wiring/capability-table/trust-ledger.ts": [
"../../cli/io"
],
"wiring/capability-table/workflow-apps.ts": [
"../../cli/io"
]
}
}
#!/usr/bin/env node
"use strict";
// purity-gate — fail closed if src/ crosses a layer boundary the rebuild
// docs claim is enforced, or if src/core/** reads an impure primitive
// (node:fs/child_process/net/http, process.env, process.cwd(), Date.now(),
// new Date(), Math.random()), beyond a committed, itemized baseline.
//
// Why this exists: docs/rebuild/PLAN.md and AGENTS.md both describe core/
// as pure (no IO) and shell/ as the only impure layer, and say this is
// "enforced by a lint rule". No such lint exists anywhere in scripts/ or
// package.json — only `tsc --noEmit`, which does not check import
// direction. The one place the rule is already broken is the hub itself:
// core/capability-table.ts imports ~30 things from ../shell.
//
// This is a RATCHET, not a clean-slate rule (matching dist-drift-check.js's
// and version-sync-check.js's style: node + git only, no new dependency).
// scripts/purity-baseline.json lists every violation that exists TODAY.
// The gate fails on:
// - a violation NOT in the baseline (a NEW break), and
// - a baseline entry that no longer matches reality (STALE — either the
// violation was fixed, in which case delete the entry, or the count
// changed, in which case update it consciously).
// Fail-closed both directions, same as dist-drift-check.js's added/changed/
// removed three-way diff.
//
// Layers (by top-level directory under src/):
// core/** -> may only import core/** (+ node:path, node:crypto)
// shell/** -> may import core/** or shell/**
// wiring/** -> may import core/**, shell/**, or wiring/** (not yet used;
// reserved for the capability-table split)
// cli/** -> may import core/**, shell/**, or cli/** (never mcp/**)
// mcp/** -> may import core/**, shell/**, or mcp/** (never cli/**)
//
// Approach: a plain-text specifier scan (import/export-from/require), not a
// real TS parser — this codebase uses only those three forms, verified
// against every current violation this baseline lists.
const fs = require("node:fs");
const path = require("node:path");
const packageDir = path.resolve(__dirname, "..");
const srcDir = path.join(packageDir, "src");
const baselinePath = path.join(__dirname, "purity-baseline.json");
const CORE_ALLOWED_BUILTINS = new Set(["node:path", "node:crypto"]);
const IMPURE_PATTERNS = ["process.env", "process.cwd()", "Date.now()", "new Date()", "Math.random()"];
const ALLOWED_TARGET_LAYERS = {
core: new Set(["core"]),
shell: new Set(["core", "shell"]),
wiring: new Set(["core", "shell", "wiring"]),
cli: new Set(["core", "shell", "cli"]),
mcp: new Set(["core", "shell", "mcp"]),
other: new Set(["core", "shell", "wiring", "cli", "mcp", "other"]),
};
function layerOf(relPath) {
if (relPath.startsWith("core/")) return "core";
if (relPath.startsWith("shell/")) return "shell";
if (relPath.startsWith("wiring/")) return "wiring";
if (relPath.startsWith("cli/") || relPath === "cli") return "cli";
if (relPath.startsWith("mcp/") || relPath === "mcp-server") return "mcp";
return "other";
}
function listTsFiles(dir) {
const out = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) out.push(...listTsFiles(full));
else if (entry.name.endsWith(".ts")) out.push(full);
}
return out;
}
// This codebase's file-header comments routinely quote the EXACT patterns
// this gate looks for, in prose explaining why a file is pure (e.g. "never
// a top-level `require(\"node:fs\")`", "no fs, no process.env, no clock").
// A naive text scan reads those quotes as violations. Strip `//` and
// `/* */` comments before scanning (string/template literal contents are
// left untouched, so a real `from "..."` clause is never damaged); newlines
// are preserved so extractSpecifiers' `(?:^|\n)` anchor still lines up.
function stripComments(text) {
let out = "";
let mode = "code"; // "code" | "line" | "block" | quote character
for (let i = 0; i < text.length; i++) {
const c = text[i];
const c2 = text[i + 1];
if (mode === "line") {
if (c === "\n") { mode = "code"; out += c; }
continue;
}
if (mode === "block") {
if (c === "*" && c2 === "/") { mode = "code"; i++; continue; }
if (c === "\n") out += c;
continue;
}
if (mode === '"' || mode === "'" || mode === "`") {
out += c;
if (c === "\\") { out += c2 ?? ""; i++; continue; }
if (c === mode) mode = "code";
continue;
}
if (c === "/" && c2 === "/") { mode = "line"; i++; continue; }
if (c === "/" && c2 === "*") { mode = "block"; i++; continue; }
if (c === '"' || c === "'" || c === "`") { mode = c; out += c; continue; }
out += c;
}
return out;
}
// Every `from "spec"` (import or export-from, type or value — a type-only
// import still names a module, so it counts the same way a real dependency
// scan would) plus every `require("spec")`. Non-greedy across `from` finds
// each import's own clause without needing a real parser.
function extractSpecifiers(codeText) {
const specs = [];
const fromRe = /(?:^|\n)\s*(?:import|export)\s[\s\S]*?from\s+["']([^"']+)["']/g;
let m;
while ((m = fromRe.exec(codeText))) specs.push(m[1]);
const requireRe = /require\(\s*["']([^"']+)["']\s*\)/g;
while ((m = requireRe.exec(codeText))) specs.push(m[1]);
return specs;
}
function countOccurrences(text, needle) {
let count = 0;
let index = 0;
while ((index = text.indexOf(needle, index)) !== -1) {
count += 1;
index += needle.length;
}
return count;
}
function relSrcPath(absPath) {
return path.relative(srcDir, absPath).split(path.sep).join("/");
}
function scan() {
const layerViolations = {}; // relFile -> string[] (specs that cross a disallowed layer)
const builtinViolations = {}; // relFile -> string[] (banned node builtins imported from core)
const clockEnvCounts = {}; // relFile -> { pattern: count } (core only, count > 0)
for (const absFile of listTsFiles(srcDir)) {
const relFile = relSrcPath(absFile);
const fileLayer = layerOf(relFile.replace(/\.ts$/, ""));
const text = stripComments(fs.readFileSync(absFile, "utf8"));
for (const spec of extractSpecifiers(text)) {
if (spec.startsWith(".")) {
const targetAbs = path.resolve(path.dirname(absFile), spec);
const targetRel = relSrcPath(targetAbs);
const targetLayer = layerOf(targetRel);
if (!ALLOWED_TARGET_LAYERS[fileLayer].has(targetLayer)) {
(layerViolations[relFile] = layerViolations[relFile] || []).push(spec);
}
} else if (spec.startsWith("node:")) {
if (fileLayer === "core" && !CORE_ALLOWED_BUILTINS.has(spec)) {
(builtinViolations[relFile] = builtinViolations[relFile] || []).push(spec);
}
}
// Bare package specifiers (no "." or "node:" prefix): this package
// has zero runtime dependencies, so there are none to check.
}
if (fileLayer === "core") {
const counts = {};
for (const pattern of IMPURE_PATTERNS) {
const n = countOccurrences(text, pattern);
if (n > 0) counts[pattern] = n;
}
if (Object.keys(counts).length > 0) clockEnvCounts[relFile] = counts;
}
}
for (const bucket of [layerViolations, builtinViolations]) {
for (const file of Object.keys(bucket)) bucket[file] = [...new Set(bucket[file])].sort();
}
return { layerViolations, builtinViolations, clockEnvCounts };
}
function sortedKeys(obj) {
return Object.keys(obj).sort();
}
function diffListBuckets(actual, baseline, label, problems) {
const files = new Set([...sortedKeys(actual), ...sortedKeys(baseline)]);
for (const file of [...files].sort()) {
const actualList = actual[file] || [];
const baseList = baseline[file] || [];
const actualSet = new Set(actualList);
const baseSet = new Set(baseList);
const added = actualList.filter((s) => !baseSet.has(s));
const removed = baseList.filter((s) => !actualSet.has(s));
for (const spec of added) problems.push(`NEW ${label}: ${file} imports ${spec} (not in purity-baseline.json)`);
for (const spec of removed) problems.push(`STALE ${label} baseline entry: ${file} no longer imports ${spec} — delete it from purity-baseline.json`);
}
}
function diffCountBuckets(actual, baseline, problems) {
const files = new Set([...sortedKeys(actual), ...sortedKeys(baseline)]);
for (const file of [...files].sort()) {
const actualCounts = actual[file] || {};
const baseCounts = baseline[file] || {};
const patterns = new Set([...Object.keys(actualCounts), ...Object.keys(baseCounts)]);
for (const pattern of [...patterns].sort()) {
const a = actualCounts[pattern] || 0;
const b = baseCounts[pattern] || 0;
if (a !== b) {
problems.push(`clock/env count drift: ${file} has ${a} occurrence(s) of "${pattern}", purity-baseline.json expects ${b} — update the baseline consciously (this catches new AND removed occurrences)`);
}
}
}
}
function main() {
if (!fs.existsSync(baselinePath)) {
process.stderr.write(`purity gate: missing ${path.relative(packageDir, baselinePath)}\n`);
process.exit(1);
}
const baseline = JSON.parse(fs.readFileSync(baselinePath, "utf8"));
const actual = scan();
const problems = [];
diffListBuckets(actual.layerViolations, baseline.layerViolations || {}, "layer violation", problems);
diffListBuckets(actual.builtinViolations, baseline.builtinViolations || {}, "core builtin violation", problems);
diffCountBuckets(actual.clockEnvCounts, baseline.clockEnvCounts || {}, problems);
if (problems.length > 0) {
process.stderr.write(`purity gate: ${problems.length} problem(s)\n`);
for (const p of problems) process.stderr.write(` ${p}\n`);
process.exit(1);
}
process.stdout.write("purity gate: src/ matches the committed baseline (no new core/shell layer breaks, no clock/env drift).\n");
}
main();
# workflows/ — legacy compatibility surface (pinned, do not remove)
`architecture-review.workflow.js` and `research-synthesis.workflow.js` are
the old, pre-`apps/` workflow-file format. The real, current homes for
these two workflows are `apps/architecture-review/` and
`apps/research-synthesis/` — these files exist ONLY so an id from before
the `apps/` format still resolves.
This is deliberate duplication, not drift to clean up: each file here
registers its OWN distinct id (`legacy-architecture-review`,
`legacy-research-synthesis`), pinned by
`v2/conformance/cases/multiagent-app-list.case.js` and
`plugins/cool-workflow/test/workflow-app-framework-smoke.js`. `cw list` /
`cw app list` show both the real app and its legacy id side by side
(`src/shell/workflow-app-loader.ts` discovers both roots).
Per this project's own POLA rule, removing or thinning either file would
change `cw list`'s output bytes — that is a breaking change, only doable
behind a major-version break, not a routine cleanup.
+1
-1
{
"name": "cool-workflow",
"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.",
"version": "0.2.1",
"version": "0.2.2",
"author": {

@@ -6,0 +6,0 @@ "name": "COOLWHITE LLC"

{
"name": "cool-workflow",
"version": "0.2.1",
"version": "0.2.2",
"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,0 +5,0 @@ "author": {

@@ -6,3 +6,3 @@ {

"summary": "Run a shorter architecture review with parallel map and assess phases for faster first results.",
"version": "0.2.1",
"version": "0.2.2",
"author": "COOLWHITE LLC",

@@ -9,0 +9,0 @@ "inputs": [

@@ -6,3 +6,3 @@ {

"summary": "Map a repository architecture, assess risks, verify important findings, and synthesize an evidence-backed verdict.",
"version": "0.2.1",
"version": "0.2.2",
"author": "COOLWHITE LLC",

@@ -9,0 +9,0 @@ "inputs": [

@@ -6,3 +6,3 @@ {

"summary": "Deterministic one-worker workflow app for proving the CW integration chain.",
"version": "0.2.1",
"version": "0.2.2",
"author": "COOLWHITE LLC",

@@ -9,0 +9,0 @@ "inputs": [

@@ -6,3 +6,3 @@ {

"summary": "Review a pull request or branch, inspect CI failures, diagnose actionable issues, optionally patch, verify, and summarize with evidence.",
"version": "0.2.1",
"version": "0.2.2",
"author": "COOLWHITE LLC",

@@ -9,0 +9,0 @@ "inputs": [

@@ -6,3 +6,3 @@ {

"summary": "Prepare a release with checklist discipline: version checks, changelog, tests, packaging, release notes, and final verification.",
"version": "0.2.1",
"version": "0.2.2",
"author": "COOLWHITE LLC",

@@ -9,0 +9,0 @@ "inputs": [

@@ -6,3 +6,3 @@ {

"summary": "Split a research question into claims, investigate sources, cross-check evidence, verify claims, and synthesize a concise answer.",
"version": "0.2.1",
"version": "0.2.2",
"author": "COOLWHITE LLC",

@@ -9,0 +9,0 @@ "inputs": [

@@ -30,3 +30,2 @@ "use strict";

const io_1 = require("./io");
const workflow_app_loader_1 = require("../shell/workflow-app-loader");
function firstPositional(args, index = 0) {

@@ -79,22 +78,9 @@ return args.positionals[index];

}
// MILESTONE 12 (workflow-apps) — `search` filters the SAME real app
// discovery `cw app list`/`cw list` use (shell/workflow-app-loader.ts),
// by id/title/summary, matching `listApps` in the old build.
function formatSearchResults(keyword, results) {
if (results.length === 0) {
return `No workflows matched "${keyword}".\n Tip: cw list for all available workflows.`;
}
const lines = [`${results.length} workflow${results.length === 1 ? "" : "s"} matching "${keyword}"`];
for (const r of results) {
lines.push(` ${r.id} — ${r.title}`);
const cut = r.summary.length > 120 ? `${r.summary.slice(0, 119)}…` : r.summary;
lines.push(` ${cut}`);
}
lines.push("");
lines.push("Use cw info <id> for full details.");
return lines.join("\n");
}
/** The milestone-1 carry-over switch. See file header: never extended
* again — each arm here is replaced by a capability-table row when its
* own build-order milestone lands, not edited in place. */
* own build-order milestone lands, not edited in place. Only entry-level
* concerns live here now (bare help, and the two verbs a real
* capability-table row can never fully own): every other verb this
* switch used to carry is now a table row (see the NOTEs below) and
* dispatchTable() above always matches it first. */
function dispatchLegacy(args) {

@@ -119,34 +105,11 @@ switch (args.command) {

// matches first, per the Revision note's "table rows, never a new
// switch arm" rule. Same for "list", "status", and "sandbox list".
// `search` filters the real app discovery by title/summary/id (see
// note above); MILESTONE 12.
case "search": {
const keyword = args.positionals.join(" ");
if (!keyword.trim()) {
throw new Error('Missing search keyword.\n Tip: cw search architecture to find workflows about architecture.');
}
const lower = keyword.toLowerCase();
const results = (0, workflow_app_loader_1.listWorkflowApps)()
.filter((a) => String(a.title).toLowerCase().includes(lower) ||
String(a.summary).toLowerCase().includes(lower) ||
String(a.id).toLowerCase().includes(lower))
.map((a) => ({ id: String(a.id), title: String(a.title), summary: String(a.summary) }));
if ((0, io_1.wantsJson)(args.options)) {
(0, io_1.printJson)(results);
}
else {
process.stdout.write(`${formatSearchResults(keyword, results)}\n`);
}
return;
}
// switch arm" rule. Same for "list", "status", "sandbox list", and
// "search" (a cli-only row, hiddenFromHelp, so `cw help search` keeps
// its existing "Unknown command" text).
// NOTE: "plan" and "quickstart" are not arms here any more — both are
// now real capability-table rows (core/capability-table.ts, milestone
// 6+7) that dispatchTable() above always matches first.
// PLACEHOLDER (milestone 3/6, state kernel + pipeline) — real `next`
// loads run state and returns dispatchable tasks; this milestone only
// reproduces the io.required missing-run-id refusal.
case "next": {
const runId = (0, io_1.required)((0, io_1.optionalArg)(firstPositional(args)), "run id");
throw new Error(`next is not implemented in this milestone (runId=${runId})`);
}
// NOTE: "next" is not an arm here any more — it is a real
// capability-table row (core/capability-table.ts) that dispatchTable()
// above always matches first.
// MILESTONE 8 — `ledger propose|review|verify|apply|list` are now

@@ -163,26 +126,5 @@ // real capability-table rows (core/capability-table.ts) that

}
// PLACEHOLDER (milestone 10, scheduling/gc) — real `gc verify` checks
// whether a run's disk footprint was actually reclaimed; a run that
// was never reclaimed is not a failure (exit 0), which is exactly the
// shape this stub reproduces for an unresolvable run id.
case "gc": {
const sub = firstPositional(args);
if (sub === "verify") {
const runId = (0, io_1.optionalArg)(firstPositional(args, 1));
const payload = {
schemaVersion: 1,
runId: runId ?? null,
reclaimed: false,
verified: false,
tier: "live",
capability: "re-runnable",
chainLength: 0,
checks: [{ name: "located", pass: false, code: "not-reclaimed", detail: "run source not found" }],
nextAction: "node scripts/cw.js registry refresh --scope home",
};
(0, io_1.printJson)(payload);
return;
}
throw new Error(`gc ${sub ?? ""} is not implemented in this milestone`);
}
// NOTE: "gc" is not an arm here any more — gc.plan/gc.run/gc.verify
// are now real capability-table rows (core/capability-table.ts) that
// dispatchTable() above always matches first.
// NOTE: "run" is not an arm here any more — run.drive.step/run.drive

@@ -203,16 +145,6 @@ // are now real capability-table rows (core/capability-table.ts,

// new switch arm" rule.
// PLACEHOLDER (milestone 3/4, state kernel + contract-migration) —
// real `migration check`/`prove` resolve a run id or file target; the
// missing-target refusal is the only shape this milestone reproduces.
case "migration": {
const sub = firstPositional(args);
if (sub === "check" || sub === "prove") {
const target = (0, io_1.optionalArg)(firstPositional(args, 1));
if (!target) {
throw new Error('Missing target (run-id or state/app file).\n Tip: find run ids with "cw run list" or create one with "cw quickstart"');
}
throw new Error(`migration ${sub} is not implemented in this milestone`);
}
throw new Error(`migration ${sub ?? ""} is not implemented in this milestone`);
}
// NOTE: "migration" is not an arm here any more — migration.list/
// check/prove are now real capability-table rows
// (core/capability-table.ts) that dispatchTable() above always
// matches first.
// NOTE: "report" is not an arm here any more — report/report.bundle/

@@ -219,0 +151,0 @@ // report.verify-bundle are now real capability-table rows

@@ -106,3 +106,6 @@ "use strict";

* though the dispatcher handles it and formatHelp lists it — a known,
* intentionally-preserved wart (see docs/rebuild/PLAN.md "Kept byte-for-byte"). */
* intentionally-preserved wart (see docs/rebuild/PLAN.md "Kept byte-for-byte").
* NOTE: "update" is gone from the old capture's list on purpose: no code
* was behind the verb in this build, so having it here made `cw update`
* say "Did you mean: update?" — a hint that points at itself. */
exports.KNOWN_COMMANDS = new Set([

@@ -116,3 +119,3 @@ "help", "list", "doctor", "info", "search", "man", "init", "quickstart",

"sched", "gc", "telemetry", "migration", "demo", "workbench", "approve",
"reject", "comment", "handoff", "graph", "eval", "version", "update", "fix",
"reject", "comment", "handoff", "graph", "eval", "version", "fix",
]);

@@ -119,0 +122,0 @@ /** Levenshtein edit distance between two strings. */

@@ -31,5 +31,9 @@ "use strict";

exports.formatInfo = formatInfo;
exports.formatSearchResults = formatSearchResults;
const capability_table_1 = require("../capability-table");
/** src/orchestrator.ts:934-951 — the exact "More commands" token set, in
* this exact order (space-joined in the source, pipe-joined for display). */
/** src/orchestrator.ts:934-951 — the "More commands" token set, in the old
* build's order (space-joined in the source, pipe-joined for display).
* One change from the old capture: `update` is gone. The verb had no code
* behind it in this build (`cw update` said "Unknown command"), so the
* help must not offer it. See parseargv.ts KNOWN_COMMANDS. */
const MORE_COMMANDS_TOKENS = [

@@ -44,3 +48,3 @@ "list", "search", "info", "init", "plan", "status", "next", "dispatch",

"comment", "handoff", "ledger", "graph", "eval", "man", "version",
"update", "fix",
"fix",
];

@@ -210,3 +214,5 @@ const MORE_COMMANDS_WRAP_WIDTH = 76;

* stdout, so NO_COLOR/non-TTY always wins); byte content matches the
* plain-text capture at SPEC/cli-help/_root.txt exactly. */
* plain-text capture at SPEC/cli-help/_root.txt, but for the dead
* `update` lines, which were taken out on purpose (no code was behind
* the verb — see MORE_COMMANDS_TOKENS note above). */
function formatHelp() {

@@ -220,3 +226,2 @@ const moreCommandsLines = wrapPipeJoined(MORE_COMMANDS_TOKENS, MORE_COMMANDS_WRAP_WIDTH);

" version Show version",
" update Update to latest release",
" doctor Check setup",

@@ -319,1 +324,19 @@ " fix Show fix commands for setup issues",

}
/** `cw search <keyword>`'s human text — byte-exact to the milestone-1
* carry-over's own formatSearchResults (moved here from cli/dispatch.ts
* so the search capability-table row, which lives in core/, can render
* its own text without core importing from cli/). */
function formatSearchResults(keyword, results) {
if (results.length === 0) {
return `No workflows matched "${keyword}".\n Tip: cw list for all available workflows.`;
}
const lines = [`${results.length} workflow${results.length === 1 ? "" : "s"} matching "${keyword}"`];
for (const r of results) {
lines.push(` ${r.id} — ${r.title}`);
const cut = r.summary.length > 120 ? `${r.summary.slice(0, 119)}…` : r.summary;
lines.push(` ${cut}`);
}
lines.push("");
lines.push("Use cw info <id> for full details.");
return lines.join("\n");
}

@@ -39,2 +39,3 @@ "use strict";

exports.formatCommentList = formatCommentList;
const collate_1 = require("../util/collate");
exports.COLLABORATION_SCHEMA_VERSION = 1;

@@ -217,3 +218,3 @@ /** The single, honest stand-in for an absent identity. */

function compareByCreated(left, right) {
return left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id);
return (0, collate_1.stableCompare)(left.createdAt, right.createdAt) || (0, collate_1.stableCompare)(left.id, right.id);
}

@@ -458,3 +459,3 @@ function disqualify(record, policy, selfIds) {

seen.set(targetKey(record.target), record.target);
return [...seen.values()].sort((left, right) => targetKey(left).localeCompare(targetKey(right)));
return [...seen.values()].sort((left, right) => (0, collate_1.stableCompare)(targetKey(left), targetKey(right)));
}

@@ -461,0 +462,0 @@ function formatReviewStatus(report) {

@@ -50,2 +50,3 @@ "use strict";

exports.buildBlackboardGraph = buildBlackboardGraph;
const collate_1 = require("../util/collate");
exports.BLACKBOARD_SCHEMA_VERSION = 1;

@@ -424,3 +425,3 @@ /** Dedup, SORTS. Coordinator-side sorting `unique` — byte-identical

const latestSnapshot = scoped(state.snapshots)
.sort((left, right) => left.createdAt.localeCompare(right.createdAt))
.sort((left, right) => (0, collate_1.stableCompare)(left.createdAt, right.createdAt))
.at(-1);

@@ -459,3 +460,3 @@ return {

.filter((message) => (!options.blackboardId || message.blackboardId === options.blackboardId) && (!options.topicId || message.topicId === options.topicId))
.sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id));
.sort((left, right) => (0, collate_1.stableCompare)(left.createdAt, right.createdAt) || (0, collate_1.stableCompare)(left.id, right.id));
}

@@ -465,3 +466,3 @@ function listBlackboardArtifacts(state, options = {}) {

.filter((artifact) => (!options.blackboardId || artifact.blackboardId === options.blackboardId) && (!options.topicId || artifact.topicId === options.topicId))
.sort((left, right) => left.id.localeCompare(right.id));
.sort((left, right) => (0, collate_1.stableCompare)(left.id, right.id));
}

@@ -468,0 +469,0 @@ function buildBlackboardGraph(runId, state, recordPath, messagesPath) {

@@ -23,2 +23,3 @@ "use strict";

const result_normalize_1 = require("./result-normalize");
const collate_1 = require("../util/collate");
/** The sandbox profile that accepted a candidate's worker output — the

@@ -101,3 +102,3 @@ * worker's own profile when present, else the backing task's. Pure port of

.filter((s) => s.candidateId === candidateId)
.sort((a, b) => (b.selectedAt || "").localeCompare(a.selectedAt || ""))[0];
.sort((a, b) => (0, collate_1.stableCompare)(b.selectedAt || "", a.selectedAt || ""))[0];
}

@@ -104,0 +105,0 @@ function evidenceLocatorString(entry) {

@@ -32,2 +32,3 @@ "use strict";

const helpers_1 = require("./helpers");
const collate_1 = require("../../util/collate");
/** Deterministic structural summary of one (or the default) blackboard.

@@ -67,3 +68,3 @@ * Every list is sorted by id (`byId`); `recentChanges` is the last 10 by

.filter((m) => m.topicId === topic.id)
.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
.sort((a, b) => (0, collate_1.stableCompare)(a.createdAt, b.createdAt) || (0, collate_1.stableCompare)(a.id, b.id));
const last = topicMessages[topicMessages.length - 1];

@@ -186,3 +187,3 @@ return {

}))
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt) || a.id.localeCompare(b.id))
.sort((a, b) => (0, collate_1.stableCompare)(b.updatedAt, a.updatedAt) || (0, collate_1.stableCompare)(a.id, b.id))
.slice(0, 10)

@@ -189,0 +190,0 @@ .map((record) => ({

@@ -40,2 +40,3 @@ "use strict";

Object.defineProperty(exports, "byId", { enumerable: true, get: function () { return helpers_1.byId; } });
const collate_1 = require("../../util/collate");
const runtime_1 = require("../../multi-agent/runtime");

@@ -148,4 +149,4 @@ const coordinator_1 = require("../../multi-agent/coordinator");

return {
nodes: [...nodes.values()].sort((a, b) => a.kind.localeCompare(b.kind) || a.id.localeCompare(b.id)),
edges: dedupedEdges.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to) || (a.label || "").localeCompare(b.label || "")),
nodes: [...nodes.values()].sort((a, b) => (0, collate_1.stableCompare)(a.kind, b.kind) || (0, collate_1.stableCompare)(a.id, b.id)),
edges: dedupedEdges.sort((a, b) => (0, collate_1.stableCompare)(a.from, b.from) || (0, collate_1.stableCompare)(a.to, b.to) || (0, collate_1.stableCompare)(a.label || "", b.label || "")),
};

@@ -197,4 +198,4 @@ }

return {
nodes: [...nodes.values()].sort((a, b) => a.kind.localeCompare(b.kind) || a.id.localeCompare(b.id)),
edges: edges.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to) || (a.label || "").localeCompare(b.label || "")),
nodes: [...nodes.values()].sort((a, b) => (0, collate_1.stableCompare)(a.kind, b.kind) || (0, collate_1.stableCompare)(a.id, b.id)),
edges: edges.sort((a, b) => (0, collate_1.stableCompare)(a.from, b.from) || (0, collate_1.stableCompare)(a.to, b.to) || (0, collate_1.stableCompare)(a.label || "", b.label || "")),
};

@@ -476,3 +477,3 @@ }

const collapsedNodeIds = new Map(); // sourceNodeId -> syntheticId
for (const [bucketKey, ids] of [...buckets.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
for (const [bucketKey, ids] of [...buckets.entries()].sort((a, b) => (0, collate_1.stableCompare)(a[0], b[0]))) {
if (view !== "critical-path" && ids.length < thresholds.collapseBucket) {

@@ -526,7 +527,7 @@ for (const id of ids)

return finalizeGraphRecord(runId, view, options, full, {
nodes: nodes.sort((a, b) => a.kind.localeCompare(b.kind) || a.id.localeCompare(b.id)),
edges: edges.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to) || (a.label || "").localeCompare(b.label || "")),
syntheticNodes: synthetic.sort((a, b) => a.id.localeCompare(b.id)),
nodes: nodes.sort((a, b) => (0, collate_1.stableCompare)(a.kind, b.kind) || (0, collate_1.stableCompare)(a.id, b.id)),
edges: edges.sort((a, b) => (0, collate_1.stableCompare)(a.from, b.from) || (0, collate_1.stableCompare)(a.to, b.to) || (0, collate_1.stableCompare)(a.label || "", b.label || "")),
syntheticNodes: synthetic.sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id)),
critical,
});
}

@@ -36,2 +36,3 @@ "use strict";

Object.defineProperty(exports, "fingerprintStrings", { enumerable: true, get: function () { return hash_1.fingerprintStrings; } });
const collate_1 = require("../../util/collate");
/** True for `failed`, `blocked`, `rejected`, `conflicting` — the never-

@@ -87,3 +88,3 @@ * collapse status set (docs/rebuild/PLAN.md byte-compat item 9). */

function byId(a, b) {
return a.id.localeCompare(b.id);
return (0, collate_1.stableCompare)(a.id, b.id);
}

@@ -90,0 +91,0 @@ /** Whitespace-collapsed; over 80 chars becomes the first 77 chars + `...`. */

@@ -25,2 +25,3 @@ "use strict";

const hash_1 = require("../hash");
const collate_1 = require("../util/collate");
/** sha256 over the canonical content (every field except `id` and

@@ -191,3 +192,3 @@ * `digest`, which are derived FROM it). Returns the full `sha256:<hex>`

})
.sort((a, b) => a.id.localeCompare(b.id));
.sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id));
const tally = (s) => proposals.filter((p) => p.resolution === s).length;

@@ -194,0 +195,0 @@ return {

@@ -10,3 +10,3 @@ "use strict";

// conformance/cases/version-basic.case.js (regex `/^\d+\.\d+\.\d+\n$/`).
exports.CURRENT_COOL_WORKFLOW_VERSION = "0.2.1";
exports.CURRENT_COOL_WORKFLOW_VERSION = "0.2.2";
// State-kernel schema version constants (SPEC/state-core.md "Version

@@ -13,0 +13,0 @@ // constants"). Pinned to the old build's src/version.ts byte-for-byte.

@@ -47,2 +47,3 @@ "use strict";

exports.auditVerifyCli = auditVerifyCli;
exports.auditHeadCli = auditHeadCli;
exports.auditSummaryCli = auditSummaryCli;

@@ -69,8 +70,32 @@ exports.auditMultiAgentCli = auditMultiAgentCli;

}
/** Parse the optional truncation anchor off the CLI options / MCP args
* (`--expect-head <hash>` / `--expect-count <n>`; MCP: expectHead /
* expectCount). Fail-closed on a malformed count — a flag given without
* a usable value must never silently weaken the check it asked for. */
function anchorOption(args) {
const headRaw = args["expect-head"] ?? args.expectHead;
const countRaw = args["expect-count"] ?? args.expectCount;
const expectHead = optionalString(headRaw);
if (headRaw !== undefined && headRaw !== null && expectHead === undefined) {
throw new Error("audit verify: --expect-head requires a hash value");
}
let expectCount;
if (countRaw !== undefined && countRaw !== null) {
const parsed = Number(countRaw);
if (countRaw === true || !Number.isInteger(parsed) || parsed < 0) {
throw new Error("audit verify: --expect-count requires a non-negative integer");
}
expectCount = parsed;
}
if (expectHead === undefined && expectCount === undefined)
return undefined;
return { expectHead, expectCount };
}
function auditVerifyCli(runId, args) {
if (!runId)
throw new Error("audit verify requires a run id (cw audit verify <run-id>)");
const anchor = anchorOption(args);
const run = (0, run_store_1.loadRunFromCwd)(runId, invocationCwd(args));
const v = (0, trust_audit_1.verifyTrustAudit)(run);
return {
const v = (0, trust_audit_1.verifyTrustAudit)(run, anchor);
const result = {
schemaVersion: 1,

@@ -86,3 +111,22 @@ runId: run.id,

};
if (anchor) {
result.anchor = {
...(anchor.expectHead !== undefined ? { expectHead: anchor.expectHead } : {}),
...(anchor.expectCount !== undefined ? { expectCount: anchor.expectCount } : {}),
satisfied: !v.checks.some((c) => c.code === "trust-audit-truncated"),
};
}
return result;
}
/** `cw audit head <run>` — the chain head anchor (read-only projection).
* Capture it after a run (or before publishing/exporting); later,
* `cw audit verify <run> --expect-head <hash> --expect-count <n>`
* re-proves the log was not shortened since the capture. */
function auditHeadCli(runId, args) {
if (!runId)
throw new Error("audit head requires a run id (cw audit head <run-id>)");
const run = (0, run_store_1.loadRunFromCwd)(runId, invocationCwd(args));
const head = (0, trust_audit_1.trustAuditHead)(run);
return { schemaVersion: 1, runId: run.id, eventCount: head.eventCount, headHash: head.headHash };
}
/** MILESTONE 11 (reporting/observability, workbench audit panels) —

@@ -89,0 +133,0 @@ * `cw audit summary`/`audit multi-agent`/`audit policy`/`audit judge`.

@@ -49,2 +49,3 @@ "use strict";

const run_store_1 = require("./run-store");
const collate_1 = require("../core/util/collate");
function formatCommitRow(commit) {

@@ -67,3 +68,3 @@ return {

function summarizeOperatorCommits(run) {
const commits = [...(run.commits || [])].sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id));
const commits = [...(run.commits || [])].sort((left, right) => (0, collate_1.stableCompare)(left.createdAt, right.createdAt) || (0, collate_1.stableCompare)(left.id, right.id));
const rows = commits.map(formatCommitRow);

@@ -70,0 +71,0 @@ return {

@@ -10,11 +10,6 @@ "use strict";

// core/pipeline/drive-decide.ts. Sub-workflow nesting and `--incremental`
// are ported; the concurrent-round driver (driveConcurrentRound) is
// scoped down to the serial driver run through a width loop, since no
// case in this milestone's combined gate exercises true concurrent-batch
// recording order (that is `--concurrency`/parallel-phase-specific and is
// authored as its own future conformance case per Open risk 5) — the
// `mode:"parallel"` architecture-review phases still complete correctly
// through the serial per-task loop, just without the wall-clock-parallel
// spawn optimization; this is flagged here rather than silently ported as
// if fully equivalent.
// are ported; the concurrent-round driver (driveConcurrentRound, below)
// dispatches and settles a whole round's tasks in one batch (see
// `--concurrency`/`roundWidth`), pinned by
// v2/conformance/cases/pipeline-concurrent-round.case.js.
//

@@ -81,2 +76,3 @@ // Evidence: SPEC/pipeline-run.md "Drive loop — src/drive.ts".

const hash_1 = require("../core/hash");
const collate_1 = require("../core/util/collate");
const pipeline_1 = require("./pipeline");

@@ -196,3 +192,6 @@ const reporter_1 = require("./reporter");

const records = [];
for (const candidate of run.tasks.filter((t) => previousTaskIds.has(t.id)).sort((a, b) => a.id.localeCompare(b.id))) {
// stableCompare (not a bare localeCompare): this order feeds the sha256
// digest below, which feeds the incremental cache key — a host-locale-
// dependent order would silently move the cache key across machines.
for (const candidate of run.tasks.filter((t) => previousTaskIds.has(t.id)).sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id))) {
if (candidate.status !== "completed" || !candidate.resultPath || !fs.existsSync(candidate.resultPath)) {

@@ -309,3 +308,20 @@ records.push(undefined);

fs.writeFileSync(manifest.resultPath, fs.readFileSync(cachePath, "utf8"), "utf8");
// Not gated by requireAttestedTelemetry here: the underlying result was
// already gated (attested or explicitly overridden) at its FIRST
// acceptance, before it was cached. Re-blocking a cache hit would only
// punish the operator for their own earlier, already-audited accept.
// Still made visible, not silent: when the operator requires attested
// telemetry, record that this particular accept came from the cache
// rather than a freshly re-verified hop.
(0, worker_isolation_1.recordWorkerOutput)(run, workerId, manifest.resultPath);
if (ctx.config.requireAttestedTelemetry) {
(0, trust_audit_1.recordTrustAuditEvent)(run, {
kind: "telemetry.cache-accept",
decision: "recorded",
source: "cw-validated",
workerId,
taskId: selected.id,
metadata: { reason: "result-cache hit; original attestation gate applied at first acceptance, not re-verified here" },
});
}
// Advance the run lifecycle stage on accept, as the old build's

@@ -312,0 +328,0 @@ // recordWorkerOutput wrapper did (run.loopStage = "observe").

@@ -32,2 +32,3 @@ "use strict";

const trust_policy_1 = require("../core/multi-agent/trust-policy");
const collate_1 = require("../core/util/collate");
exports.EVIDENCE_REASONING_SCHEMA_VERSION = 1;

@@ -60,3 +61,3 @@ function candidatesOf(run) {

.map((evidence) => buildChain(run, evidence, { scores, auditEvents, counterfactuals }))
.sort((left, right) => statusRank(left.evidenceStatus) - statusRank(right.evidenceStatus) || left.id.localeCompare(right.id));
.sort((left, right) => statusRank(left.evidenceStatus) - statusRank(right.evidenceStatus) || (0, collate_1.stableCompare)(left.id, right.id));
const totals = summarizeTotals(chains);

@@ -345,3 +346,3 @@ const currentFingerprint = fingerprintChains(chains);

totals: report.totals,
entries: entries.sort((a, b) => a.id.localeCompare(b.id)),
entries: entries.sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id)),
paths: { reasoningDir: dir, indexPath, reportPath },

@@ -607,3 +608,3 @@ nextAction: `node scripts/cw.js multi-agent reasoning ${run.id}`,

function byRef(a, b) {
return a.ref.localeCompare(b.ref);
return (0, collate_1.stableCompare)(a.ref, b.ref);
}

@@ -36,2 +36,3 @@ "use strict";

const agent_1 = require("./agent");
const collate_1 = require("../../core/util/collate");
exports.EXECUTION_BACKEND_SCHEMA_VERSION = 1;

@@ -163,3 +164,3 @@ exports.DEFAULT_BACKEND_ID = "node";

.map((driver) => specDescriptor(driver.spec))
.sort((left, right) => left.id.localeCompare(right.id));
.sort((left, right) => (0, collate_1.stableCompare)(left.id, right.id));
}

@@ -166,0 +167,0 @@ function backendIds() {

"use strict";
// shell/execution-backend/types.ts — plain data shapes for the driver layer.
//
// MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of the shapes
// in the old build's src/types/execution-backend.ts and the sandbox slice of
// src/types/sandbox.ts that this subsystem needs. Types only — no logic — so
// this file is safe to import from both shell/ (impure) and any future core/
// caller without violating the core/shell purity split.
//
// Evidence: SPEC/execution-backend.md.
Object.defineProperty(exports, "__esModule", { value: true });

@@ -201,2 +201,8 @@ "use strict";

const FILE_LOCK_STALE_MS = 30_000;
// Lock paths this process holds right now. A nested withFileLock on the
// SAME target runs its fn directly (re-entrant) instead of waiting on its
// own lock file until the 240 tries run out — that lets a whole
// load -> change -> save cycle hold one lock while the save path inside
// it keeps its own withFileLock call unchanged.
const HELD_LOCKS = new Set();
function sleepSync(ms) {

@@ -207,5 +213,10 @@ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);

* (unless the lock was stolen mid-operation, in which case releasing would
* corrupt the thief's critical section, so it is deliberately NOT released). */
* corrupt the thief's critical section, so it is deliberately NOT released).
* Re-entrant inside one process: a nested call on the same target runs
* `fn` under the already-held lock. */
function withFileLock(targetPath, fn) {
const lock = `${targetPath}.lock`;
const heldKey = path.resolve(lock);
if (HELD_LOCKS.has(heldKey))
return fn();
fs.mkdirSync(path.dirname(lock), { recursive: true });

@@ -239,2 +250,3 @@ const pid = String(process.pid);

throw new Error(`could not acquire file lock for ${targetPath}`);
HELD_LOCKS.add(heldKey);
// Refresh mtime right before the critical section.

@@ -269,2 +281,3 @@ try {

finally {
HELD_LOCKS.delete(heldKey);
try {

@@ -271,0 +284,0 @@ // Only release if we still own the lock.

@@ -65,2 +65,3 @@ "use strict";

const trust_audit_1 = require("./trust-audit");
const collate_1 = require("../core/util/collate");
function maOf(run) {

@@ -279,3 +280,3 @@ return run.multiAgent || {};

}
return rows.filter(uniqueById).sort((left, right) => left.from.localeCompare(right.from) || left.to.localeCompare(right.to));
return rows.filter(uniqueById).sort((left, right) => (0, collate_1.stableCompare)(left.from, right.from) || (0, collate_1.stableCompare)(left.to, right.to));
}

@@ -342,3 +343,3 @@ function deriveFailures(run, dependencies) {

add(String(readySelection.id), "commit-gate", "not-ready", `selection ${readySelection.id} has no verifier-gated commit`, `node scripts/cw.js commit ${run.id} --selection ${readySelection.id} --reason "<verified rationale>"`, readySelection.candidateId);
return rows.filter(uniqueByFailure).sort((left, right) => left.kind.localeCompare(right.kind) || left.id.localeCompare(right.id));
return rows.filter(uniqueByFailure).sort((left, right) => (0, collate_1.stableCompare)(left.kind, right.kind) || (0, collate_1.stableCompare)(left.id, right.id));
}

@@ -448,3 +449,3 @@ function deriveEvidence(run) {

.map(withDisposition)
.sort((left, right) => statusRank(left.status) - statusRank(right.status) || left.id.localeCompare(right.id));
.sort((left, right) => statusRank(left.status) - statusRank(right.status) || (0, collate_1.stableCompare)(left.id, right.id));
}

@@ -451,0 +452,0 @@ function formatDependencies(rows) {

@@ -69,2 +69,3 @@ "use strict";

const telemetry_ledger_io_1 = require("./telemetry-ledger-io");
const collate_1 = require("../core/util/collate");
exports.METRICS_SCHEMA_VERSION = 1;

@@ -109,18 +110,18 @@ const VERIFIER_PASS_STATUSES = new Set(["verified", "completed", "committed"]);

const parts = [`id:${run.id}`, `createdAt:${run.createdAt}`, `updatedAt:${run.updatedAt}`, `app:${run.workflow.app?.id || run.workflow.id}`];
for (const task of [...run.tasks].sort((a, b) => a.id.localeCompare(b.id))) {
for (const task of [...run.tasks].sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id))) {
parts.push(`task:${task.id}:${task.status}:${task.dispatchedAt || "-"}:${task.completedAt || "-"}:${usageKey(task.usage)}:${task.backendId || "-"}`);
}
for (const worker of [...(run.workers || [])].sort((a, b) => a.id.localeCompare(b.id))) {
for (const worker of [...(run.workers || [])].sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id))) {
parts.push(`worker:${worker.id}:${worker.status}:${worker.output?.recordedAt || "-"}:${usageKey(worker.usage)}:${worker.backendId || "-"}`);
}
for (const node of [...(run.nodes || [])].filter((n) => n.kind === "verifier").sort((a, b) => a.id.localeCompare(b.id))) {
for (const node of [...(run.nodes || [])].filter((n) => n.kind === "verifier").sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id))) {
parts.push(`verifier:${node.id}:${node.status}`);
}
for (const cand of [...(run.candidates || [])].sort((a, b) => a.id.localeCompare(b.id))) {
for (const cand of [...(run.candidates || [])].sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id))) {
parts.push(`candidate:${cand.id}:${cand.status}`);
}
for (const fb of [...(run.feedback || [])].sort((a, b) => a.id.localeCompare(b.id))) {
for (const fb of [...(run.feedback || [])].sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id))) {
parts.push(`feedback:${fb.id}:${fb.status}`);
}
for (const m of [...(run.multiAgent?.memberships || [])].sort((a, b) => a.id.localeCompare(b.id))) {
for (const m of [...(run.multiAgent?.memberships || [])].sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id))) {
parts.push(`membership:${m.id}:${m.status}`);

@@ -153,3 +154,3 @@ }

}
return units.sort((a, b) => a.unit.localeCompare(b.unit));
return units.sort((a, b) => (0, collate_1.stableCompare)(a.unit, b.unit));
}

@@ -355,3 +356,3 @@ function tokenTotal(usage) {

.map((task) => ({ id: task.id, kind: "task", status: task.status, duration: duration(task.dispatchedAt, task.completedAt) }))
.sort((a, b) => a.id.localeCompare(b.id));
.sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id));
}

@@ -361,3 +362,3 @@ function workerRows(run) {

.map((worker) => ({ id: worker.id, kind: "worker", status: worker.status, duration: duration(worker.createdAt, workerEndAt(worker)) }))
.sort((a, b) => a.id.localeCompare(b.id));
.sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id));
}

@@ -549,3 +550,3 @@ function targetCreatedAt(run, target) {

}
perRun.sort((a, b) => a.report.runId.localeCompare(b.report.runId));
perRun.sort((a, b) => (0, collate_1.stableCompare)(a.report.runId, b.report.runId));
const groupBy = (keyOf) => {

@@ -561,3 +562,3 @@ const map = new Map();

return [...map.entries()]
.sort((a, b) => a[0].localeCompare(b[0]))
.sort((a, b) => (0, collate_1.stableCompare)(a[0], b[0]))
.map(([key, reports]) => ({

@@ -564,0 +565,0 @@ key,

@@ -297,6 +297,22 @@ "use strict";

const smokeFiles = normalized.filter((file) => /^plugins\/cool-workflow\/test\/.+-smoke\.js$/.test(file));
// WP1.1 (#360) restored a second, parallel test layer: pure `core/`
// logic proven by `test/*.test.js` under `npm run test:unit`, run and
// gated separately from the black-box `test/*-smoke.js` suite. A cycle
// that proves its fix with a unit test only (no smoke touched) is a
// real, complete cycle — the gate must accept either kind, not just
// the one that existed before the unit-test layer came back.
const unitTestFiles = normalized.filter((file) => /^plugins\/cool-workflow\/test\/.+\.test\.js$/.test(file));
// The black-box conformance suite (v2/conformance/cases/*.case.js) is a
// third, equally real proof layer — CI-gated on every push, and the one
// North Star Track C leans on. A cycle proven end to end by a new or
// changed conformance case (with no test/*-smoke.js or test/*.test.js
// touched) is a real, complete cycle too.
const conformanceCaseFiles = normalized.filter((file) => /^v2\/conformance\/cases\/.+\.case\.js$/.test(file));
const docFiles = normalized.filter(isDocFile);
const iterationFiles = normalized.filter((file) => file === "ITERATION_LOG.md");
const sourceAppOrScript = runtimeFiles.length > 0 || typeFiles.length > 0 || appFiles.length > 0 || scriptFiles.length > 0;
if ((runtimeFiles.length > 0 || appFiles.length > 0) && smokeFiles.length === 0) {
if ((runtimeFiles.length > 0 || appFiles.length > 0) &&
smokeFiles.length === 0 &&
unitTestFiles.length === 0 &&
conformanceCaseFiles.length === 0) {
issues.push({

@@ -303,0 +319,0 @@ code: "runtime-smoke-required",

@@ -27,4 +27,5 @@ "use strict";

const multi_agent_operator_ux_1 = require("./multi-agent-operator-ux");
const collate_1 = require("../core/util/collate");
function formatCounts(counts) {
const entries = Object.entries(counts).sort(([a], [b]) => a.localeCompare(b));
const entries = Object.entries(counts).sort(([a], [b]) => (0, collate_1.stableCompare)(a, b));
if (!entries.length)

@@ -31,0 +32,0 @@ return "none";

@@ -69,2 +69,3 @@ "use strict";

const trust_policy_io_1 = require("./trust-policy-io");
const collate_1 = require("../core/util/collate");
function countBy(values, key) {

@@ -147,4 +148,4 @@ const counts = {};

const counts = (0, candidate_scoring_io_1.summarizeCandidates)(run);
const candidates = [...(run.candidates || [])].sort((left, right) => left.id.localeCompare(right.id));
const selections = [...(run.candidateSelections || [])].sort((left, right) => left.id.localeCompare(right.id));
const candidates = [...(run.candidates || [])].sort((left, right) => (0, collate_1.stableCompare)(left.id, right.id));
const selections = [...(run.candidateSelections || [])].sort((left, right) => (0, collate_1.stableCompare)(left.id, right.id));
const commits = run.commits || [];

@@ -195,3 +196,3 @@ const selectedIds = new Set(selections.map((selection) => selection.candidateId));

function summarizeOperatorCommits(run) {
const commits = [...(run.commits || [])].sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id));
const commits = [...(run.commits || [])].sort((left, right) => (0, collate_1.stableCompare)(left.createdAt, right.createdAt) || (0, collate_1.stableCompare)(left.id, right.id));
return {

@@ -206,3 +207,3 @@ total: commits.length,

function summarizeOperatorWorkers(run) {
const workers = (run.workers || []).slice().sort((a, b) => a.id.localeCompare(b.id));
const workers = (run.workers || []).slice().sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id));
return {

@@ -432,5 +433,5 @@ total: workers.length,

addEdge(edge.from, edge.to, edge.label);
const sortedNodes = [...nodes.values()].sort((a, b) => a.kind.localeCompare(b.kind) || a.id.localeCompare(b.id));
const sortedEdges = edges.slice().sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to) || (a.label || "").localeCompare(b.label || ""));
const sortedNodes = [...nodes.values()].sort((a, b) => (0, collate_1.stableCompare)(a.kind, b.kind) || (0, collate_1.stableCompare)(a.id, b.id));
const sortedEdges = edges.slice().sort((a, b) => (0, collate_1.stableCompare)(a.from, b.from) || (0, collate_1.stableCompare)(a.to, b.to) || (0, collate_1.stableCompare)(a.label || "", b.label || ""));
return { runId: run.id, nodes: sortedNodes, edges: sortedEdges };
}

@@ -555,23 +555,26 @@ "use strict";

const runId = String(args.runId);
const run = (0, run_store_1.loadRunFromCwd)(runId, invocationCwd(args));
// parseArgv keys long flags in kebab-case; accept camelCase as a fallback.
const flag = (kebab, camel) => {
const v = args[kebab] ?? args[camel];
return typeof v === "string" && v.trim() ? v : undefined;
};
const manifest = (0, dispatch_1.createDispatchManifest)(run, args.limit !== undefined ? Number(args.limit) : undefined, {
sandboxProfileId: typeof args.sandbox === "string" ? args.sandbox : undefined,
sandbox: typeof args.sandbox === "string" ? args.sandbox : undefined,
backendId: typeof args.backend === "string" ? args.backend : undefined,
multiAgentRunId: flag("multi-agent-run", "multiAgentRun"),
multiAgentGroupId: flag("multi-agent-group", "multiAgentGroup"),
multiAgentRoleId: flag("multi-agent-role", "multiAgentRole"),
multiAgentFanoutId: flag("multi-agent-fanout", "multiAgentFanout"),
// The whole load -> change -> save cycle holds the state.json lock so a
// concurrent dispatch/result on the same run cannot drop this update.
return (0, run_store_1.withRunStateLock)(runId, invocationCwd(args), (run) => {
// parseArgv keys long flags in kebab-case; accept camelCase as a fallback.
const flag = (kebab, camel) => {
const v = args[kebab] ?? args[camel];
return typeof v === "string" && v.trim() ? v : undefined;
};
const manifest = (0, dispatch_1.createDispatchManifest)(run, args.limit !== undefined ? Number(args.limit) : undefined, {
sandboxProfileId: typeof args.sandbox === "string" ? args.sandbox : undefined,
sandbox: typeof args.sandbox === "string" ? args.sandbox : undefined,
backendId: typeof args.backend === "string" ? args.backend : undefined,
multiAgentRunId: flag("multi-agent-run", "multiAgentRun"),
multiAgentGroupId: flag("multi-agent-group", "multiAgentGroup"),
multiAgentRoleId: flag("multi-agent-role", "multiAgentRole"),
multiAgentFanoutId: flag("multi-agent-fanout", "multiAgentFanout"),
});
if (manifest.dispatchId) {
(0, commit_1.commitState)(run, `dispatch:${manifest.dispatchId}`);
(0, run_store_1.saveCheckpoint)(run);
(0, report_1.writeReport)(run);
}
return manifest;
});
if (manifest.dispatchId) {
(0, commit_1.commitState)(run, `dispatch:${manifest.dispatchId}`);
(0, run_store_1.saveCheckpoint)(run);
(0, report_1.writeReport)(run);
}
return manifest;
}

@@ -582,54 +585,61 @@ function recordResultRun(args) {

const resultPath = String(args.resultPath);
const run = (0, run_store_1.loadRunFromCwd)(runId, invocationCwd(args));
const task = run.tasks.find((t) => t.id === taskId);
if (!task || !task.workerId)
throw new Error(`Unknown task id for run ${runId}: ${taskId}`);
const absolute = path.resolve(resultPath);
// A result path inside a system directory is never accepted (POLA): the
// operator file gets copied into the worker's result.md, so a /etc/passwd
// source would smuggle system content into a run. Byte-behavior port of the
// old build's recordResult system-directory blacklist.
if (/^\/(etc|bin|sbin|usr|Library|System|Applications|boot|dev|proc|sys|root|var\/log|var\/run)\//.test(absolute)) {
throw new Error(`Result path must not be a system directory: ${resultPath}`);
}
if (!fs.existsSync(absolute))
throw new Error(`Result file does not exist: ${resultPath}`);
const workerId = String(task.workerId);
// Host-attested `cw result <run> <task> <file>` intake: the operator hands CW
// an EXTERNAL result file that lives OUTSIDE the worker's read-only write
// boundary. The old task-level recordResult (lifecycle-operations.ts:279-280)
// COPIED that external file into the run's results area and recorded the
// internal path — it never ran the external path through validateSandboxWrite.
// v2 collapsed the two intakes into recordWorkerOutput, which sandbox-validates
// its input against the worker boundary, so a bare external path is rejected
// ("write path is outside sandbox profile <id>"). Restore the copy-in: stage
// the operator file at the worker's OWN result.md (which IS inside the write
// boundary), then record that internal path exactly like a driven worker.
const manifest = (0, worker_isolation_1.showWorkerManifest)(run, workerId);
fs.mkdirSync(path.dirname(manifest.resultPath), { recursive: true });
fs.copyFileSync(absolute, manifest.resultPath);
const output = (0, worker_isolation_1.recordWorkerOutput)(run, workerId, manifest.resultPath);
// Host-attested token usage (v0.1.31): record it verbatim as provenance when
// the operator supplied `--usage-*` flags; CW never synthesizes usage. The old
// task-level recordResult set `task.usage = usage` (lifecycle-operations.ts:286)
// and its unit was the TASK. v2 records through recordWorkerOutput, which gives
// the worker an `output` record — so the observability usage UNIT becomes the
// WORKER (deriveUsageTotals reads worker.usage for workers with output, and
// EXCLUDES that task). Attach the usage to the worker scope so the report counts
// it as an attested unit; also stamp task.usage for byte-parity with the old
// task-level record.
const usage = (0, observability_1.parseUsageFromArgs)(args, new Date().toISOString());
if (usage) {
task.usage = usage;
const scope = (0, worker_isolation_1.getWorkerScope)(run, workerId);
if (scope)
scope.usage = usage;
}
// Byte-exact to the old build's orchestrator recordWorkerOutput()
// wrapper: an accepted result is its own checkpoint commit, not just a
// bare saveCheckpoint (SPEC/pipeline-run.md's persist-ordering rule).
(0, commit_1.commitState)(run, `worker:${workerId}:result`);
(0, run_store_1.saveCheckpoint)(run);
(0, report_1.writeReport)(run);
return output;
// Two processes recording results for two tasks of the SAME run used to
// race: both loaded, and the later saveCheckpoint dropped the earlier
// task's completion. The lock now covers the whole cycle.
return (0, run_store_1.withRunStateLock)(runId, invocationCwd(args), (run) => {
const task = run.tasks.find((t) => t.id === taskId);
if (!task || !task.workerId)
throw new Error(`Unknown task id for run ${runId}: ${taskId}`);
const absolute = path.resolve(resultPath);
// A result path inside a system directory is never accepted (POLA): the
// operator file gets copied into the worker's result.md, so a /etc/passwd
// source would smuggle system content into a run. Byte-behavior port of the
// old build's recordResult system-directory blacklist.
if (/^\/(etc|bin|sbin|usr|Library|System|Applications|boot|dev|proc|sys|root|var\/log|var\/run)\//.test(absolute)) {
throw new Error(`Result path must not be a system directory: ${resultPath}`);
}
if (!fs.existsSync(absolute))
throw new Error(`Result file does not exist: ${resultPath}`);
const workerId = String(task.workerId);
// Host-attested `cw result <run> <task> <file>` intake: the operator hands CW
// an EXTERNAL result file that lives OUTSIDE the worker's read-only write
// boundary. The old task-level recordResult (lifecycle-operations.ts:279-280)
// COPIED that external file into the run's results area and recorded the
// internal path — it never ran the external path through validateSandboxWrite.
// v2 collapsed the two intakes into recordWorkerOutput, which sandbox-validates
// its input against the worker boundary, so a bare external path is rejected
// ("write path is outside sandbox profile <id>"). Restore the copy-in: stage
// the operator file at the worker's OWN result.md (which IS inside the write
// boundary), then record that internal path exactly like a driven worker.
const manifest = (0, worker_isolation_1.showWorkerManifest)(run, workerId);
fs.mkdirSync(path.dirname(manifest.resultPath), { recursive: true });
fs.copyFileSync(absolute, manifest.resultPath);
const output = (0, worker_isolation_1.recordWorkerOutput)(run, workerId, manifest.resultPath, {
requireAttestedTelemetry: (0, agent_config_1.resolveAgentConfig)(args).requireAttestedTelemetry,
allowUnattested: Boolean(args.allowUnattested ?? args["allow-unattested"]),
});
// Host-attested token usage (v0.1.31): record it verbatim as provenance when
// the operator supplied `--usage-*` flags; CW never synthesizes usage. The old
// task-level recordResult set `task.usage = usage` (lifecycle-operations.ts:286)
// and its unit was the TASK. v2 records through recordWorkerOutput, which gives
// the worker an `output` record — so the observability usage UNIT becomes the
// WORKER (deriveUsageTotals reads worker.usage for workers with output, and
// EXCLUDES that task). Attach the usage to the worker scope so the report counts
// it as an attested unit; also stamp task.usage for byte-parity with the old
// task-level record.
const usage = (0, observability_1.parseUsageFromArgs)(args, new Date().toISOString());
if (usage) {
task.usage = usage;
const scope = (0, worker_isolation_1.getWorkerScope)(run, workerId);
if (scope)
scope.usage = usage;
}
// Byte-exact to the old build's orchestrator recordWorkerOutput()
// wrapper: an accepted result is its own checkpoint commit, not just a
// bare saveCheckpoint (SPEC/pipeline-run.md's persist-ordering rule).
(0, commit_1.commitState)(run, `worker:${workerId}:result`);
(0, run_store_1.saveCheckpoint)(run);
(0, report_1.writeReport)(run);
return output;
});
}

@@ -636,0 +646,0 @@ /** `cw commit <run-id>` — byte-exact port of the old build's

@@ -109,2 +109,3 @@ "use strict";

const hash_1 = require("../core/hash");
const collate_1 = require("../core/util/collate");
const run_registry_io_1 = require("./run-registry-io");

@@ -1291,3 +1292,3 @@ // ---------------------------------------------------------------------------

}
entries.sort((a, b) => (a.fetchedAt || "").localeCompare(b.fetchedAt || ""));
entries.sort((a, b) => (0, collate_1.stableCompare)(a.fetchedAt || "", b.fetchedAt || ""));
return entries;

@@ -1294,0 +1295,0 @@ }

@@ -62,2 +62,3 @@ "use strict";

const multi_agent_operator_ux_1 = require("./multi-agent-operator-ux");
const collate_1 = require("../core/util/collate");
function formatInputList(value) {

@@ -69,3 +70,3 @@ if (Array.isArray(value))

function formatCounts(counts) {
const entries = Object.entries(counts).sort(([a], [b]) => a.localeCompare(b));
const entries = Object.entries(counts).sort(([a], [b]) => (0, collate_1.stableCompare)(a, b));
if (!entries.length)

@@ -72,0 +73,0 @@ return "none";

@@ -53,2 +53,3 @@ "use strict";

exports.loadRunFromCwd = loadRunFromCwd;
exports.withRunStateLock = withRunStateLock;
exports.saveCheckpoint = saveCheckpoint;

@@ -116,2 +117,18 @@ exports.compactCheckpoint = compactCheckpoint;

}
/** Hold the state.json lock over a WHOLE load -> change -> save cycle.
* A bare loadRunFromCwd + saveCheckpoint pair leaves a window where two
* processes both load the same state and the later save silently drops
* the earlier change (the same lost-update class PR #339 fixed for
* queue.json / triggers.json). `fn` gets the run loaded UNDER the lock;
* saveCheckpoint calls inside `fn` re-enter the same lock (withFileLock
* is re-entrant in-process) and write exactly as before. The probe load
* runs BEFORE the lock so an unknown run id throws the exact
* loadRunFromCwd error without first creating the run directory as a
* lock-file side effect — and it supplies `paths.state`, the same lock
* target saveCheckpoint uses. Keep `fn` short: a critical section past
* 30s can be stolen as a stale lock. */
function withRunStateLock(runId, cwd, fn) {
const probe = loadRunFromCwd(runId, cwd);
return (0, fs_atomic_1.withFileLock)(probe.paths.state, () => fn(loadRunFromCwd(runId, cwd)));
}
/** state.json is the single source of truth — set `updatedAt`, then write

@@ -118,0 +135,0 @@ * it DURABLY with a lock so concurrent processes never lose an update. */

@@ -69,2 +69,3 @@ "use strict";

const multi_agent_operator_ux_1 = require("./multi-agent-operator-ux");
const collate_1 = require("../core/util/collate");
function summariesDir(run) {

@@ -138,3 +139,3 @@ return path.join(run.paths.runDir, "summaries");

nextAction: `node scripts/cw.js summary show ${run.id}`,
entries: entries.sort((a, b) => a.id.localeCompare(b.id)),
entries: entries.sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id)),
views,

@@ -141,0 +142,0 @@ paths: { summariesDir: dir, indexPath: path.join(dir, "index.json"), reportPath },

@@ -66,2 +66,3 @@ "use strict";

const coordinator_io_1 = require("./coordinator-io");
const collate_1 = require("../core/util/collate");
function topologyRoot(run) {

@@ -311,3 +312,3 @@ return run.paths.topologiesDir || path.join(run.paths.runDir, "topologies");

function formatTopologyCounts(counts) {
const entries = Object.entries(counts).sort(([a], [b]) => a.localeCompare(b));
const entries = Object.entries(counts).sort(([a], [b]) => (0, collate_1.stableCompare)(a, b));
if (!entries.length)

@@ -314,0 +315,0 @@ return "none";

@@ -61,2 +61,3 @@ "use strict";

exports.listTrustAuditEvents = listTrustAuditEvents;
exports.trustAuditHead = trustAuditHead;
exports.verifyTrustAudit = verifyTrustAudit;

@@ -140,2 +141,17 @@ exports.recordTrustAuditEvent = recordTrustAuditEvent;

}
/** The current head of a run's trust-audit chain: the hash the NEXT
* appended event will link from (genesis when the log is empty), plus
* the event count. Read-only projection over existing data. Capture it
* (e.g. right after a run, or at export time) and later hand it to
* `verifyTrustAudit`'s anchor / `cw audit verify --expect-head` to
* re-prove the log was not shortened since the capture. */
function trustAuditHead(run) {
const audit = ensureTrustAudit(run);
const events = readEventsRaw(audit.eventLogPath);
let head = trustAuditGenesis(run.id);
for (const event of events) {
head = event.eventHash !== undefined ? event.eventHash : computeEventHash(event);
}
return { eventCount: events.length, headHash: head };
}
/** Re-prove the run's trust-audit chain: prevEventHash linkage (append

@@ -152,4 +168,10 @@ * order) + per-event hash recompute. A corrupt line, an edited event,

* the hash to be waved through as "legacy" — so it fails with
* `trust-audit-unchained-event`, never silently accepted. */
function verifyTrustAudit(run) {
* `trust-audit-unchained-event`, never silently accepted.
*
* ANCHOR (optional): the walk alone cannot see tail truncation — see
* TrustAuditAnchor. With an anchor, the head-hash trail (genesis plus
* the hash after each event) must contain `expectHead`, and the log
* must reach `expectCount` events; a shortfall fails closed with
* `trust-audit-truncated`. Without an anchor, behavior is unchanged. */
function verifyTrustAudit(run, anchor) {
const audit = ensureTrustAudit(run);

@@ -164,2 +186,3 @@ const { events, corruptLines } = readEventsRawCounted(audit.eventLogPath);

let expectedPrev = trustAuditGenesis(run.id);
const headTrail = new Set([expectedPrev]);
for (let i = 0; i < events.length; i++) {

@@ -171,2 +194,3 @@ const event = events[i];

expectedPrev = recomputed; // advance the chain over legacy events
headTrail.add(expectedPrev);
continue;

@@ -184,2 +208,3 @@ }

expectedPrev = event.eventHash;
headTrail.add(expectedPrev);
}

@@ -191,2 +216,16 @@ // Era rule: a log with ANY chained event must have EVERY event chained.

}
// Anchor rule: the captured head must still be ON the chain, and the log
// must be at least as long as it was at capture time. A truncated-then-
// appended log fails the head check (new events link from an earlier
// point, so the old head is no longer in the trail).
if (anchor) {
if (anchor.expectCount !== undefined && events.length < anchor.expectCount) {
verified = false;
checks.push({ name: "anchor-count", pass: false, code: "trust-audit-truncated" });
}
if (anchor.expectHead !== undefined && !headTrail.has(anchor.expectHead)) {
verified = false;
checks.push({ name: "anchor-head", pass: false, code: "trust-audit-truncated" });
}
}
return { present: events.length > 0, verified, eventCount: events.length, chained, unchained, corruptLines, checks };

@@ -193,0 +232,0 @@ }

@@ -54,2 +54,3 @@ "use strict";

const operator_ux_1 = require("./operator-ux");
const agent_config_1 = require("./agent-config");
function cwdFor(args) {

@@ -64,2 +65,6 @@ return typeof args.cwd === "string" && args.cwd.trim() ? path.resolve(args.cwd) : process.cwd();

}
/** `--allow-unattested` (CLI: dashed key; MCP: allowUnattested). */
function allowUnattestedOption(args) {
return Boolean(args.allowUnattested ?? args["allow-unattested"]);
}
function workerListCli(args) {

@@ -97,3 +102,6 @@ const run = (0, run_store_1.loadRunFromCwd)(req(args.runId, "run id"), cwdFor(args));

const run = (0, run_store_1.loadRunFromCwd)(req(args.runId, "run id"), cwdFor(args));
(0, worker_isolation_1.recordWorkerOutput)(run, req(args.workerId, "worker id"), req(args.resultPath, "result file"), {});
(0, worker_isolation_1.recordWorkerOutput)(run, req(args.workerId, "worker id"), req(args.resultPath, "result file"), {
requireAttestedTelemetry: (0, agent_config_1.resolveAgentConfig)(args).requireAttestedTelemetry,
allowUnattested: allowUnattestedOption(args),
});
run.loopStage = "observe";

@@ -100,0 +108,0 @@ (0, dispatch_1.updatePhaseStatuses)(run);

@@ -109,2 +109,3 @@ "use strict";

const hash_1 = require("../core/hash");
const collate_1 = require("../core/util/collate");
const telemetry_attestation_1 = require("../core/trust/telemetry-attestation");

@@ -535,9 +536,33 @@ const telemetry_ledger_io_1 = require("./telemetry-ledger-io");

// Opt-in fail-closed gate (default off): when the operator requires
// attested telemetry, a delegated hop whose verdict is not `attested`
// is REJECTED here — BEFORE any accept-side state mutation — so the
// drive parks it instead of recording unverifiable usage.
if (options.requireAttestedTelemetry && telemetry && telemetry.status !== "attested") {
const message = `Worker ${workerId} telemetry is ${telemetry.status} (${telemetry.reason || "unverified"}) and require-attested-telemetry is enabled — refusing to accept a hop whose usage cannot be cryptographically verified`;
recordWorkerFailure(run, workerId, message, { code: "telemetry-unattested-blocked", path: absoluteResultPath, retryable: false });
throw new Error(message);
// attested telemetry, an accept whose usage cannot be verified is
// REJECTED here — BEFORE any accept-side state mutation — so the drive
// parks it instead of recording unverifiable usage. This fires on BOTH
// shapes: a delegation present but not attested (telemetry.status !==
// "attested"), and NO delegation metadata at all. The second shape is
// the gap a manual `cw worker output` / `cw result` accept used to slip
// through silently: options.agentDelegation was simply absent, so
// `telemetry` was undefined and the old `telemetry &&` condition
// short-circuited false — an unattested result could be laundered
// through the manual accept path even with the require flag on.
// --allow-unattested is the operator's explicit way past this: it never
// skips the gate silently, it records a telemetry.gate-override event.
if (options.requireAttestedTelemetry && (!telemetry || telemetry.status !== "attested")) {
if (options.allowUnattested) {
(0, trust_audit_1.recordTrustAuditEvent)(run, {
kind: "telemetry.gate-override",
decision: "allowed",
source: "operator",
workerId,
taskId: task.id,
metadata: { reason: "--allow-unattested", telemetryStatus: telemetry ? telemetry.status : "absent" },
});
}
else {
const code = telemetry ? "telemetry-unattested-blocked" : "telemetry-missing-blocked";
const message = telemetry
? `Worker ${workerId} telemetry is ${telemetry.status} (${telemetry.reason || "unverified"}) and require-attested-telemetry is enabled — refusing to accept a hop whose usage cannot be cryptographically verified`
: `Worker ${workerId} carries no agent-delegation telemetry at all and require-attested-telemetry is enabled — refusing to accept an unattested manual result (pass --allow-unattested to record an audited override)`;
recordWorkerFailure(run, workerId, message, { code, path: absoluteResultPath, retryable: false });
throw new Error(message);
}
}

@@ -802,3 +827,3 @@ const agentDelegationMeta = delegation

run.workers = merged;
const workers = merged.slice().sort((a, b) => a.id.localeCompare(b.id));
const workers = merged.slice().sort((a, b) => (0, collate_1.stableCompare)(a.id, b.id));
return options.status ? workers.filter((w) => w.status === options.status) : workers;

@@ -830,3 +855,3 @@ }

function formatCountBucket(counts) {
const entries = Object.entries(counts).sort(([a], [b]) => a.localeCompare(b));
const entries = Object.entries(counts).sort(([a], [b]) => (0, collate_1.stableCompare)(a, b));
if (!entries.length)

@@ -833,0 +858,0 @@ return "none";

@@ -78,2 +78,3 @@ "use strict";

const version_1 = require("../core/version");
const collate_1 = require("../core/util/collate");
class WorkflowAppNotFoundError extends Error {

@@ -425,6 +426,6 @@ constructor(appId) {

].sort((left, right) => {
const byId = left.app.id.localeCompare(right.app.id);
const byId = (0, collate_1.stableCompare)(left.app.id, right.app.id);
if (byId)
return byId;
return sourcePathOf(left).localeCompare(sourcePathOf(right));
return (0, collate_1.stableCompare)(sourcePathOf(left), sourcePathOf(right));
});

@@ -431,0 +432,0 @@ const seen = new Map();

@@ -420,1 +420,3 @@ # Agent Delegation Drive

0.2.1
0.2.2

@@ -85,3 +85,3 @@ # CLI ↔ MCP Parity

<!-- gen:parity:count -->
machine-complete by design: 237 capabilities, 196 MCP tools.
machine-complete by design: 239 capabilities, 197 MCP tools.
<!-- /gen:parity:count -->

@@ -288,3 +288,5 @@

| `history` | `cw history` | `cw_history` | `history` | both | identical |
| `audit.head` | `cw audit head` | `cw_audit_head` | `audit.head` | both | identical |
| `version` | `cw version` | `—` | `version` | cli-only | cli-only |
| `search` | `cw search` | `—` | `search` | cli-only | cli-only |
| `doctor` | `cw doctor` | `—` | `doctor` | cli-only | cli-only |

@@ -344,5 +346,6 @@ | `fix` | `cw fix` | `—` | `fix` | cli-only | cli-only |

<!-- gen:parity:cliOnly -->
41 capabilities are CLI-only:
42 capabilities are CLI-only:
- `version` — version is a local, no-run-state print; the old build never gave it an MCP peer.
- `search` — CLI-only discovery helper over the same real app data cw list shows; no MCP client needs a free-text search tool alongside cw_list's structured output.
- `doctor` — 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`.

@@ -634,1 +637,3 @@ - `fix` — Environment fix commands are local diagnostics, same reasoning as doctor.

0.2.1
0.2.2

@@ -180,1 +180,3 @@ # Contract Migration Tooling

0.2.1
0.2.2

@@ -164,1 +164,3 @@ # Control-Plane Scheduling

0.2.1
0.2.2

@@ -48,2 +48,22 @@ # Durable State & Locking

### The whole-cycle run-state lock (unreleased)
`saveCheckpoint` locks only the WRITE of `state.json`. A verb that does
load → change → save on its own left a window where two processes both
load the same state and the later save silently drops the earlier change
— the same lost-update class the v0.2.1 fix closed for `queue.json`
and `triggers.json`. Two additions close it for the run state itself:
- **`withRunStateLock(runId, cwd, fn)`** (run-store) holds the
`state.json` lock over the whole load → change → save cycle. `fn` gets
the run loaded UNDER the lock. `cw dispatch` and `cw result` go
through it, so two `cw result` calls for two tasks of the same run can
no longer lose a completion. Keep `fn` short: a critical section past
the 30 s steal window can be stolen.
- **`withFileLock` is now re-entrant inside one process**: a nested call
on the same target runs its `fn` under the already-held lock instead
of waiting on its own lockfile until the tries run out. Save paths
inside `fn` (`saveCheckpoint`, worker-failure persists) keep their own
lock calls unchanged. Cross-process behavior is untouched.
## Reclamation durability (the write-ahead seam, v0.1.40)

@@ -164,1 +184,3 @@

0.2.1
0.2.2

@@ -324,1 +324,3 @@ # Evidence Adoption Reasoning Chain

0.2.1
0.2.2

@@ -354,1 +354,3 @@ # EXECUTION-BACKENDS(7)

0.2.1
0.2.2

@@ -52,2 +52,3 @@ # Cool Workflow Docs

- [Security / Trust Hardening](security-trust-hardening.7.md) - audit records, provenance, sandbox attestations, and acceptance rationale.
- [Trust Audit Anchor](trust-audit-anchor.7.md) - head anchor that makes a cut-off audit-log tail visible.
- [State Explosion Management](state-explosion-management.7.md) - summaries, compact graph views, blackboard digests, and stale-aware compaction.

@@ -54,0 +55,0 @@ - [Evidence Adoption Reasoning Chain](evidence-adoption-reasoning-chain.7.md) - why evidence was adopted or rejected.

@@ -330,1 +330,3 @@ # Multi-Agent CLI + MCP Surface

0.2.1
0.2.2

@@ -356,1 +356,3 @@ # Multi-Agent Eval & Replay Harness

0.2.1
0.2.2

@@ -368,1 +368,3 @@ # Multi-Agent Operator UX

0.2.1
0.2.2

@@ -189,1 +189,3 @@ # Node Snapshot / Diff / Replay

0.2.1
0.2.2

@@ -249,1 +249,3 @@ # Observability + Cost Accounting

0.2.1
0.2.2
# Cool Workflow Project Index
Generated from the current repository code on 2026-07-07 by `npm run sync:project-index`.
Generated from the current repository code on 2026-07-08 by `npm run sync:project-index`.

@@ -8,7 +8,7 @@ ## Snapshot

- Package: `cool-workflow`
- Version: `0.2.1`
- Source modules: `129`
- Version: `0.2.2`
- Source modules: `145`
- Workflow apps: `8`
- Docs: `61`
- Smoke tests: `178`
- Docs: `62`
- Smoke tests: `181`
- Repository: https://github.com/coo1white/cool-workflow

@@ -86,2 +86,3 @@

- [cli/parseargv.ts](../src/cli/parseargv.ts)
- [core/capability-data.ts](../src/core/capability-data.ts)
- [core/capability-table.ts](../src/core/capability-table.ts)

@@ -118,2 +119,5 @@ - [core/format/help.ts](../src/core/format/help.ts)

- [core/types/boundary.ts](../src/core/types/boundary.ts)
- [core/types/execution-backend.ts](../src/core/types/execution-backend.ts)
- [core/types/observability.ts](../src/core/types/observability.ts)
- [core/util/collate.ts](../src/core/util/collate.ts)
- [mcp/dispatch.ts](../src/mcp/dispatch.ts)

@@ -187,2 +191,14 @@ - [mcp/server.ts](../src/mcp/server.ts)

- [shell/worker-cli.ts](../src/shell/worker-cli.ts)
- [wiring/capability-table/basics.ts](../src/wiring/capability-table/basics.ts)
- [wiring/capability-table/exec-backend.ts](../src/wiring/capability-table/exec-backend.ts)
- [wiring/capability-table/index.ts](../src/wiring/capability-table/index.ts)
- [wiring/capability-table/multi-agent.ts](../src/wiring/capability-table/multi-agent.ts)
- [wiring/capability-table/parity.ts](../src/wiring/capability-table/parity.ts)
- [wiring/capability-table/pipeline.ts](../src/wiring/capability-table/pipeline.ts)
- [wiring/capability-table/registry-core.ts](../src/wiring/capability-table/registry-core.ts)
- [wiring/capability-table/reporting.ts](../src/wiring/capability-table/reporting.ts)
- [wiring/capability-table/scheduling-registry.ts](../src/wiring/capability-table/scheduling-registry.ts)
- [wiring/capability-table/state.ts](../src/wiring/capability-table/state.ts)
- [wiring/capability-table/trust-ledger.ts](../src/wiring/capability-table/trust-ledger.ts)
- [wiring/capability-table/workflow-apps.ts](../src/wiring/capability-table/workflow-apps.ts)

@@ -258,2 +274,3 @@ ## Workflow Apps

- [Team Collaboration](team-collaboration.7.md)
- [Trust Audit Anchor](trust-audit-anchor.7.md)
- [Trust Model & Limitations](trust-model.md)

@@ -321,2 +338,3 @@ - [Unix-Inspired Workflow Principles](unix-principles.md)

- [det-ids-b-smoke.js](../test/det-ids-b-smoke.js)
- [dispatch-legacy-burndown-smoke.js](../test/dispatch-legacy-burndown-smoke.js)
- [doctor-smoke.js](../test/doctor-smoke.js)

@@ -418,2 +436,3 @@ - [dogfood-architecture-review-smoke.js](../test/dogfood-architecture-review-smoke.js)

- [run-retention-reclamation-smoke.js](../test/run-retention-reclamation-smoke.js)
- [run-state-lock-concurrency-smoke.js](../test/run-state-lock-concurrency-smoke.js)
- [sample-determinism-smoke.js](../test/sample-determinism-smoke.js)

@@ -442,2 +461,3 @@ - [sandbox-env-batch-hardening-smoke.js](../test/sandbox-env-batch-hardening-smoke.js)

- [token-budget-enforcement-smoke.js](../test/token-budget-enforcement-smoke.js)
- [trust-audit-anchor-smoke.js](../test/trust-audit-anchor-smoke.js)
- [vendor-manifest-load-smoke.js](../test/vendor-manifest-load-smoke.js)

@@ -444,0 +464,0 @@ - [vendor-preflight-smoke.js](../test/vendor-preflight-smoke.js)

@@ -196,1 +196,3 @@ # Real Execution Backend Integrations

0.2.1
0.2.2

@@ -336,1 +336,3 @@ # Release And Migration Discipline

0.2.1
0.2.2

@@ -299,1 +299,3 @@ # Release Tooling

0.2.1
0.2.2

@@ -479,1 +479,3 @@ # Run Registry / Control Plane

0.2.1
0.2.2

@@ -268,1 +268,3 @@ # Run Retention & Provable Reclamation

0.2.1
0.2.2

@@ -54,2 +54,30 @@ # Security / Trust Hardening

## Requiring Attested Telemetry
`CW_REQUIRE_ATTESTED_TELEMETRY=1` (or `--require-attested-telemetry`, or a
durable `agent-config.json` field) is an opt-in, off-by-default gate: once
on, CW refuses to accept ANY worker result whose usage telemetry is not
cryptographically `attested` — a delegated hop whose signature does not
verify, and now also a MANUAL accept (`cw worker output`, `cw result`) that
carries no delegation telemetry at all. The manual shape used to slip past
the gate silently; it is now blocked the same way, with its own code
(`telemetry-missing-blocked`, distinct from a present-but-unverified hop's
`telemetry-unattested-blocked`).
An operator who genuinely needs to accept an unattested manual result under
the require flag passes `--allow-unattested` (MCP: `allowUnattested`). This
is never silent: it writes a `telemetry.gate-override` trust-audit event
(`decision: "allowed"`, `source: "operator"`) into the same hash-chained log
every other audit decision goes through, so the override itself is part of
the auditable record, not a hole in it.
A result-cache hit (the drive loop replaying a previously accepted result) is
never re-blocked by this gate — the underlying result was already gated (or
overridden) at its first acceptance. When the require flag is on, a cache
accept still records a `telemetry.cache-accept` event, so the audit trail
shows which accepts came from a fresh hop and which from the cache.
With the require flag off (the default), none of this changes: a plain
`cw worker output` / `cw result` behaves exactly as before.
## CLI

@@ -71,2 +99,4 @@

node scripts/cw.js audit decision <run-id> <worker-id> --env SECRET_NAME
node scripts/cw.js worker output <run-id> <worker-id> <result-file> [--allow-unattested]
node scripts/cw.js result <run-id> <task-id> <result-file> [--allow-unattested]
```

@@ -73,0 +103,0 @@

@@ -325,1 +325,3 @@ # State Explosion Management

0.2.1
0.2.2

@@ -261,1 +261,3 @@ # Team Collaboration

0.2.1
0.2.2

@@ -279,1 +279,3 @@ # Web / Desktop Workbench

0.2.1
0.2.2

@@ -5,3 +5,3 @@ {

"name": "cool-workflow",
"version": "0.2.1",
"version": "0.2.2",
"license": "BSD-2-Clause",

@@ -8,0 +8,0 @@ "homepage": "https://github.com/coo1white/cool-workflow",

{
"name": "cool-workflow",
"version": "0.2.1",
"version": "0.2.2",
"bin": {

@@ -46,2 +46,3 @@ "cool-workflow": "scripts/cw.js",

"dist:check": "node scripts/dist-drift-check.js",
"purity:check": "node scripts/purity-gate.js",
"golden-path": "node scripts/golden-path.js",

@@ -72,2 +73,3 @@ "dogfood:release": "node scripts/dogfood-release.js",

"test:coverage": "node dist/cli.js version > /dev/null && node scripts/coverage-gate.js --concurrency auto",
"test:unit": "node dist/cli.js version > /dev/null && node test/run-unit.js",
"eval:replay": "tsc -p tsconfig.json && node test/multi-agent-eval-replay-harness-smoke.js",

@@ -74,0 +76,0 @@ "ci": "npm run build && npm run check && npm run test && npm run release:check",

@@ -49,2 +49,19 @@ #!/usr/bin/env node

function replaceLockfileVersions(absPath, next) {
// Move BOTH lockfile version fields with targeted string swaps, so the byte
// formatting npm wrote is kept as it is: the top-level "version" (the first
// one in the file) and the root-package entry `"": { "name": ..., "version": ... }`.
// Dependency entries are not touched — the second swap is keyed on the
// `"": {` root-package opening, which is present only once.
const text = fs.readFileSync(absPath, "utf8");
let updated = text.replace(/"version":\s*"[^"]*"/, `"version": "${next}"`);
updated = updated.replace(
/("":\s*\{\s*"name":\s*"[^"]*",\s*"version":\s*)"[^"]*"/,
`$1"${next}"`
);
if (updated === text) return false;
fs.writeFileSync(absPath, updated);
return true;
}
function setNestedVersion(absPath, next) {

@@ -79,5 +96,9 @@ // For files where the first `"version"` is NOT the right one, parse + set.

// 2. package-lock.json (gitignored install artifact; only if present)
// 2. package-lock.json (tracked; only if present). The lockfile keeps the
// version in TWO places: the top-level "version" and the root-package
// entry packages[""].version. The old code moved only the first one, so
// the root-package entry kept an old version till the next `npm install`
// (the v0.1.97 drift seen after the v0.2.0/v0.2.1 cuts). Move both here.
const lock = path.join(pluginRoot, "package-lock.json");
if (fs.existsSync(lock) && replaceFirstVersionField(lock, next)) note("package-lock.json");
if (fs.existsSync(lock) && replaceLockfileVersions(lock, next)) note("package-lock.json");

@@ -223,2 +244,3 @@ // 2b. Official MCP Registry server metadata (top-level server version + npm package version).

{ path: "plugins/cool-workflow/docs/release-and-migration.7.md", needle: next, desc: "release & migration doc" },
{ path: "plugins/cool-workflow/docs/trust-audit-anchor.7.md", needle: next, desc: "trust audit anchor doc" },
];

@@ -225,0 +247,0 @@ }

@@ -86,3 +86,3 @@ #!/usr/bin/env node

"--scope",
"Cool Workflow v0.2.1",
"Cool Workflow v0.2.2",
"--freshness",

@@ -121,3 +121,3 @@ "as of release preparation"

assert.equal(summary.legacy, false);
assert.equal(summary.version, "0.2.1");
assert.equal(summary.version, "0.2.2");

@@ -129,3 +129,3 @@ const validation = runJson(["app", "validate", manifestPath]);

assert.equal(shown.app.id, app.id);
assert.equal(shown.app.version, "0.2.1");
assert.equal(shown.app.version, "0.2.2");
assert.ok(shown.app.metadata.canonical, `${app.id} must be marked canonical`);

@@ -141,3 +141,3 @@ assert.ok(shown.app.sandboxProfiles.length > 0, `${app.id} must declare sandbox profiles`);

assert.equal(state.workflow.app.id, app.id);
assert.equal(state.workflow.app.version, "0.2.1");
assert.equal(state.workflow.app.version, "0.2.2");
assert.equal(state.workflow.app.metadata.canonical, true);

@@ -144,0 +144,0 @@ assert.ok(state.tasks.some((task) => task.requiresEvidence), `${app.id} plan must include evidence gates`);

@@ -9,3 +9,3 @@ #!/usr/bin/env node

const TARGET_VERSION = "0.2.1";
const TARGET_VERSION = "0.2.2";
const PREVIOUS_VERSION = "0.1.31";

@@ -12,0 +12,0 @@ const pluginRoot = path.resolve(__dirname, "..");

@@ -36,3 +36,3 @@ #!/usr/bin/env node

assert.equal(appValidation.summary.id, "end-to-end-golden-path");
assert.equal(appValidation.summary.version, "0.2.1");
assert.equal(appValidation.summary.version, "0.2.2");

@@ -46,3 +46,3 @@ const plan = runJson(

"--question",
"Prove the deterministic v0.2.1 end-to-end golden path."
"Prove the deterministic v0.2.2 end-to-end golden path."
],

@@ -57,3 +57,3 @@ pluginRoot

assert.equal(state.workflow.app.id, "end-to-end-golden-path");
assert.equal(state.workflow.app.version, "0.2.1");
assert.equal(state.workflow.app.version, "0.2.2");
assert.equal(state.loopStage, "interpret");

@@ -201,3 +201,3 @@

const report = fs.readFileSync(reportPath, "utf8");
assert.match(report, /Workflow App: end-to-end-golden-path@0\.2\.1/);
assert.match(report, /Workflow App: end-to-end-golden-path@0\.2\.2/);
assert.match(report, /## Candidates/);

@@ -204,0 +204,0 @@ assert.match(report, /## Trust Audit/);

@@ -42,2 +42,3 @@ #!/usr/bin/env node

"docs/security-trust-hardening.7.md",
"docs/trust-audit-anchor.7.md",
"../../CHANGELOG.md",

@@ -62,2 +63,3 @@ "../../RELEASE.md"

{ name: "dist freshness", command: ["npm", "run", "dist:check"] },
{ name: "core/shell purity", command: ["npm", "run", "purity:check"] },
{ name: "type check", command: ["npm", "run", "check"] },

@@ -71,2 +73,7 @@ { name: "onramp contract", command: ["npm", "run", "onramp:check"] },

{ name: "tests", command: ["npm", "run", "test:ci"] },
// Pure core/ unit tests (test/*.test.js) — a separate suite from the smoke
// tests above (see test/run-unit.js's header). --skip-tests also skips
// this one, matching "tests": ci.yml runs test:unit directly, so this
// entry would just repeat it a second time in the release-check pass.
{ name: "unit tests", command: ["npm", "run", "test:unit"] },
{ name: "canonical apps", command: ["npm", "run", "canonical-apps"] },

@@ -94,3 +101,3 @@ { name: "golden path", command: ["npm", "run", "golden-path"] },

try {
if (skipTests && check.name === "tests") {
if (skipTests && (check.name === "tests" || check.name === "unit tests")) {
results.push({ name: check.name, ok: true, skipped: true, elapsedMs: 0 });

@@ -97,0 +104,0 @@ process.stdout.write("skipped\n");

@@ -23,3 +23,3 @@ #!/usr/bin/env node

// Fallback to the filesystem only when we cannot read from HEAD: not a git work
// tree, or the path is not tracked at HEAD (e.g. the gitignored package-lock).
// tree, or the path is not tracked at HEAD.
// node + git only — no ripgrep (CI portability rule).

@@ -40,3 +40,3 @@ const insideGitWorkTree = (() => {

if (r.status === 0) return { text: r.stdout, exists: true, fromHead: true };
// Not tracked at HEAD — fall through to the working tree (e.g. package-lock).
// Not tracked at HEAD — fall through to the working tree.
}

@@ -61,5 +61,10 @@ const abs = path.join(repoRoot, relativePath);

checkJson("plugins/cool-workflow/package.json", "version", VERSION, checks);
// package-lock.json is a gitignored install artifact (the documented install
// uses `npm install --no-package-lock`), so only validate it when present.
// package-lock.json is tracked now, but the documented install still works
// without it (`npm install --no-package-lock`), so only validate it when
// present. Check BOTH version fields: the top-level one and the root-package
// entry packages[""].version — the second one is the field npm keeps in step
// with package.json on `npm install`, and it is the one that went stale
// (0.1.97) through the v0.2.0/v0.2.1 cuts while only the first was checked.
checkJsonIfPresent("plugins/cool-workflow/package-lock.json", "version", VERSION, checks);
checkNestedJsonIfPresent("plugins/cool-workflow/package-lock.json", ["packages", "", "version"], VERSION, checks);
checkJson("plugins/cool-workflow/.codex-plugin/plugin.json", "version", VERSION, checks);

@@ -166,3 +171,3 @@ checkJson("plugins/cool-workflow/server.json", "version", VERSION, checks);

checkIncludes("plugins/cool-workflow/test/run-retention-reclamation-smoke.js", "run-retention-reclamation-smoke", checks);
checkIncludes("plugins/cool-workflow/src/core/capability-table.ts", "gc.plan", checks);
checkIncludes("plugins/cool-workflow/src/wiring/capability-table/scheduling-registry.ts", "gc.plan", checks);
checkIncludes("plugins/cool-workflow/docs/durable-state-and-locking.7.md", "Durable State & Locking", checks);

@@ -175,23 +180,23 @@ checkIncludes("plugins/cool-workflow/docs/durable-state-and-locking.7.md", VERSION, checks);

checkIncludes("plugins/cool-workflow/dist/shell/drive.js", "driveStep", checks);
checkIncludes("plugins/cool-workflow/src/core/capability-table.ts", "run.drive", checks);
checkIncludes("plugins/cool-workflow/src/wiring/capability-table/pipeline.ts", "run.drive", checks);
checkIncludes("plugins/cool-workflow/src/shell/collaboration-io.ts", "deriveReviewState", checks);
checkIncludes("plugins/cool-workflow/dist/shell/collaboration-io.js", "deriveReviewState", checks);
checkIncludes("plugins/cool-workflow/src/core/capability-table.ts", "review.status", checks);
checkIncludes("plugins/cool-workflow/src/wiring/capability-table/multi-agent.ts", "review.status", checks);
checkIncludes("plugins/cool-workflow/src/shell/observability.ts", "deriveMetricsReport", checks);
checkIncludes("plugins/cool-workflow/dist/shell/observability.js", "deriveMetricsReport", checks);
checkIncludes("plugins/cool-workflow/src/core/capability-table.ts", "metrics.show", checks);
checkIncludes("plugins/cool-workflow/src/wiring/capability-table/reporting.ts", "metrics.show", checks);
checkIncludes("plugins/cool-workflow/manifest/pricing.policy.json", "schemaVersion", checks);
checkIncludes("plugins/cool-workflow/src/shell/workbench.ts", "buildWorkbenchRunView", checks);
checkIncludes("plugins/cool-workflow/dist/shell/workbench.js", "buildWorkbenchRunView", checks);
checkIncludes("plugins/cool-workflow/src/core/capability-table.ts", "workbench.view", checks);
checkIncludes("plugins/cool-workflow/src/wiring/capability-table/reporting.ts", "workbench.view", checks);
checkIncludes("plugins/cool-workflow/src/shell/execution-backend/registry.ts", "ExecutionBackend", checks);
checkIncludes("plugins/cool-workflow/dist/shell/execution-backend/registry.js", "ExecutionBackend", checks);
checkIncludes("plugins/cool-workflow/src/core/capability-table.ts", "backend.list", checks);
checkIncludes("plugins/cool-workflow/src/wiring/capability-table/exec-backend.ts", "backend.list", checks);
checkIncludes("plugins/cool-workflow/src/shell/run-registry-io.ts", "RunRegistry", checks);
checkIncludes("plugins/cool-workflow/dist/shell/run-registry-io.js", "RunRegistry", checks);
checkIncludes("plugins/cool-workflow/src/core/capability-table.ts", "registry.refresh", checks);
checkIncludes("plugins/cool-workflow/src/wiring/capability-table/scheduling-registry.ts", "registry.refresh", checks);
checkIncludes("plugins/cool-workflow/package.json", "parity:check", checks);
checkIncludes("plugins/cool-workflow/scripts/parity-check.js", "buildParityReport", checks);
checkIncludes("plugins/cool-workflow/test/cli-mcp-parity-smoke.js", "cli-mcp-parity-smoke", checks);
checkIncludes("plugins/cool-workflow/src/core/capability-table.ts", "export const REGISTRY", checks);
checkIncludes("plugins/cool-workflow/src/wiring/capability-table/registry-core.ts", "export const REGISTRY", checks);
checkIncludes("plugins/cool-workflow/src/shell/pipeline-cli.ts", "planSummary", checks);

@@ -203,2 +208,6 @@ checkIncludes("plugins/cool-workflow/dist/core/capability-table.js", "REGISTRY", checks);

checkIncludes("plugins/cool-workflow/package.json", "eval:replay", checks);
checkIncludes("plugins/cool-workflow/docs/trust-audit-anchor.7.md", "Trust Audit Anchor", checks);
checkIncludes("plugins/cool-workflow/docs/trust-audit-anchor.7.md", VERSION, checks);
checkIncludes("plugins/cool-workflow/docs/index.md", "trust-audit-anchor.7.md", checks);
checkIncludes("plugins/cool-workflow/test/trust-audit-anchor-smoke.js", "trust-audit-anchor-smoke", checks);
checkIncludes("plugins/cool-workflow/docs/release-and-migration.7.md", VERSION, checks);

@@ -236,2 +245,14 @@ checkIncludes("CHANGELOG.md", `## ${VERSION}`, checks);

function checkNestedJsonIfPresent(relativePath, keyPath, expected, checks) {
const src = readReleaseSource(relativePath);
if (!src.exists) {
checks.push({ path: relativePath, key: keyPath.join("."), skipped: "absent" });
return;
}
let value = JSON.parse(src.text);
for (const key of keyPath) value = value?.[key];
assert.equal(value, expected, `${relativePath}.${keyPath.join(".")} must be ${expected}`);
checks.push({ path: relativePath, key: keyPath.join("."), value });
}
function checkJsonIfPresent(relativePath, key, expected, checks) {

@@ -238,0 +259,0 @@ const src = readReleaseSource(relativePath);

Sorry, the diff of this file is too big to display