patchwarden 1.5.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/README.en.md +43 -2
  2. package/README.md +43 -2
  3. package/dist/assessments/agentAssessor.d.ts +1 -1
  4. package/dist/assessments/agentAssessor.js +3 -3
  5. package/dist/assessments/assessmentStore.d.ts +1 -1
  6. package/dist/assessments/assessmentStore.js +2 -2
  7. package/dist/assessments/confirmCli.js +5 -4
  8. package/dist/config.d.ts +7 -0
  9. package/dist/config.js +34 -0
  10. package/dist/control/middleware/auth.d.ts +10 -0
  11. package/dist/control/middleware/auth.js +8 -0
  12. package/dist/control/middleware/static.d.ts +3 -0
  13. package/dist/control/middleware/static.js +65 -0
  14. package/dist/control/routes/audit.d.ts +15 -0
  15. package/dist/control/routes/audit.js +277 -0
  16. package/dist/control/routes/evidence.d.ts +4 -0
  17. package/dist/control/routes/evidence.js +96 -0
  18. package/dist/control/routes/lineage.d.ts +3 -0
  19. package/dist/control/routes/lineage.js +71 -0
  20. package/dist/control/routes/policy.d.ts +3 -0
  21. package/dist/control/routes/policy.js +81 -0
  22. package/dist/control/routes/process.d.ts +5 -0
  23. package/dist/control/routes/process.js +200 -0
  24. package/dist/control/routes/sessions.d.ts +22 -0
  25. package/dist/control/routes/sessions.js +224 -0
  26. package/dist/control/routes/status.d.ts +6 -0
  27. package/dist/control/routes/status.js +250 -0
  28. package/dist/control/routes/taskActions.d.ts +21 -0
  29. package/dist/control/routes/taskActions.js +233 -0
  30. package/dist/control/routes/tasks.d.ts +20 -0
  31. package/dist/control/routes/tasks.js +310 -0
  32. package/dist/control/routes/workspace.d.ts +15 -0
  33. package/dist/control/routes/workspace.js +193 -0
  34. package/dist/control/runtime.d.ts +91 -0
  35. package/dist/control/runtime.js +392 -0
  36. package/dist/control/server.d.ts +13 -0
  37. package/dist/control/server.js +479 -0
  38. package/dist/control/shared.d.ts +35 -0
  39. package/dist/control/shared.js +288 -0
  40. package/dist/controlCenter.d.ts +6 -0
  41. package/dist/controlCenter.js +7 -2197
  42. package/dist/direct/directGuards.js +30 -8
  43. package/dist/doctor.d.ts +18 -1
  44. package/dist/doctor.js +579 -348
  45. package/dist/goal/goalReport.d.ts +54 -0
  46. package/dist/goal/goalReport.js +204 -0
  47. package/dist/goal/goalStatus.d.ts +6 -0
  48. package/dist/goal/specKitImport.d.ts +63 -0
  49. package/dist/goal/specKitImport.js +220 -0
  50. package/dist/goal/subgoalSync.js +2 -1
  51. package/dist/httpServer.js +15 -12
  52. package/dist/index.js +7 -4
  53. package/dist/logging.d.ts +7 -1
  54. package/dist/logging.js +8 -0
  55. package/dist/runner/changeCapture.d.ts +3 -3
  56. package/dist/runner/changeCapture.js +63 -39
  57. package/dist/runner/cli.js +7 -6
  58. package/dist/runner/postTaskCleanup.js +26 -2
  59. package/dist/runner/runTask.js +245 -221
  60. package/dist/runner/simpleProcess.js +4 -4
  61. package/dist/runner/watch.js +17 -14
  62. package/dist/security/contentRedaction.d.ts +6 -0
  63. package/dist/security/contentRedaction.js +22 -0
  64. package/dist/smoke-test.js +257 -251
  65. package/dist/test/unit/apply-patch.test.d.ts +1 -0
  66. package/dist/test/unit/apply-patch.test.js +225 -0
  67. package/dist/test/unit/create-task.test.d.ts +1 -0
  68. package/dist/test/unit/create-task.test.js +197 -0
  69. package/dist/test/unit/direct-guards.test.js +124 -9
  70. package/dist/test/unit/evidence-pack.test.js +95 -1
  71. package/dist/test/unit/get-task-status.test.d.ts +1 -0
  72. package/dist/test/unit/get-task-status.test.js +174 -0
  73. package/dist/test/unit/get-task-summary.test.d.ts +1 -0
  74. package/dist/test/unit/get-task-summary.test.js +146 -0
  75. package/dist/test/unit/goal-report.test.d.ts +1 -0
  76. package/dist/test/unit/goal-report.test.js +159 -0
  77. package/dist/test/unit/goal-subgoal-task.test.js +6 -6
  78. package/dist/test/unit/goal-tools-registry.test.js +6 -4
  79. package/dist/test/unit/path-guard.test.js +24 -0
  80. package/dist/test/unit/spec-kit-import.test.d.ts +1 -0
  81. package/dist/test/unit/spec-kit-import.test.js +341 -0
  82. package/dist/test/unit/wait-for-task.test.d.ts +1 -0
  83. package/dist/test/unit/wait-for-task.test.js +144 -0
  84. package/dist/tools/auditTask.d.ts +8 -63
  85. package/dist/tools/auditTask.js +12 -8
  86. package/dist/tools/createDirectSession.d.ts +1 -1
  87. package/dist/tools/createDirectSession.js +2 -2
  88. package/dist/tools/createTask.d.ts +2 -2
  89. package/dist/tools/createTask.js +4 -4
  90. package/dist/tools/dispatch/coreDispatch.d.ts +9 -0
  91. package/dist/tools/dispatch/coreDispatch.js +282 -0
  92. package/dist/tools/dispatch/diagnosticDispatch.d.ts +14 -0
  93. package/dist/tools/dispatch/diagnosticDispatch.js +78 -0
  94. package/dist/tools/dispatch/directDispatch.d.ts +8 -0
  95. package/dist/tools/dispatch/directDispatch.js +115 -0
  96. package/dist/tools/dispatch/goalDispatch.d.ts +8 -0
  97. package/dist/tools/dispatch/goalDispatch.js +91 -0
  98. package/dist/tools/dispatch/releaseDispatch.d.ts +8 -0
  99. package/dist/tools/dispatch/releaseDispatch.js +45 -0
  100. package/dist/tools/dispatch/types.d.ts +23 -0
  101. package/dist/tools/dispatch/types.js +15 -0
  102. package/dist/tools/evidencePack.d.ts +6 -0
  103. package/dist/tools/evidencePack.js +213 -6
  104. package/dist/tools/finalizeDirectSession.d.ts +1 -1
  105. package/dist/tools/finalizeDirectSession.js +3 -3
  106. package/dist/tools/goalSubgoalTask.d.ts +1 -1
  107. package/dist/tools/goalSubgoalTask.js +2 -2
  108. package/dist/tools/healthCheck.js +3 -3
  109. package/dist/tools/registry.d.ts +3 -3
  110. package/dist/tools/registry.js +60 -503
  111. package/dist/tools/retryTask.d.ts +2 -2
  112. package/dist/tools/retryTask.js +2 -2
  113. package/dist/tools/runTaskLoop.js +4 -4
  114. package/dist/tools/safeViews.d.ts +2 -2
  115. package/dist/tools/safeViews.js +2 -2
  116. package/dist/tools/toolRegistry.js +22 -0
  117. package/dist/version.d.ts +1 -1
  118. package/dist/version.js +1 -1
  119. package/docs/CODE_WIKI.md +983 -0
  120. package/docs/agentseal-integration.md +150 -0
  121. package/docs/architecture.md +63 -0
  122. package/docs/assets/patchwarden-oss-demo.gif +0 -0
  123. package/docs/dashboard-overview.md +86 -0
  124. package/docs/demo.md +8 -0
  125. package/docs/direct-session-workflow.md +98 -0
  126. package/docs/evidence-pack-schema.md +215 -0
  127. package/docs/execution-plan-2026-07-09.md +315 -0
  128. package/docs/lineage-evidence-pack-workflow.md +127 -0
  129. package/docs/mcp-inspector-testing.md +200 -0
  130. package/docs/open-source-application.md +162 -0
  131. package/docs/opencode-worker.md +151 -0
  132. package/docs/openhands-worker.md +181 -0
  133. package/docs/release-evidence.md +72 -0
  134. package/docs/roadmap-execution-and-acceptance.md +365 -0
  135. package/docs/spec-kit-integration.md +131 -0
  136. package/docs/task-safe-review-workflow.md +98 -0
  137. package/docs/threat-model.md +79 -0
  138. package/docs/user-feedback.md +40 -0
  139. package/docs/why-patchwarden.md +110 -0
  140. package/package.json +2 -2
  141. package/scripts/checks/control-center-smoke.js +356 -0
  142. package/scripts/checks/lifecycle-smoke.js +29 -23
  143. package/scripts/checks/mcp-smoke.js +3 -1
  144. package/scripts/generate-demo-gif.py +320 -0
  145. package/src/assessments/agentAssessor.ts +3 -3
  146. package/src/assessments/assessmentStore.ts +2 -2
  147. package/src/assessments/confirmCli.ts +5 -4
  148. package/src/config.ts +37 -0
  149. package/src/control/middleware/auth.ts +17 -0
  150. package/src/control/middleware/static.ts +71 -0
  151. package/src/control/routes/audit.ts +321 -0
  152. package/src/control/routes/evidence.ts +107 -0
  153. package/src/control/routes/lineage.ts +92 -0
  154. package/src/control/routes/policy.ts +81 -0
  155. package/src/control/routes/process.ts +204 -0
  156. package/src/control/routes/sessions.ts +251 -0
  157. package/src/control/routes/status.ts +325 -0
  158. package/src/control/routes/taskActions.ts +248 -0
  159. package/src/control/routes/tasks.ts +323 -0
  160. package/src/control/routes/workspace.ts +203 -0
  161. package/src/control/runtime.ts +472 -0
  162. package/src/control/server.ts +471 -0
  163. package/src/control/shared.ts +294 -0
  164. package/src/controlCenter.ts +7 -2347
  165. package/src/direct/directGuards.ts +28 -7
  166. package/src/doctor.ts +741 -481
  167. package/src/goal/goalReport.ts +271 -0
  168. package/src/goal/goalStatus.ts +6 -0
  169. package/src/goal/specKitImport.ts +355 -0
  170. package/src/goal/subgoalSync.ts +4 -2
  171. package/src/httpServer.ts +17 -14
  172. package/src/index.ts +7 -4
  173. package/src/logging.ts +10 -1
  174. package/src/runner/changeCapture.ts +70 -42
  175. package/src/runner/cli.ts +7 -6
  176. package/src/runner/postTaskCleanup.ts +26 -2
  177. package/src/runner/runTask.ts +325 -223
  178. package/src/runner/simpleProcess.ts +4 -4
  179. package/src/runner/watch.ts +17 -14
  180. package/src/security/contentRedaction.ts +29 -0
  181. package/src/smoke-test.ts +252 -250
  182. package/src/test/unit/apply-patch.test.ts +293 -0
  183. package/src/test/unit/create-task.test.ts +255 -0
  184. package/src/test/unit/direct-guards.test.ts +178 -8
  185. package/src/test/unit/evidence-pack.test.ts +110 -1
  186. package/src/test/unit/get-task-status.test.ts +203 -0
  187. package/src/test/unit/get-task-summary.test.ts +173 -0
  188. package/src/test/unit/goal-report.test.ts +189 -0
  189. package/src/test/unit/goal-subgoal-task.test.ts +6 -6
  190. package/src/test/unit/goal-tools-registry.test.ts +7 -5
  191. package/src/test/unit/path-guard.test.ts +30 -0
  192. package/src/test/unit/spec-kit-import.test.ts +429 -0
  193. package/src/test/unit/wait-for-task.test.ts +176 -0
  194. package/src/tools/auditTask.ts +99 -59
  195. package/src/tools/createDirectSession.ts +3 -3
  196. package/src/tools/createTask.ts +7 -7
  197. package/src/tools/dispatch/coreDispatch.ts +374 -0
  198. package/src/tools/dispatch/diagnosticDispatch.ts +101 -0
  199. package/src/tools/dispatch/directDispatch.ts +167 -0
  200. package/src/tools/dispatch/goalDispatch.ts +127 -0
  201. package/src/tools/dispatch/releaseDispatch.ts +65 -0
  202. package/src/tools/dispatch/types.ts +24 -0
  203. package/src/tools/evidencePack.ts +291 -6
  204. package/src/tools/finalizeDirectSession.ts +4 -4
  205. package/src/tools/goalSubgoalTask.ts +2 -2
  206. package/src/tools/healthCheck.ts +3 -3
  207. package/src/tools/registry.ts +68 -628
  208. package/src/tools/retryTask.ts +2 -2
  209. package/src/tools/runTaskLoop.ts +4 -4
  210. package/src/tools/safeViews.ts +2 -2
  211. package/src/tools/toolRegistry.ts +22 -0
  212. package/src/version.ts +1 -1
  213. package/ui/pages/audit.html +192 -3
  214. package/ui/pages/dashboard.html +959 -44
  215. package/ui/pages/direct-sessions.html +505 -53
  216. package/ui/pages/task-detail.html +456 -438
  217. package/ui/pages/tasks.html +598 -61
