cool-workflow
Advanced tools
| "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. |
| { | ||
| "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": [ |
+18
-86
@@ -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 { |
+26
-10
@@ -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, |
+17
-1
@@ -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 |
+1
-0
@@ -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", |
+3
-1
| { | ||
| "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
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
3054447
1.98%300
7.14%47813
1.64%216
0.47%