@@ -1,4 +1,4 @@
1
- /**
1
+ /**
2
2
  * Shared tool registry for PatchWarden MCP server.
3
3
  * Used by both stdio (index.ts) and HTTP (httpServer.ts) transports.
4
4
  */
@@ -9,69 +9,8 @@ import {
9
9
  ListToolsRequestSchema,
10
10
  } from "@modelcontextprotocol/sdk/types.js";
11
11
  import { getAllConfiguredTestCommands, getAllConfiguredDirectCommands, getConfig } from "../config.js";
12
- import { savePlan } from "../tools/savePlan.js";
13
- import { getPlan } from "../tools/getPlan.js";
14
- import { createTask } from "../tools/createTask.js";
15
- import { getTaskStatus } from "../tools/getTaskStatus.js";
16
- import { getResult, getResultJson, getDiff, getTestLog, getTaskLogTail } from "../tools/taskOutputs.js";
17
- import { listWorkspace } from "../tools/listWorkspace.js";
18
- import { readWorkspaceFile } from "../tools/readWorkspaceFile.js";
19
- import { listTasks } from "../tools/listTasks.js";
20
- import { cancelTask } from "../tools/cancelTask.js";
21
- import { killTask } from "../tools/killTask.js";
22
- import { retryTask } from "../tools/retryTask.js";
23
- import { getTaskStdoutTail } from "../tools/getTaskStdoutTail.js";
24
- import { getTaskProgress } from "../tools/getTaskProgress.js";
25
- import { listAgents } from "../tools/listAgents.js";
26
- import { healthCheck } from "../tools/healthCheck.js";
27
- import { getTaskSummary } from "../tools/getTaskSummary.js";
28
- import { waitForTask } from "../tools/waitForTask.js";
29
- import { runTaskLoop } from "./runTaskLoop.js";
30
- import { getTaskLineage } from "./taskLineage.js";
31
- import { exportTaskEvidencePack } from "./evidencePack.js";
32
- import { recommendAgentForTask } from "./recommendAgentForTask.js";
33
12
  import { errorPayload, PatchWardenError } from "../errors.js";
34
- import { auditTask } from "../tools/auditTask.js";
35
- import { safeStatus } from "../tools/safeStatus.js";
36
- import {
37
- safeAudit,
38
- safeAuditDirectSession,
39
- safeDiffSummary,
40
- safeDirectSummary,
41
- safeFinalizeDirectSession,
42
- safeResult,
43
- safeTestSummary,
44
- } from "../tools/safeViews.js";
45
- import { diagnoseTask } from "../tools/diagnoseTask.js";
46
- import { reconcileTasks } from "../tools/reconcileTasks.js";
47
- import { discoverTools } from "../tools/discoverTools.js";
48
- import { explainTool } from "../tools/explainTool.js";
49
- import { invokeDiscoveredTool } from "./invokeDiscoveredTool.js";
50
13
  import { logger } from "../logging.js";
51
- import { runTask } from "../runner/runTask.js";
52
- import { createDirectSession } from "../tools/createDirectSession.js";
53
- import { searchWorkspace } from "../tools/searchWorkspace.js";
54
- import { applyPatch } from "../tools/applyPatch.js";
55
- import { runVerification } from "../tools/runVerification.js";
56
- import { runDirectVerificationBundle } from "../tools/runDirectVerificationBundle.js";
57
- import { finalizeDirectSession } from "../tools/finalizeDirectSession.js";
58
- import { auditSession } from "../tools/auditSession.js";
59
- import { syncFile } from "../tools/syncFile.js";
60
- import { createGoal, listGoals, readGoal, readGoalStatus } from "../goal/goalStore.js";
61
- import { suggestNextSubgoal } from "../goal/goalGraph.js";
62
- import { exportHandoff } from "../goal/handoffExport.js";
63
- import { acceptSubgoal, rejectSubgoal, summarizeGoalProgress } from "../goal/goalProgress.js";
64
- import { createSubgoalTask } from "./goalSubgoalTask.js";
65
- import { checkReleaseGate } from "./checkReleaseGate.js";
66
- import {
67
- getProjectPolicyTool,
68
- releaseCheck,
69
- releaseCleanup,
70
- releasePrepare,
71
- releaseVerify,
72
- } from "./releaseMode.js";
73
- import { mergeWorktreeTool } from "./mergeWorktree.js";
74
- import { discardWorktreeTool } from "./discardWorktree.js";
75
14
  import { TASK_TEMPLATE_NAMES } from "./taskTemplates.js";
76
15
  import {
77
16
  buildToolCatalogSnapshot,
@@ -80,6 +19,12 @@ import {
80
19
  selectToolsForProfile,
81
20
  type ToolCatalogSnapshot,
82
21
  } from "./toolCatalog.js";
22
+ import { coreHandlers, runTaskHandler } from "./dispatch/coreDispatch.js";
23
+ import { goalHandlers } from "./dispatch/goalDispatch.js";
24
+ import { directHandlers } from "./dispatch/directDispatch.js";
25
+ import { releaseHandlers } from "./dispatch/releaseDispatch.js";
26
+ import { diagnosticHandlers } from "./dispatch/diagnosticDispatch.js";
27
+ import type { ToolHandlerMap } from "./dispatch/types.js";
83
28
 
84
29
  // ── Tool definitions ──────────────────────────────────────────────
85
30
 
@@ -954,6 +899,29 @@ export function getToolDefs(): ToolDef[] {
954
899
  required: ["goal_id"],
955
900
  },
956
901
  },
902
+ {
903
+ name: "export_goal_report",
904
+ description: "Export a structured final report for a Goal session, aggregating subgoal completion, task evidence, and risk summary.",
905
+ inputSchema: {
906
+ type: "object",
907
+ properties: {
908
+ goal_id: { type: "string", description: "The goal id to export report for." },
909
+ },
910
+ required: ["goal_id"],
911
+ },
912
+ },
913
+ {
914
+ name: "import_speckit_tasks",
915
+ description: "Import Spec Kit tasks into a Goal session as subgoals, mapping task files to scope hints and acceptance criteria.",
916
+ inputSchema: {
917
+ type: "object",
918
+ properties: {
919
+ goal_id: { type: "string", description: "The goal id to import tasks into." },
920
+ spec_kit_json: { type: "string", description: "Spec Kit JSON text containing spec, tasks[], and acceptance[] fields." },
921
+ },
922
+ required: ["goal_id", "spec_kit_json"],
923
+ },
924
+ },
957
925
  {
958
926
  name: "check_release_gate",
959
927
  description:
@@ -1287,7 +1255,7 @@ export function getToolDefs(): ToolDef[] {
1287
1255
  });
1288
1256
 
1289
1257
  // run_task: only available when explicitly enabled
1290
- if ((config as any).enableRunTaskTool === true) {
1258
+ if (config.enableRunTaskTool === true) {
1291
1259
  tools.push({
1292
1260
  name: "run_task",
1293
1261
  description:
@@ -1314,21 +1282,32 @@ export function getToolCatalogSnapshot(): ToolCatalogSnapshot {
1314
1282
  return buildToolCatalogSnapshot(tools, resolveToolProfile(config.toolProfile));
1315
1283
  }
1316
1284
 
1317
- // ── Request handler ───────────────────────────────────────────────
1285
+ // ── Dispatch map ─────────────────────────────────────────────────
1318
1286
 
1319
- function guardDirectProfileEnabled(): void {
1287
+ /**
1288
+ * Combined dispatch map assembled from all domain handler maps.
1289
+ * run_task is conditionally added when enableRunTaskTool is true,
1290
+ * mirroring the conditional registration in getToolDefs().
1291
+ */
1292
+ function buildDispatchMap(): ToolHandlerMap {
1320
1293
  const config = getConfig();
1321
- if (!config.enableDirectProfile) {
1322
- throw new PatchWardenError(
1323
- "direct_profile_disabled",
1324
- "Direct profile is disabled by local config.",
1325
- "Set enableDirectProfile: true in patchwarden.config.json to use Direct session tools.",
1326
- true,
1327
- { operation: "direct_tool_call" }
1328
- );
1294
+ const map: ToolHandlerMap = {
1295
+ ...coreHandlers,
1296
+ ...goalHandlers,
1297
+ ...directHandlers,
1298
+ ...releaseHandlers,
1299
+ ...diagnosticHandlers,
1300
+ };
1301
+ if (config.enableRunTaskTool === true) {
1302
+ map.run_task = runTaskHandler;
1329
1303
  }
1304
+ return map;
1330
1305
  }
1331
1306
 
1307
+ const dispatchMap: ToolHandlerMap = buildDispatchMap();
1308
+
1309
+ // ── Request handler ───────────────────────────────────────────────
1310
+
1332
1311
  export async function handleToolCall(name: string, args: Record<string, unknown> | undefined) {
1333
1312
  const startTime = Date.now();
1334
1313
  const taskId = args?.task_id ? String(args.task_id) : args?.session_id ? String(args.session_id) : undefined;
@@ -1344,562 +1323,11 @@ export async function handleToolCall(name: string, args: Record<string, unknown>
1344
1323
  }
1345
1324
 
1346
1325
  async function handleToolCallInternal(name: string, args: Record<string, unknown> | undefined) {
1347
- switch (name) {
1348
- case "save_plan": {
1349
- return toResult(
1350
- savePlan({
1351
- title: String(args?.title ?? ""),
1352
- content: args?.content !== undefined ? String(args.content) : "",
1353
- plan_ref: args?.plan_ref ? String(args.plan_ref) : undefined,
1354
- })
1355
- );
1356
- }
1357
-
1358
- case "get_plan": {
1359
- return toResult(
1360
- getPlan({ plan_id: String(args?.plan_id ?? "") })
1361
- );
1362
- }
1363
-
1364
- case "create_task": {
1365
- return toResult(
1366
- createTask({
1367
- plan_id: args?.plan_id ? String(args.plan_id) : undefined,
1368
- inline_plan: args?.inline_plan ? String(args.inline_plan) : undefined,
1369
- plan_title: args?.plan_title ? String(args.plan_title) : undefined,
1370
- template: args?.template ? String(args.template) as any : undefined,
1371
- goal: args?.goal ? String(args.goal) : undefined,
1372
- source_task_id: args?.source_task_id ? String(args.source_task_id) : undefined,
1373
- agent: String(args?.agent ?? ""),
1374
- repo_path: args?.repo_path ? String(args.repo_path) : undefined,
1375
- test_command: args?.test_command ? String(args.test_command) : undefined,
1376
- verify_commands: Array.isArray(args?.verify_commands)
1377
- ? args.verify_commands.map((command) => String(command))
1378
- : undefined,
1379
- timeout_seconds: args?.timeout_seconds !== undefined
1380
- ? Number(args.timeout_seconds)
1381
- : undefined,
1382
- execution_mode: args?.execution_mode === "assess_only" ? "assess_only" : "execute",
1383
- assessment_id: args?.assessment_id ? String(args.assessment_id) : undefined,
1384
- })
1385
- );
1386
- }
1387
-
1388
- case "run_task_loop": {
1389
- return toResult(await runTaskLoop({
1390
- repo_path: String(args?.repo_path ?? ""),
1391
- goal: String(args?.goal ?? ""),
1392
- verify_commands: Array.isArray(args?.verify_commands)
1393
- ? args.verify_commands.map((command) => String(command))
1394
- : [],
1395
- agent: args?.agent ? String(args.agent) : undefined,
1396
- template:
1397
- args?.template === "inspect_only" || args?.template === "release_check"
1398
- ? args.template
1399
- : "feature_small",
1400
- max_iterations: args?.max_iterations !== undefined ? Number(args.max_iterations) : undefined,
1401
- task_timeout_seconds: args?.task_timeout_seconds !== undefined ? Number(args.task_timeout_seconds) : undefined,
1402
- auto_fix_tests: args?.auto_fix_tests !== undefined ? Boolean(args.auto_fix_tests) : undefined,
1403
- auto_cleanup_artifacts: args?.auto_cleanup_artifacts !== undefined ? Boolean(args.auto_cleanup_artifacts) : undefined,
1404
- stop_on_high_risk: args?.stop_on_high_risk !== undefined ? Boolean(args.stop_on_high_risk) : undefined,
1405
- direct_verify: args?.direct_verify !== undefined ? Boolean(args.direct_verify) : undefined,
1406
- direct_verify_commands: Array.isArray(args?.direct_verify_commands)
1407
- ? args.direct_verify_commands.map((command) => String(command))
1408
- : undefined,
1409
- direct_verify_timeout_seconds: args?.direct_verify_timeout_seconds !== undefined ? Number(args.direct_verify_timeout_seconds) : undefined,
1410
- scope_files: Array.isArray(args?.scope_files)
1411
- ? args.scope_files.map((entry) => String(entry))
1412
- : undefined,
1413
- isolation_mode: args?.isolation_mode === "worktree" ? "worktree" : "current_repo",
1414
- worktree_base_branch: args?.worktree_base_branch ? String(args.worktree_base_branch) : undefined,
1415
- worktree_cleanup:
1416
- args?.worktree_cleanup === "archive" || args?.worktree_cleanup === "delete_ignored_only"
1417
- ? args.worktree_cleanup
1418
- : "keep",
1419
- }));
1420
- }
1421
-
1422
- case "recommend_agent_for_task": {
1423
- return toResult(recommendAgentForTask({
1424
- repo_path: String(args?.repo_path ?? ""),
1425
- goal: String(args?.goal ?? ""),
1426
- scope_files: Array.isArray(args?.scope_files) ? args.scope_files.map((entry) => String(entry)) : undefined,
1427
- template: args?.template ? String(args.template) : undefined,
1428
- risk_hint: args?.risk_hint ? String(args.risk_hint) : undefined,
1429
- }));
1430
- }
1431
-
1432
- case "get_task_lineage": {
1433
- return toResult(getTaskLineage(String(args?.lineage_id ?? ""), {
1434
- max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
1435
- }));
1436
- }
1437
-
1438
- case "export_task_evidence_pack": {
1439
- return toResult(exportTaskEvidencePack({
1440
- lineage_id: String(args?.lineage_id ?? ""),
1441
- max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
1442
- }));
1443
- }
1444
-
1445
- case "get_project_policy": {
1446
- return toResult(getProjectPolicyTool(String(args?.repo_path ?? "")));
1447
- }
1448
-
1449
- case "get_task_status": {
1450
- return toResult(getTaskStatus(String(args?.task_id ?? "")));
1451
- }
1452
-
1453
- case "get_result": {
1454
- return toResult(getResult(String(args?.task_id ?? "")));
1455
- }
1456
-
1457
- case "get_result_json": {
1458
- return toResult(getResultJson(String(args?.task_id ?? "")));
1459
- }
1460
-
1461
- case "get_diff": {
1462
- return toResult(getDiff(String(args?.task_id ?? "")));
1463
- }
1464
-
1465
- case "get_test_log": {
1466
- return toResult(getTestLog(String(args?.task_id ?? "")));
1467
- }
1468
-
1469
- case "list_workspace": {
1470
- return toResult(
1471
- listWorkspace(args?.path ? String(args.path) : undefined)
1472
- );
1473
- }
1474
-
1475
- case "read_workspace_file": {
1476
- const sessionId = args?.session_id ? String(args.session_id) : undefined;
1477
- return toResult(readWorkspaceFile({
1478
- path: String(args?.path ?? ""),
1479
- session_id: sessionId,
1480
- }));
1481
- }
1482
-
1483
- case "list_tasks": {
1484
- return toResult(listTasks({
1485
- status: args?.status ? String(args.status) : undefined,
1486
- repo_path: args?.repo_path ? String(args.repo_path) : undefined,
1487
- active_only: args?.active_only !== undefined ? Boolean(args.active_only) : undefined,
1488
- limit: args?.limit ? Number(args.limit) : undefined,
1489
- }));
1490
- }
1491
-
1492
- case "list_agents": {
1493
- return toResult(listAgents());
1494
- }
1495
-
1496
- case "health_check": {
1497
- return toResult(healthCheck(getToolCatalogSnapshot(), {
1498
- detail: args?.detail === "self_diagnostic" ? "self_diagnostic" : "standard",
1499
- }));
1500
- }
1501
-
1502
- case "cancel_task": {
1503
- return toResult(cancelTask(String(args?.task_id ?? "")));
1504
- }
1505
-
1506
- case "kill_task": {
1507
- return toResult(killTask(String(args?.task_id ?? "")));
1508
- }
1509
-
1510
- case "retry_task": {
1511
- return toResult(retryTask(String(args?.task_id ?? "")));
1512
- }
1513
-
1514
- case "get_task_stdout_tail": {
1515
- return toResult(getTaskStdoutTail(
1516
- String(args?.task_id ?? ""),
1517
- args?.lines ? Number(args.lines) : undefined
1518
- ));
1519
- }
1520
-
1521
- case "get_task_log_tail": {
1522
- return toResult(getTaskLogTail(
1523
- String(args?.task_id ?? ""),
1524
- (args?.file as "stdout" | "stderr" | "test" | "verify") || "stdout",
1525
- {
1526
- lines: args?.lines ? Number(args.lines) : undefined,
1527
- redact: args?.redact !== undefined ? Boolean(args.redact) : undefined,
1528
- }
1529
- ));
1530
- }
1531
-
1532
- case "get_task_progress": {
1533
- return toResult(getTaskProgress(String(args?.task_id ?? "")));
1534
- }
1535
-
1536
- case "wait_for_task": {
1537
- const waitSeconds = normalizeWaitSeconds(args);
1538
- return toResult(await waitForTask(
1539
- String(args?.task_id ?? ""),
1540
- waitSeconds
1541
- ));
1542
- }
1543
-
1544
- case "get_task_summary": {
1545
- return toResult(getTaskSummary(String(args?.task_id ?? ""), {
1546
- view: normalizeSummaryView(args?.view),
1547
- max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
1548
- }));
1549
- }
1550
-
1551
- case "audit_task": {
1552
- return toResult(auditTask(String(args?.task_id ?? "")));
1553
- }
1554
-
1555
- case "safe_status": {
1556
- return toResult(safeStatus(String(args?.task_id ?? "")));
1557
- }
1558
-
1559
- case "safe_result": {
1560
- return toResult(safeResult(String(args?.task_id ?? ""), {
1561
- max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
1562
- }));
1563
- }
1564
-
1565
- case "safe_audit": {
1566
- return toResult(safeAudit(String(args?.task_id ?? ""), {
1567
- max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
1568
- }));
1569
- }
1570
-
1571
- case "safe_test_summary": {
1572
- return toResult(safeTestSummary(String(args?.task_id ?? "")));
1573
- }
1574
-
1575
- case "safe_diff_summary": {
1576
- return toResult(safeDiffSummary(String(args?.task_id ?? ""), {
1577
- max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
1578
- }));
1579
- }
1580
- case "diagnose_task": {
1581
- return toResult(diagnoseTask({
1582
- task_id: String(args?.task_id ?? ""),
1583
- include_logs: args?.include_logs !== undefined ? Boolean(args.include_logs) : undefined,
1584
- }));
1585
- }
1586
-
1587
- case "reconcile_tasks": {
1588
- return toResult(reconcileTasks({
1589
- mode: args?.mode === "safe_fix" ? "safe_fix" : "report_only",
1590
- max_age_minutes: args?.max_age_minutes !== undefined ? Number(args.max_age_minutes) : undefined,
1591
- include_done_candidates: args?.include_done_candidates !== undefined ? Boolean(args.include_done_candidates) : undefined,
1592
- }));
1593
- }
1594
-
1595
- case "discover_tools": {
1596
- const profile = args?.profile === "full" || args?.profile === "chatgpt_core" || args?.profile === "chatgpt_direct" || args?.profile === "chatgpt_search"
1597
- ? args.profile : undefined;
1598
- const mode = args?.mode === "delegate" || args?.mode === "direct" || args?.mode === "audit" || args?.mode === "release" || args?.mode === "diagnostic"
1599
- ? args.mode : undefined;
1600
- const riskCeiling = ["readonly", "workspace_read_sensitive", "workspace_write", "command", "release", "credential_sensitive"]
1601
- .includes(String(args?.riskCeiling ?? "")) ? String(args?.riskCeiling) as any : undefined;
1602
- return toResult(discoverTools({
1603
- query: String(args?.query ?? ""),
1604
- profile,
1605
- mode,
1606
- maxResults: args?.maxResults !== undefined ? Number(args.maxResults) : undefined,
1607
- riskCeiling,
1608
- includeHighRisk: args?.includeHighRisk !== undefined ? Boolean(args.includeHighRisk) : undefined,
1609
- }, getToolDefs()));
1610
- }
1611
-
1612
- case "explain_tool": {
1613
- return toResult(explainTool({
1614
- name: String(args?.name ?? ""),
1615
- includeSchema: args?.includeSchema !== undefined ? Boolean(args.includeSchema) : undefined,
1616
- }, getToolDefs()));
1617
- }
1618
-
1619
- case "invoke_discovered_tool": {
1620
- const profile = resolveToolProfile(getConfig().toolProfile);
1621
- const result = await invokeDiscoveredTool({
1622
- toolName: String(args?.toolName ?? ""),
1623
- arguments: (args?.arguments && typeof args.arguments === "object" ? args.arguments : {}) as Record<string, unknown>,
1624
- discoveryToken: String(args?.discoveryToken ?? ""),
1625
- assessmentId: args?.assessmentId ? String(args.assessmentId) : undefined,
1626
- }, {
1627
- tools: getToolDefs(),
1628
- profile,
1629
- dispatch: async (name, dispatchArgs) => {
1630
- return handleToolCall(name, dispatchArgs);
1631
- },
1632
- });
1633
- return toResult(result);
1634
- }
1635
-
1636
- case "run_task": {
1637
- const config = getConfig();
1638
- if ((config as any).enableRunTaskTool !== true) {
1639
- throw new Error(
1640
- "run_task is disabled. Set enableRunTaskTool: true in config to enable. Prefer using the local watcher (npm run watch)."
1641
- );
1642
- }
1643
- const taskId = String(args?.task_id ?? "");
1644
- const result = await runTask(taskId);
1645
- return toResult(result);
1646
- }
1647
-
1648
- case "create_direct_session": {
1649
- guardDirectProfileEnabled();
1650
- return toResult(createDirectSession({
1651
- repo_path: String(args?.repo_path ?? ""),
1652
- title: args?.title ? String(args.title) : undefined,
1653
- }));
1654
- }
1655
-
1656
- case "search_workspace": {
1657
- guardDirectProfileEnabled();
1658
- return toResult(searchWorkspace({
1659
- session_id: String(args?.session_id ?? ""),
1660
- query: String(args?.query ?? ""),
1661
- max_results: args?.max_results ? Number(args.max_results) : undefined,
1662
- case_sensitive: args?.case_sensitive !== undefined ? Boolean(args.case_sensitive) : undefined,
1663
- max_preview_chars: args?.max_preview_chars ? Number(args.max_preview_chars) : undefined,
1664
- include_globs: Array.isArray(args?.include_globs) ? args.include_globs.map(String) : undefined,
1665
- }));
1666
- }
1667
-
1668
- case "apply_patch": {
1669
- guardDirectProfileEnabled();
1670
- return toResult(applyPatch({
1671
- session_id: String(args?.session_id ?? ""),
1672
- path: String(args?.path ?? ""),
1673
- expected_sha256: String(args?.expected_sha256 ?? ""),
1674
- operations: Array.isArray(args?.operations) ? args.operations as any : [],
1675
- }));
1676
- }
1677
-
1678
- case "run_verification": {
1679
- guardDirectProfileEnabled();
1680
- return toResult(await runVerification({
1681
- session_id: String(args?.session_id ?? ""),
1682
- command: String(args?.command ?? ""),
1683
- timeout_seconds: args?.timeout_seconds ? Number(args.timeout_seconds) : undefined,
1684
- }));
1685
- }
1686
-
1687
- case "run_direct_verification_bundle": {
1688
- guardDirectProfileEnabled();
1689
- return toResult(await runDirectVerificationBundle({
1690
- session_id: String(args?.session_id ?? ""),
1691
- commands: Array.isArray(args?.commands) ? args.commands.map((command) => String(command)) : [],
1692
- timeout_seconds: args?.timeout_seconds ? Number(args.timeout_seconds) : undefined,
1693
- }));
1694
- }
1695
-
1696
- case "finalize_direct_session": {
1697
- guardDirectProfileEnabled();
1698
- return toResult(finalizeDirectSession({
1699
- session_id: String(args?.session_id ?? ""),
1700
- }));
1701
- }
1702
-
1703
- case "audit_session": {
1704
- guardDirectProfileEnabled();
1705
- return toResult(auditSession({
1706
- session_id: String(args?.session_id ?? ""),
1707
- }));
1708
- }
1709
-
1710
- case "safe_direct_summary": {
1711
- guardDirectProfileEnabled();
1712
- return toResult(safeDirectSummary(String(args?.session_id ?? ""), {
1713
- max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
1714
- }));
1715
- }
1716
-
1717
- case "safe_finalize_direct_session": {
1718
- guardDirectProfileEnabled();
1719
- return toResult(safeFinalizeDirectSession(String(args?.session_id ?? ""), {
1720
- max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
1721
- }));
1722
- }
1723
-
1724
- case "safe_audit_direct_session": {
1725
- guardDirectProfileEnabled();
1726
- return toResult(safeAuditDirectSession(String(args?.session_id ?? ""), {
1727
- max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
1728
- }));
1729
- }
1730
- case "sync_file": {
1731
- guardDirectProfileEnabled();
1732
- return toResult(syncFile(
1733
- String(args?.session_id ?? ""),
1734
- String(args?.source_path ?? ""),
1735
- String(args?.target_path ?? ""),
1736
- {
1737
- expected_source_sha256: args?.expected_source_sha256 ? String(args.expected_source_sha256) : undefined,
1738
- expected_target_sha256: args?.expected_target_sha256 ? String(args.expected_target_sha256) : undefined,
1739
- }
1740
- ));
1741
- }
1742
-
1743
- case "create_goal": {
1744
- return toResult(createGoal(
1745
- String(args?.repo_path ?? ""),
1746
- String(args?.title ?? ""),
1747
- String(args?.goal_description ?? "")
1748
- ));
1749
- }
1750
-
1751
- case "list_goals": {
1752
- return toResult({ goals: listGoals() });
1753
- }
1754
-
1755
- case "read_goal": {
1756
- return toResult(readGoal(String(args?.goal_id ?? "")));
1757
- }
1758
-
1759
- case "create_subgoal_task": {
1760
- return toResult(createSubgoalTask({
1761
- goal_id: String(args?.goal_id ?? ""),
1762
- subgoal_title: String(args?.subgoal_title ?? ""),
1763
- depends_on: Array.isArray(args?.depends_on) ? args.depends_on.map(String) : undefined,
1764
- plan_id: args?.plan_id ? String(args.plan_id) : undefined,
1765
- inline_plan: args?.inline_plan ? String(args.inline_plan) : undefined,
1766
- plan_title: args?.plan_title ? String(args.plan_title) : undefined,
1767
- template: args?.template ? String(args.template) as any : undefined,
1768
- goal: args?.goal ? String(args.goal) : undefined,
1769
- agent: args?.agent ? String(args.agent) : undefined,
1770
- repo_path: String(args?.repo_path ?? ""),
1771
- test_command: args?.test_command ? String(args.test_command) : undefined,
1772
- verify_commands: Array.isArray(args?.verify_commands) ? args.verify_commands.map(String) : undefined,
1773
- timeout_seconds: args?.timeout_seconds ? Number(args.timeout_seconds) : undefined,
1774
- scope: Array.isArray(args?.scope) ? args.scope.map(String) : undefined,
1775
- forbidden: Array.isArray(args?.forbidden) ? args.forbidden.map(String) : undefined,
1776
- verification: Array.isArray(args?.verification) ? args.verification.map(String) : undefined,
1777
- done_evidence: Array.isArray(args?.done_evidence) ? args.done_evidence.map(String) : undefined,
1778
- isolate_worktree: args?.isolate_worktree === undefined ? undefined : Boolean(args.isolate_worktree),
1779
- }));
1780
- }
1781
-
1782
- case "accept_subgoal": {
1783
- return toResult(acceptSubgoal(
1784
- String(args?.goal_id ?? ""),
1785
- String(args?.subgoal_id ?? "")
1786
- ));
1787
- }
1788
-
1789
- case "reject_subgoal": {
1790
- return toResult(rejectSubgoal(
1791
- String(args?.goal_id ?? ""),
1792
- String(args?.subgoal_id ?? ""),
1793
- String(args?.reason ?? "")
1794
- ));
1795
- }
1796
-
1797
- case "suggest_next_subgoal": {
1798
- const goalStatus = readGoalStatus(String(args?.goal_id ?? ""));
1799
- return toResult(suggestNextSubgoal(goalStatus));
1800
- }
1801
-
1802
- case "summarize_goal_progress": {
1803
- return toResult(summarizeGoalProgress(String(args?.goal_id ?? "")));
1804
- }
1805
-
1806
- case "export_handoff": {
1807
- const goalId = String(args?.goal_id ?? "");
1808
- const goalStatus = readGoalStatus(goalId);
1809
- return toResult(exportHandoff(goalId, goalStatus));
1810
- }
1811
-
1812
- case "check_release_gate": {
1813
- return toResult(await checkReleaseGate({
1814
- repo_path: String(args?.repo_path ?? ""),
1815
- target_stage: String(args?.target_stage ?? "local_ready") as any,
1816
- package_name: args?.package_name ? String(args.package_name) : undefined,
1817
- version: args?.version ? String(args.version) : undefined,
1818
- github_repo: args?.github_repo ? String(args.github_repo) : undefined,
1819
- branch: args?.branch ? String(args.branch) : undefined,
1820
- }));
1821
- }
1822
-
1823
- case "release_check": {
1824
- return toResult(await releaseCheck({
1825
- repo_path: String(args?.repo_path ?? ""),
1826
- target_stage: String(args?.target_stage ?? "local_ready") as any,
1827
- package_name: args?.package_name ? String(args.package_name) : undefined,
1828
- version: args?.version ? String(args.version) : undefined,
1829
- github_repo: args?.github_repo ? String(args.github_repo) : undefined,
1830
- branch: args?.branch ? String(args.branch) : undefined,
1831
- }));
1832
- }
1833
-
1834
- case "release_prepare": {
1835
- return toResult(releasePrepare({
1836
- repo_path: String(args?.repo_path ?? ""),
1837
- required_commands: Array.isArray(args?.required_commands)
1838
- ? args.required_commands.map(String)
1839
- : undefined,
1840
- timeout_seconds: args?.timeout_seconds !== undefined ? Number(args.timeout_seconds) : undefined,
1841
- }));
1842
- }
1843
-
1844
- case "release_verify": {
1845
- return toResult(await releaseVerify({
1846
- repo_path: String(args?.repo_path ?? ""),
1847
- package_name: args?.package_name ? String(args.package_name) : undefined,
1848
- version: args?.version ? String(args.version) : undefined,
1849
- github_repo: args?.github_repo ? String(args.github_repo) : undefined,
1850
- branch: args?.branch ? String(args.branch) : undefined,
1851
- }));
1852
- }
1853
-
1854
- case "release_cleanup": {
1855
- return toResult(releaseCleanup({
1856
- repo_path: String(args?.repo_path ?? ""),
1857
- dry_run: args?.dry_run !== undefined ? Boolean(args.dry_run) : undefined,
1858
- patterns: Array.isArray(args?.patterns) ? args.patterns.map(String) : undefined,
1859
- }));
1860
- }
1861
-
1862
- case "merge_worktree": {
1863
- return toResult(mergeWorktreeTool({
1864
- worktree_id: String(args?.worktree_id ?? ""),
1865
- repo_path: String(args?.repo_path ?? ""),
1866
- }));
1867
- }
1868
-
1869
- case "discard_worktree": {
1870
- return toResult(discardWorktreeTool({
1871
- worktree_id: String(args?.worktree_id ?? ""),
1872
- repo_path: String(args?.repo_path ?? ""),
1873
- }));
1874
- }
1875
-
1876
- default:
1877
- throw new Error(`Unknown tool: ${name}`);
1878
- }
1879
- }
1880
-
1881
- function normalizeWaitSeconds(args: Record<string, unknown> | undefined): number | undefined {
1882
- const legacy = args?.wait_seconds;
1883
- const preferred = args?.timeout_seconds;
1884
- if (legacy !== undefined && preferred !== undefined && Number(legacy) !== Number(preferred)) {
1885
- throw new Error("wait_seconds and timeout_seconds must match when both are supplied.");
1326
+ const handler = dispatchMap[name];
1327
+ if (!handler) {
1328
+ throw new Error(`Unknown tool: ${name}`);
1886
1329
  }
1887
- const value = preferred ?? legacy;
1888
- return value === undefined ? undefined : Number(value);
1889
- }
1890
-
1891
- function normalizeSummaryView(value: unknown): "compact" | "standard" {
1892
- if (value === undefined) return "standard";
1893
- if (value !== "compact" && value !== "standard") {
1894
- throw new Error('view must be "compact" or "standard".');
1895
- }
1896
- return value;
1897
- }
1898
-
1899
- function toResult(data: unknown) {
1900
- return {
1901
- content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
1902
- };
1330
+ return handler(args);
1903
1331
  }
1904
1332
 
1905
1333
  // ── Register on MCP Server ────────────────────────────────────────
@@ -1911,6 +1339,18 @@ export function registerTools(server: Server) {
1911
1339
  const activeTools = getToolDefs();
1912
1340
  const activeNames = new Set(activeTools.map((tool) => tool.name));
1913
1341
 
1342
+ // Verify every registered tool has a dispatch handler.
1343
+ // The dispatch map may contain extra handlers for tools exposed under
1344
+ // other profiles (e.g. get_plan is only in the "full" profile), so we
1345
+ // only check the subset direction: every active tool must have a handler.
1346
+ for (const tool of activeTools) {
1347
+ if (!dispatchMap[tool.name]) {
1348
+ throw new Error(
1349
+ `Dispatch map is missing handler for registered tool "${tool.name}".`,
1350
+ );
1351
+ }
1352
+ }
1353
+
1914
1354
  server.setRequestHandler(ListToolsRequestSchema, async () => {
1915
1355
  const catalog = getLastToolCatalogSnapshot();
1916
1356
  return {