patchwarden 0.6.4 → 1.5.0

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 (254) hide show
  1. package/README.en.md +54 -20
  2. package/README.md +48 -21
  3. package/dist/agents/agentRouter.d.ts +40 -0
  4. package/dist/agents/agentRouter.js +95 -0
  5. package/dist/config.d.ts +1 -1
  6. package/dist/config.js +6 -2
  7. package/dist/controlCenter.js +212 -3
  8. package/dist/direct/directSessionStore.d.ts +2 -0
  9. package/dist/direct/directVerification.js +7 -0
  10. package/dist/doctor.js +32 -1
  11. package/dist/goal/acceptanceEngine.d.ts +62 -0
  12. package/dist/goal/acceptanceEngine.js +103 -0
  13. package/dist/goal/acceptanceTemplate.d.ts +10 -0
  14. package/dist/goal/acceptanceTemplate.js +104 -0
  15. package/dist/goal/goalGraph.d.ts +58 -0
  16. package/dist/goal/goalGraph.js +189 -0
  17. package/dist/goal/goalProgress.d.ts +81 -0
  18. package/dist/goal/goalProgress.js +216 -0
  19. package/dist/goal/goalStatus.d.ts +60 -0
  20. package/dist/goal/goalStatus.js +137 -0
  21. package/dist/goal/goalStore.d.ts +79 -0
  22. package/dist/goal/goalStore.js +211 -0
  23. package/dist/goal/handoffExport.d.ts +34 -0
  24. package/dist/goal/handoffExport.js +183 -0
  25. package/dist/goal/subgoalSync.d.ts +40 -0
  26. package/dist/goal/subgoalSync.js +72 -0
  27. package/dist/goal/worktreeManager.d.ts +96 -0
  28. package/dist/goal/worktreeManager.js +292 -0
  29. package/dist/logging.d.ts +44 -0
  30. package/dist/logging.js +68 -0
  31. package/dist/policy/projectPolicy.d.ts +55 -0
  32. package/dist/policy/projectPolicy.js +286 -0
  33. package/dist/release/releaseGate.d.ts +99 -0
  34. package/dist/release/releaseGate.js +475 -0
  35. package/dist/runner/postTaskCleanup.d.ts +13 -0
  36. package/dist/runner/postTaskCleanup.js +147 -0
  37. package/dist/runner/runTask.js +50 -10
  38. package/dist/security/discoveryTokenStore.d.ts +62 -0
  39. package/dist/security/discoveryTokenStore.js +100 -0
  40. package/dist/security/toolInvocationGuard.d.ts +35 -0
  41. package/dist/security/toolInvocationGuard.js +153 -0
  42. package/dist/smoke-test.js +18 -10
  43. package/dist/taskRuntime.d.ts +17 -0
  44. package/dist/test/unit/acceptance-engine.test.d.ts +1 -0
  45. package/dist/test/unit/acceptance-engine.test.js +228 -0
  46. package/dist/test/unit/agent-router.test.d.ts +1 -0
  47. package/dist/test/unit/agent-router.test.js +287 -0
  48. package/dist/test/unit/audit-checks.test.d.ts +1 -0
  49. package/dist/test/unit/audit-checks.test.js +350 -0
  50. package/dist/test/unit/diagnose-task.test.d.ts +1 -0
  51. package/dist/test/unit/diagnose-task.test.js +457 -0
  52. package/dist/test/unit/discovery-token-store.test.d.ts +1 -0
  53. package/dist/test/unit/discovery-token-store.test.js +139 -0
  54. package/dist/test/unit/evidence-pack.test.d.ts +1 -0
  55. package/dist/test/unit/evidence-pack.test.js +130 -0
  56. package/dist/test/unit/goal-graph.test.d.ts +1 -0
  57. package/dist/test/unit/goal-graph.test.js +298 -0
  58. package/dist/test/unit/goal-progress.test.d.ts +1 -0
  59. package/dist/test/unit/goal-progress.test.js +381 -0
  60. package/dist/test/unit/goal-status.test.d.ts +1 -0
  61. package/dist/test/unit/goal-status.test.js +215 -0
  62. package/dist/test/unit/goal-store.test.d.ts +1 -0
  63. package/dist/test/unit/goal-store.test.js +253 -0
  64. package/dist/test/unit/goal-subgoal-task.test.d.ts +1 -0
  65. package/dist/test/unit/goal-subgoal-task.test.js +55 -0
  66. package/dist/test/unit/goal-tools-registry.test.d.ts +1 -0
  67. package/dist/test/unit/goal-tools-registry.test.js +190 -0
  68. package/dist/test/unit/handoff-export.test.d.ts +1 -0
  69. package/dist/test/unit/handoff-export.test.js +263 -0
  70. package/dist/test/unit/invoke-discovered-tool.test.d.ts +1 -0
  71. package/dist/test/unit/invoke-discovered-tool.test.js +167 -0
  72. package/dist/test/unit/logging.test.js +127 -5
  73. package/dist/test/unit/post-task-cleanup.test.d.ts +1 -0
  74. package/dist/test/unit/post-task-cleanup.test.js +48 -0
  75. package/dist/test/unit/project-policy-release-mode.test.d.ts +1 -0
  76. package/dist/test/unit/project-policy-release-mode.test.js +125 -0
  77. package/dist/test/unit/reconcile-tasks.test.d.ts +1 -0
  78. package/dist/test/unit/reconcile-tasks.test.js +456 -0
  79. package/dist/test/unit/release-gate.test.d.ts +1 -0
  80. package/dist/test/unit/release-gate.test.js +242 -0
  81. package/dist/test/unit/run-task-loop.test.d.ts +1 -0
  82. package/dist/test/unit/run-task-loop.test.js +380 -0
  83. package/dist/test/unit/safe-views.test.d.ts +1 -0
  84. package/dist/test/unit/safe-views.test.js +171 -0
  85. package/dist/test/unit/schema-drift-check.test.d.ts +1 -0
  86. package/dist/test/unit/schema-drift-check.test.js +176 -0
  87. package/dist/test/unit/subgoal-sync.test.d.ts +1 -0
  88. package/dist/test/unit/subgoal-sync.test.js +183 -0
  89. package/dist/test/unit/tool-invocation-guard.test.d.ts +1 -0
  90. package/dist/test/unit/tool-invocation-guard.test.js +432 -0
  91. package/dist/test/unit/tool-usage-stats.test.d.ts +1 -0
  92. package/dist/test/unit/tool-usage-stats.test.js +300 -0
  93. package/dist/test/unit/toolSearch.test.d.ts +1 -0
  94. package/dist/test/unit/toolSearch.test.js +571 -0
  95. package/dist/test/unit/worktree-manager.test.d.ts +1 -0
  96. package/dist/test/unit/worktree-manager.test.js +176 -0
  97. package/dist/tools/auditTask.d.ts +103 -1
  98. package/dist/tools/auditTask.js +461 -8
  99. package/dist/tools/cancelTask.js +1 -1
  100. package/dist/tools/checkReleaseGate.d.ts +21 -0
  101. package/dist/tools/checkReleaseGate.js +22 -0
  102. package/dist/tools/createTask.d.ts +18 -2
  103. package/dist/tools/createTask.js +29 -1
  104. package/dist/tools/diagnoseTask.d.ts +45 -0
  105. package/dist/tools/diagnoseTask.js +347 -0
  106. package/dist/tools/discardWorktree.d.ts +24 -0
  107. package/dist/tools/discardWorktree.js +27 -0
  108. package/dist/tools/discoverTools.d.ts +11 -0
  109. package/dist/tools/discoverTools.js +39 -0
  110. package/dist/tools/evidencePack.d.ts +39 -0
  111. package/dist/tools/evidencePack.js +168 -0
  112. package/dist/tools/explainTool.d.ts +11 -0
  113. package/dist/tools/explainTool.js +21 -0
  114. package/dist/tools/getTaskSummary.js +2 -1
  115. package/dist/tools/goalSubgoalTask.d.ts +51 -0
  116. package/dist/tools/goalSubgoalTask.js +110 -0
  117. package/dist/tools/healthCheck.d.ts +1 -0
  118. package/dist/tools/healthCheck.js +5 -1
  119. package/dist/tools/invokeDiscoveredTool.d.ts +37 -0
  120. package/dist/tools/invokeDiscoveredTool.js +112 -0
  121. package/dist/tools/listTasks.d.ts +3 -1
  122. package/dist/tools/listTasks.js +14 -1
  123. package/dist/tools/mergeWorktree.d.ts +24 -0
  124. package/dist/tools/mergeWorktree.js +27 -0
  125. package/dist/tools/recommendAgentForTask.d.ts +19 -0
  126. package/dist/tools/recommendAgentForTask.js +56 -0
  127. package/dist/tools/reconcileTasks.d.ts +41 -0
  128. package/dist/tools/reconcileTasks.js +352 -0
  129. package/dist/tools/registry.js +926 -2
  130. package/dist/tools/releaseMode.d.ts +50 -0
  131. package/dist/tools/releaseMode.js +370 -0
  132. package/dist/tools/runDirectVerificationBundle.d.ts +26 -0
  133. package/dist/tools/runDirectVerificationBundle.js +64 -0
  134. package/dist/tools/runTaskLoop.d.ts +57 -0
  135. package/dist/tools/runTaskLoop.js +417 -0
  136. package/dist/tools/runVerification.d.ts +4 -0
  137. package/dist/tools/runVerification.js +4 -0
  138. package/dist/tools/safeStatus.d.ts +31 -1
  139. package/dist/tools/safeStatus.js +43 -1
  140. package/dist/tools/safeViews.d.ts +262 -0
  141. package/dist/tools/safeViews.js +252 -0
  142. package/dist/tools/schemaDriftCheck.d.ts +46 -0
  143. package/dist/tools/schemaDriftCheck.js +80 -0
  144. package/dist/tools/taskLineage.d.ts +91 -0
  145. package/dist/tools/taskLineage.js +175 -0
  146. package/dist/tools/toolCatalog.d.ts +4 -3
  147. package/dist/tools/toolCatalog.js +39 -11
  148. package/dist/tools/toolRegistry.d.ts +61 -0
  149. package/dist/tools/toolRegistry.js +834 -0
  150. package/dist/tools/toolSearch.d.ts +110 -0
  151. package/dist/tools/toolSearch.js +331 -0
  152. package/dist/tools/toolUsageStats.d.ts +41 -0
  153. package/dist/tools/toolUsageStats.js +116 -0
  154. package/dist/tools/waitForTask.js +1 -0
  155. package/dist/version.d.ts +2 -2
  156. package/dist/version.js +2 -2
  157. package/dist/watcherStatus.d.ts +29 -0
  158. package/dist/watcherStatus.js +92 -1
  159. package/docs/chatgpt-usage.md +31 -0
  160. package/docs/control-center/README.md +9 -0
  161. package/package.json +3 -2
  162. package/scripts/checks/control-center-smoke.js +87 -0
  163. package/scripts/checks/control-smoke.js +2 -2
  164. package/scripts/checks/doctor-smoke.js +1 -0
  165. package/scripts/checks/lifecycle-smoke.js +13 -9
  166. package/scripts/checks/mcp-manifest-check.js +65 -6
  167. package/scripts/checks/mcp-smoke.js +60 -9
  168. package/scripts/checks/watcher-supervisor-smoke.js +2 -2
  169. package/src/agents/agentRouter.ts +149 -0
  170. package/src/config.ts +9 -3
  171. package/src/controlCenter.ts +215 -4
  172. package/src/direct/directSessionStore.ts +2 -0
  173. package/src/direct/directVerification.ts +7 -0
  174. package/src/doctor.ts +36 -1
  175. package/src/goal/acceptanceEngine.ts +160 -0
  176. package/src/goal/acceptanceTemplate.ts +121 -0
  177. package/src/goal/goalGraph.ts +225 -0
  178. package/src/goal/goalProgress.ts +301 -0
  179. package/src/goal/goalStatus.ts +234 -0
  180. package/src/goal/goalStore.ts +306 -0
  181. package/src/goal/handoffExport.ts +211 -0
  182. package/src/goal/subgoalSync.ts +82 -0
  183. package/src/goal/worktreeManager.ts +404 -0
  184. package/src/logging.ts +91 -0
  185. package/src/policy/projectPolicy.ts +344 -0
  186. package/src/release/releaseGate.ts +567 -0
  187. package/src/runner/postTaskCleanup.ts +154 -0
  188. package/src/runner/runTask.ts +49 -10
  189. package/src/security/discoveryTokenStore.ts +157 -0
  190. package/src/security/toolInvocationGuard.ts +251 -0
  191. package/src/smoke-test.ts +15 -7
  192. package/src/taskRuntime.ts +17 -0
  193. package/src/test/unit/acceptance-engine.test.ts +261 -0
  194. package/src/test/unit/agent-router.test.ts +342 -0
  195. package/src/test/unit/audit-checks.test.ts +567 -0
  196. package/src/test/unit/diagnose-task.test.ts +544 -0
  197. package/src/test/unit/discovery-token-store.test.ts +181 -0
  198. package/src/test/unit/evidence-pack.test.ts +142 -0
  199. package/src/test/unit/goal-graph.test.ts +347 -0
  200. package/src/test/unit/goal-progress.test.ts +538 -0
  201. package/src/test/unit/goal-status.test.ts +270 -0
  202. package/src/test/unit/goal-store.test.ts +318 -0
  203. package/src/test/unit/goal-subgoal-task.test.ts +72 -0
  204. package/src/test/unit/goal-tools-registry.test.ts +243 -0
  205. package/src/test/unit/handoff-export.test.ts +295 -0
  206. package/src/test/unit/invoke-discovered-tool.test.ts +216 -0
  207. package/src/test/unit/logging.test.ts +177 -5
  208. package/src/test/unit/post-task-cleanup.test.ts +53 -0
  209. package/src/test/unit/project-policy-release-mode.test.ts +156 -0
  210. package/src/test/unit/reconcile-tasks.test.ts +551 -0
  211. package/src/test/unit/release-gate.test.ts +314 -0
  212. package/src/test/unit/run-task-loop.test.ts +425 -0
  213. package/src/test/unit/safe-views.test.ts +184 -0
  214. package/src/test/unit/schema-drift-check.test.ts +260 -0
  215. package/src/test/unit/subgoal-sync.test.ts +236 -0
  216. package/src/test/unit/tool-invocation-guard.test.ts +542 -0
  217. package/src/test/unit/tool-usage-stats.test.ts +384 -0
  218. package/src/test/unit/toolSearch.test.ts +756 -0
  219. package/src/test/unit/worktree-manager.test.ts +247 -0
  220. package/src/tools/auditTask.ts +831 -402
  221. package/src/tools/cancelTask.ts +1 -1
  222. package/src/tools/checkReleaseGate.ts +45 -0
  223. package/src/tools/createTask.ts +64 -6
  224. package/src/tools/diagnoseTask.ts +460 -0
  225. package/src/tools/discardWorktree.ts +42 -0
  226. package/src/tools/discoverTools.ts +51 -0
  227. package/src/tools/evidencePack.ts +205 -0
  228. package/src/tools/explainTool.ts +26 -0
  229. package/src/tools/getTaskSummary.ts +2 -1
  230. package/src/tools/goalSubgoalTask.ts +170 -0
  231. package/src/tools/healthCheck.ts +4 -1
  232. package/src/tools/invokeDiscoveredTool.ts +163 -0
  233. package/src/tools/listTasks.ts +16 -2
  234. package/src/tools/listWorkspace.ts +71 -71
  235. package/src/tools/mergeWorktree.ts +42 -0
  236. package/src/tools/recommendAgentForTask.ts +79 -0
  237. package/src/tools/reconcileTasks.ts +464 -0
  238. package/src/tools/registry.ts +1093 -73
  239. package/src/tools/releaseMode.ts +450 -0
  240. package/src/tools/runDirectVerificationBundle.ts +98 -0
  241. package/src/tools/runTaskLoop.ts +526 -0
  242. package/src/tools/runVerification.ts +8 -0
  243. package/src/tools/safeStatus.ts +73 -2
  244. package/src/tools/safeViews.ts +273 -0
  245. package/src/tools/schemaDriftCheck.ts +120 -0
  246. package/src/tools/taskLineage.ts +300 -0
  247. package/src/tools/toolCatalog.ts +40 -11
  248. package/src/tools/toolRegistry.ts +896 -0
  249. package/src/tools/toolSearch.ts +464 -0
  250. package/src/tools/toolUsageStats.ts +151 -0
  251. package/src/tools/waitForTask.ts +1 -0
  252. package/src/version.ts +2 -2
  253. package/src/watcherStatus.ts +97 -1
  254. package/ui/pages/dashboard.html +143 -2
@@ -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
  */
@@ -26,18 +26,52 @@ import { listAgents } from "../tools/listAgents.js";
26
26
  import { healthCheck } from "../tools/healthCheck.js";
27
27
  import { getTaskSummary } from "../tools/getTaskSummary.js";
28
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";
29
33
  import { errorPayload, PatchWardenError } from "../errors.js";
30
34
  import { auditTask } from "../tools/auditTask.js";
31
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";
32
50
  import { logger } from "../logging.js";
33
51
  import { runTask } from "../runner/runTask.js";
34
52
  import { createDirectSession } from "../tools/createDirectSession.js";
35
53
  import { searchWorkspace } from "../tools/searchWorkspace.js";
36
54
  import { applyPatch } from "../tools/applyPatch.js";
37
55
  import { runVerification } from "../tools/runVerification.js";
56
+ import { runDirectVerificationBundle } from "../tools/runDirectVerificationBundle.js";
38
57
  import { finalizeDirectSession } from "../tools/finalizeDirectSession.js";
39
58
  import { auditSession } from "../tools/auditSession.js";
40
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";
41
75
  import { TASK_TEMPLATE_NAMES } from "./taskTemplates.js";
42
76
  import {
43
77
  buildToolCatalogSnapshot,
@@ -62,10 +96,12 @@ export interface ToolDef {
62
96
  export function getToolDefs(): ToolDef[] {
63
97
  const config = getConfig();
64
98
  const agentNames = Object.keys(config.agents).sort();
99
+ const routableAgentNames = [...agentNames, "auto"];
65
100
  const agentDescription = agentNames.length > 0
66
- ? `Configured local agent name. Available agents: ${agentNames.map((name) => JSON.stringify(name)).join(", ")}`
101
+ ? `Configured local agent name. Available agents: ${agentNames.map((name) => JSON.stringify(name)).join(", ")}. run_task_loop also accepts "auto" for bounded routing.`
67
102
  : "Configured local agent name. No agents are currently configured.";
68
103
  const testCommands = getAllConfiguredTestCommands(config).sort();
104
+ const directCommands = getAllConfiguredDirectCommands(config);
69
105
  const tools: ToolDef[] = [
70
106
  {
71
107
  name: "save_plan",
@@ -179,6 +215,178 @@ export function getToolDefs(): ToolDef[] {
179
215
  required: [],
180
216
  },
181
217
  },
218
+ {
219
+ name: "run_task_loop",
220
+ description:
221
+ "Run a guarded PatchWarden task loop by composing create_task, wait_for_task, safe summaries, and audit_task. It does not bypass the watcher, command allow-list, workspace confinement, or confirmation boundaries. Returns only bounded structured lineage and final status.",
222
+ inputSchema: {
223
+ type: "object",
224
+ properties: {
225
+ repo_path: {
226
+ type: "string",
227
+ description: "Required repository path inside workspaceRoot. No implicit workspace-root fallback is allowed.",
228
+ },
229
+ goal: { type: "string", description: "Task goal to execute through the guarded loop." },
230
+ verify_commands: {
231
+ type: "array",
232
+ minItems: 1,
233
+ maxItems: 20,
234
+ items: {
235
+ type: "string",
236
+ ...(testCommands.length > 0 ? { enum: testCommands } : {}),
237
+ },
238
+ description: "Exact-match verification commands. The loop reuses create_task validation and will not run commands outside the allow-list.",
239
+ },
240
+ agent: {
241
+ type: "string",
242
+ description: agentDescription,
243
+ ...(agentNames.length > 0 ? { enum: routableAgentNames } : {}),
244
+ },
245
+ template: {
246
+ type: "string",
247
+ enum: ["inspect_only", "feature_small", "release_check"],
248
+ default: "feature_small",
249
+ description: "Initial guarded task template. Follow-up repair tasks use fix_tests automatically when verification fails.",
250
+ },
251
+ max_iterations: {
252
+ type: "integer",
253
+ minimum: 1,
254
+ maximum: 5,
255
+ default: 3,
256
+ description: "Maximum total main/fix task attempts.",
257
+ },
258
+ task_timeout_seconds: {
259
+ type: "integer",
260
+ minimum: 1,
261
+ maximum: config.maxTaskTimeoutSeconds,
262
+ default: config.defaultTaskTimeoutSeconds,
263
+ description: `Per-task wait budget in seconds (default ${config.defaultTaskTimeoutSeconds}, max ${config.maxTaskTimeoutSeconds}).`,
264
+ },
265
+ auto_fix_tests: {
266
+ type: "boolean",
267
+ default: true,
268
+ description: "When true, create a fix_tests follow-up task after failed_verification until max_iterations is reached.",
269
+ },
270
+ auto_cleanup_artifacts: {
271
+ type: "boolean",
272
+ default: true,
273
+ description: "Records cleanup intent in lineage. Low-risk cleanup remains handled by runTask post-task cleanup.",
274
+ },
275
+ stop_on_high_risk: {
276
+ type: "boolean",
277
+ default: true,
278
+ description: "When true, stop immediately on non-policy high-risk audit evidence. Scope, policy, sensitive-path, release/publish, and confirmation boundaries always stop regardless of this flag.",
279
+ },
280
+ direct_verify: {
281
+ type: "boolean",
282
+ default: false,
283
+ description: "When true, run an independent Direct verification session after the guarded task/audit succeeds. Direct never patches files in this loop.",
284
+ },
285
+ direct_verify_commands: {
286
+ type: "array",
287
+ minItems: 1,
288
+ maxItems: 20,
289
+ items: {
290
+ type: "string",
291
+ ...(directCommands.length > 0 ? { enum: directCommands } : {}),
292
+ },
293
+ description: "Optional Direct verification commands. Defaults to verify_commands and still must pass the Direct command allow-list.",
294
+ },
295
+ direct_verify_timeout_seconds: {
296
+ type: "integer",
297
+ minimum: 1,
298
+ maximum: Math.min(config.maxTaskTimeoutSeconds, config.directSessionTtlSeconds),
299
+ default: 120,
300
+ description: "Per-command timeout for Direct verification.",
301
+ },
302
+ scope_files: {
303
+ type: "array",
304
+ maxItems: 50,
305
+ items: { type: "string" },
306
+ description: "Optional bounded file scope used only for agent routing hints.",
307
+ },
308
+ isolation_mode: {
309
+ type: "string",
310
+ enum: ["current_repo", "worktree"],
311
+ default: "current_repo",
312
+ description: "Use current_repo by default. worktree creates an isolated git worktree for the task but never auto-merges it.",
313
+ },
314
+ worktree_base_branch: {
315
+ type: "string",
316
+ description: "Optional base-branch label recorded in lineage. v1.5 does not auto-checkout or merge this branch.",
317
+ },
318
+ worktree_cleanup: {
319
+ type: "string",
320
+ enum: ["keep", "archive", "delete_ignored_only"],
321
+ default: "keep",
322
+ description: "Cleanup intent recorded in lineage. v1.5 keeps worktrees by default and does not auto-delete them.",
323
+ },
324
+ },
325
+ required: ["repo_path", "goal", "verify_commands"],
326
+ },
327
+ },
328
+ {
329
+ name: "recommend_agent_for_task",
330
+ description:
331
+ "Return a bounded read-only agent routing recommendation for a repo-scoped task. Does not start an agent, create a task, or read logs.",
332
+ inputSchema: {
333
+ type: "object",
334
+ properties: {
335
+ repo_path: { type: "string", description: "Repository path inside workspaceRoot." },
336
+ goal: { type: "string", description: "Task goal used for routing hints." },
337
+ scope_files: {
338
+ type: "array",
339
+ maxItems: 50,
340
+ items: { type: "string" },
341
+ description: "Optional bounded list of files or directories expected to be in scope.",
342
+ },
343
+ template: { type: "string", description: "Optional task template hint." },
344
+ risk_hint: { type: "string", description: "Optional compact risk hint text." },
345
+ },
346
+ required: ["repo_path", "goal"],
347
+ },
348
+ },
349
+ {
350
+ name: "get_task_lineage",
351
+ description:
352
+ "Read a bounded safe summary for a run_task_loop lineage. Does not return full logs, diffs, stdout, stderr, or markdown artifacts.",
353
+ inputSchema: {
354
+ type: "object",
355
+ properties: {
356
+ lineage_id: { type: "string", description: "Lineage ID returned by run_task_loop." },
357
+ max_items: { type: "integer", minimum: 1, maximum: 50, default: 8, description: "Maximum rounds/tasks/warnings to return." },
358
+ },
359
+ required: ["lineage_id"],
360
+ },
361
+ },
362
+ {
363
+ name: "export_task_evidence_pack",
364
+ description:
365
+ "Export a bounded evidence pack for a run_task_loop lineage. Writes evidence.json and EVIDENCE.md without stdout, stderr, full logs, full diff, or sensitive file contents.",
366
+ inputSchema: {
367
+ type: "object",
368
+ properties: {
369
+ lineage_id: { type: "string", description: "Lineage ID returned by run_task_loop." },
370
+ max_items: { type: "integer", minimum: 1, maximum: 50, default: 12, description: "Maximum rounds/tasks/warnings to include." },
371
+ },
372
+ required: ["lineage_id"],
373
+ },
374
+ },
375
+ {
376
+ name: "get_project_policy",
377
+ description:
378
+ "Read the bounded effective .patchwarden/project-policy.json summary for a repository. Missing policy returns safe defaults. Project policy never expands PatchWarden command allow-lists, workspace confinement, sensitive-path blocking, or watcher/audit boundaries.",
379
+ inputSchema: {
380
+ type: "object",
381
+ properties: {
382
+ repo_path: {
383
+ type: "string",
384
+ description: "Repository path inside workspaceRoot.",
385
+ },
386
+ },
387
+ required: ["repo_path"],
388
+ },
389
+ },
182
390
  {
183
391
  name: "get_task_status",
184
392
  description: "Check task status, execution phase, watcher health, pending reason, current command, timeout, and change evidence.",
@@ -264,176 +472,623 @@ export function getToolDefs(): ToolDef[] {
264
472
  description: "Optional Direct session ID. When provided, read scope is limited to the session's repo_path and sha256 is returned.",
265
473
  },
266
474
  },
267
- required: ["path"],
475
+ required: ["path"],
476
+ },
477
+ },
478
+ {
479
+ name: "list_tasks",
480
+ description:
481
+ "List recent tasks with status/repo/active filters plus watcher state and computed pending reasons.",
482
+ inputSchema: {
483
+ type: "object",
484
+ properties: {
485
+ status: {
486
+ type: "string",
487
+ description: "Filter by status: pending, running, done, failed, failed_verification, failed_scope_violation, failed_policy_violation, canceled",
488
+ },
489
+ limit: {
490
+ type: "number",
491
+ description: "Max tasks to return (default 20, max 100)",
492
+ },
493
+ repo_path: {
494
+ type: "string",
495
+ description: "Optional exact repo_path or resolved_repo_path filter.",
496
+ },
497
+ active_only: {
498
+ type: "boolean",
499
+ description: "When true, return only pending and running tasks.",
500
+ },
501
+ },
502
+ },
503
+ },
504
+ {
505
+ name: "cancel_task",
506
+ description:
507
+ "Request graceful cancellation. The runner that owns the child process performs termination; the MCP server never kills a PID read from task files.",
508
+ inputSchema: {
509
+ type: "object",
510
+ properties: {
511
+ task_id: { type: "string", description: "Task ID to cancel" },
512
+ },
513
+ required: ["task_id"],
514
+ },
515
+ },
516
+ {
517
+ name: "kill_task",
518
+ description:
519
+ "Request immediate termination of a pending or running task. The runner validates and kills only the child process it owns.",
520
+ inputSchema: {
521
+ type: "object",
522
+ properties: {
523
+ task_id: { type: "string", description: "Task ID to terminate" },
524
+ },
525
+ required: ["task_id"],
526
+ },
527
+ },
528
+ {
529
+ name: "retry_task",
530
+ description:
531
+ "Create a new task with the same plan, agent, repo_path, and test_command as an existing task. The original task is unchanged.",
532
+ inputSchema: {
533
+ type: "object",
534
+ properties: {
535
+ task_id: { type: "string", description: "Task ID to retry" },
536
+ },
537
+ required: ["task_id"],
538
+ },
539
+ },
540
+ {
541
+ name: "get_task_stdout_tail",
542
+ description:
543
+ "Read the last N lines of agent stdout/stderr. Reads from real-time stdout.log/stderr.log during execution, falls back to result.md after completion. Works on pending, running, and completed tasks. Default 80 lines.",
544
+ inputSchema: {
545
+ type: "object",
546
+ properties: {
547
+ task_id: { type: "string", description: "Task ID" },
548
+ lines: { type: "number", description: "Tail line count (default 80, max 200)" },
549
+ },
550
+ required: ["task_id"],
551
+ },
552
+ },
553
+ {
554
+ name: "get_task_log_tail",
555
+ description:
556
+ "Read the last N lines of a task log file (stdout/stderr/test/verify) with automatic secret redaction. Default 80 lines, max 200. Always returns tail only — never the full file. Use this instead of read_workspace_file to avoid triggering platform content filters on log output.",
557
+ inputSchema: {
558
+ type: "object",
559
+ properties: {
560
+ task_id: { type: "string", description: "Task ID" },
561
+ file: {
562
+ type: "string",
563
+ enum: ["stdout", "stderr", "test", "verify"],
564
+ description: "Log file to read: stdout (stdout.log), stderr (stderr.log), test (test.log), verify (verify.log)",
565
+ },
566
+ lines: { type: "number", description: "Tail line count (default 80, max 200)" },
567
+ redact: { type: "boolean", description: "Apply secret redaction (default true)" },
568
+ },
569
+ required: ["task_id", "file"],
570
+ },
571
+ },
572
+ {
573
+ name: "get_task_progress",
574
+ description:
575
+ "Read progress.md for task phases and the most recent heartbeat/current command.",
576
+ inputSchema: {
577
+ type: "object",
578
+ properties: {
579
+ task_id: { type: "string", description: "Task ID" },
580
+ },
581
+ required: ["task_id"],
582
+ },
583
+ },
584
+ {
585
+ name: "wait_for_task",
586
+ description:
587
+ "Long-poll a task for up to 30 seconds. If continuation_required=true, call wait_for_task again immediately and do not finish the assistant turn. Terminal responses include get_task_summary acceptance evidence.",
588
+ inputSchema: {
589
+ type: "object",
590
+ properties: {
591
+ task_id: { type: "string", description: "Task ID from create_task" },
592
+ wait_seconds: { type: "integer", minimum: 1, maximum: 30, default: 25 },
593
+ timeout_seconds: {
594
+ type: "integer",
595
+ minimum: 1,
596
+ maximum: 30,
597
+ description: "Preferred alias for wait_seconds. Maximum 30 seconds to stay within connector request limits.",
598
+ },
599
+ },
600
+ required: ["task_id"],
601
+ },
602
+ },
603
+ {
604
+ name: "get_task_summary",
605
+ description:
606
+ "Return structured acceptance evidence. Use view=compact first for bounded counts and risk excerpts; use standard only when full changed-file and log-tail detail is required.",
607
+ inputSchema: {
608
+ type: "object",
609
+ properties: {
610
+ task_id: { type: "string", description: "Task ID" },
611
+ view: { type: "string", enum: ["compact", "standard"], default: "standard", description: "Compact returns bounded acceptance evidence; standard preserves the full legacy summary." },
612
+ max_items: { type: "integer", minimum: 1, maximum: 50, default: 8, description: "Maximum entries per compact evidence group." },
613
+ },
614
+ required: ["task_id"],
615
+ },
616
+ },
617
+ {
618
+ name: "audit_task",
619
+ description:
620
+ "Independently audit a task's outputs. Verifies status, result.md, test.log, git.diff, repo_path consistency, cross-references agent claims with package.json scripts, and flags unverified release/publish claims. Evidence-backed failures, possible heuristic false positives, and manual-verification items are returned separately. Writes independent-review.md to the task directory.",
621
+ inputSchema: {
622
+ type: "object",
623
+ properties: {
624
+ task_id: { type: "string", description: "Task ID to audit" },
625
+ },
626
+ required: ["task_id"],
627
+ },
628
+ },
629
+ {
630
+ name: "safe_status",
631
+ description:
632
+ "Return minimal task lifecycle status without exposing diff, log content, file contents, or sensitive paths. Use this when only task state is needed and content-bearing tools may be blocked by upper-layer security.",
633
+ inputSchema: {
634
+ type: "object",
635
+ properties: {
636
+ task_id: { type: "string", description: "Task ID to check" },
637
+ },
638
+ required: ["task_id"],
639
+ },
640
+ },
641
+ {
642
+ name: "safe_result",
643
+ description:
644
+ "Return a low-noise structured task result summary without full logs, markdown, or diff content.",
645
+ inputSchema: {
646
+ type: "object",
647
+ properties: {
648
+ task_id: { type: "string", description: "Task ID to summarize" },
649
+ max_items: { type: "integer", minimum: 1, maximum: 50, default: 8, description: "Maximum list entries to return per evidence group." },
650
+ },
651
+ required: ["task_id"],
652
+ },
653
+ },
654
+ {
655
+ name: "safe_audit",
656
+ description:
657
+ "Run audit_task and return only bounded structured audit evidence without full review markdown.",
658
+ inputSchema: {
659
+ type: "object",
660
+ properties: {
661
+ task_id: { type: "string", description: "Task ID to audit" },
662
+ max_items: { type: "integer", minimum: 1, maximum: 50, default: 8, description: "Maximum list entries to return per evidence group." },
663
+ },
664
+ required: ["task_id"],
665
+ },
666
+ },
667
+ {
668
+ name: "safe_test_summary",
669
+ description:
670
+ "Return a compact verification summary for a task without stdout/stderr or test log content.",
671
+ inputSchema: {
672
+ type: "object",
673
+ properties: {
674
+ task_id: { type: "string", description: "Task ID to summarize" },
675
+ },
676
+ required: ["task_id"],
677
+ },
678
+ },
679
+ {
680
+ name: "safe_diff_summary",
681
+ description:
682
+ "Return changed-file counts and bounded path metadata without returning diff content.",
683
+ inputSchema: {
684
+ type: "object",
685
+ properties: {
686
+ task_id: { type: "string", description: "Task ID to summarize" },
687
+ max_items: { type: "integer", minimum: 1, maximum: 50, default: 8, description: "Maximum changed files to return." },
688
+ },
689
+ required: ["task_id"],
690
+ },
691
+ }, {
692
+ name: "diagnose_task",
693
+ description:
694
+ "v0.7.0: Diagnose a running or collecting_artifacts task using multi-signal evidence (heartbeat age, log freshness, child PID liveness, watcher ownership, artifact presence). Returns a conservative diagnosis (active_running, stale_running, possibly_stale_running, orphaned_running, artifact_collection_stuck, done_candidate, unknown, terminal) with confidence level and safe_actions. Never relies on a single signal; refuses to call PID-alive tasks 'active' when other signals are stale (PID reuse protection). Read-only — does not modify task state.",
695
+ inputSchema: {
696
+ type: "object",
697
+ properties: {
698
+ task_id: { type: "string", description: "Task ID to diagnose" },
699
+ include_logs: {
700
+ type: "boolean",
701
+ default: false,
702
+ description: "When true, include redacted stdout/stderr tails in the output. Default false to keep output minimal.",
703
+ },
704
+ },
705
+ required: ["task_id"],
706
+ },
707
+ },
708
+ {
709
+ name: "reconcile_tasks",
710
+ description:
711
+ "v0.7.0: Scan stale running/collecting_artifacts tasks and either report or safely fix them. report_only (default) returns a diagnosis report without modifying state. safe_fix additionally writes high-confidence status transitions (failed_stale/orphaned/done_by_agent) atomically with backup (status.json.bak), audit fields, and an appended reconcile.log. Never touches tasks still owned by an active watcher; never applies medium/low confidence fixes.",
712
+ inputSchema: {
713
+ type: "object",
714
+ properties: {
715
+ mode: {
716
+ type: "string",
717
+ enum: ["report_only", "safe_fix"],
718
+ default: "report_only",
719
+ description: "report_only: read-only diagnosis report. safe_fix: additionally apply high-confidence status transitions with backup + atomic write + reconcile.log.",
720
+ },
721
+ max_age_minutes: {
722
+ type: "number",
723
+ minimum: 1,
724
+ maximum: 1440,
725
+ default: 30,
726
+ description: "Only consider tasks older than this (based on created_at or status.json mtime). Default 30 minutes.",
727
+ },
728
+ include_done_candidates: {
729
+ type: "boolean",
730
+ default: true,
731
+ description: "Include done_by_agent tasks as candidates (useful for auditing acceptance_status). Default true.",
732
+ },
733
+ },
734
+ },
735
+ },
736
+ {
737
+ name: "discover_tools",
738
+ description:
739
+ "v0.7.1: Search candidate tools by natural-language query (Chinese or English). Returns compressed summaries with risk level and schema digest. Filters by profile/mode/riskCeiling. High-risk tools (command/release/credential_sensitive) are hidden by default unless includeHighRisk=true. Read-only — never invokes tools.",
740
+ inputSchema: {
741
+ type: "object",
742
+ properties: {
743
+ query: {
744
+ type: "string",
745
+ description: "Natural-language search query. Supports Chinese intent terms (验收/改文件/发布/状态/差异/卡住/旧任务/搜索/工具/诊断 etc.) and English keywords.",
746
+ },
747
+ profile: {
748
+ type: "string",
749
+ enum: ["full", "chatgpt_core", "chatgpt_direct", "chatgpt_search"],
750
+ description: "Filter tools by profile. Default: no filter (all profiles).",
751
+ },
752
+ mode: {
753
+ type: "string",
754
+ enum: ["delegate", "direct", "audit", "release", "diagnostic"],
755
+ description: "Filter tools by mode. Default: no filter.",
756
+ },
757
+ maxResults: {
758
+ type: "number",
759
+ minimum: 1,
760
+ maximum: 50,
761
+ default: 8,
762
+ description: "Maximum number of results. Default 8.",
763
+ },
764
+ riskCeiling: {
765
+ type: "string",
766
+ enum: ["readonly", "workspace_read_sensitive", "workspace_write", "command", "release", "credential_sensitive"],
767
+ description: "Maximum risk level to include. Tools above this level are hidden. Overrides includeHighRisk.",
768
+ },
769
+ includeHighRisk: {
770
+ type: "boolean",
771
+ default: false,
772
+ description: "When true, include high-risk tools (command/release/credential_sensitive) in results. Default false.",
773
+ },
774
+ },
775
+ required: ["query"],
776
+ },
777
+ },
778
+ {
779
+ name: "explain_tool",
780
+ description:
781
+ "v0.7.1: Expand a single tool's metadata — title, summary, risk level, tags, aliases, profiles, modes, schema digest, and optionally the full inputSchema. Use after discover_tools to understand a specific tool before calling it. Read-only.",
782
+ inputSchema: {
783
+ type: "object",
784
+ properties: {
785
+ name: {
786
+ type: "string",
787
+ description: "Tool name or alias to explain. Accepts both the canonical name (e.g. 'create_task') and aliases (e.g. 'new_task').",
788
+ },
789
+ includeSchema: {
790
+ type: "boolean",
791
+ default: false,
792
+ description: "When true, include the full inputSchema in the response. Default false to keep output minimal.",
793
+ },
794
+ },
795
+ required: ["name"],
796
+ },
797
+ },
798
+ {
799
+ name: "invoke_discovered_tool",
800
+ description:
801
+ "v0.8.1: Invoke a previously discovered tool using a discoveryToken. The token must be obtained from discover_tools first. Enforces 10 security checks: token validity, toolName match, profile allowance, risk ceiling, sensitive path guard, assessment requirement, command whitelist, release confirmation, credential block, and invocation logging. Cannot call itself recursively.",
802
+ inputSchema: {
803
+ type: "object",
804
+ properties: {
805
+ toolName: { type: "string", description: "Name of the tool to invoke (must match the discoveryToken's toolName)." },
806
+ arguments: {
807
+ type: "object",
808
+ description: "Arguments to pass to the tool. Must match the tool's inputSchema.",
809
+ additionalProperties: true,
810
+ },
811
+ discoveryToken: { type: "string", description: "Token id from discover_tools. Single-use, expires after 10 minutes." },
812
+ assessmentId: { type: "string", description: "Required for workspace_write/release risk tools. Obtained from the assessment flow." },
813
+ },
814
+ required: ["toolName", "arguments", "discoveryToken"],
815
+ },
816
+ },
817
+ {
818
+ name: "create_goal",
819
+ description:
820
+ "v0.8.0: Create a Goal Session for managing a multi-task objective with subgoal dependencies. Generates a structured directory under .patchwarden/goals/{goal_id}/ with GOAL.md, GOALS.md, and goal_status.json. Use list_goals to enumerate existing goals and read_goal to inspect details.",
821
+ inputSchema: {
822
+ type: "object",
823
+ properties: {
824
+ title: { type: "string", description: "Goal title (human-readable)." },
825
+ goal_description: { type: "string", description: "Markdown description of the goal, success criteria, and context." },
826
+ repo_path: { type: "string", description: "Repository path inside workspaceRoot. Must be inside the configured workspace." },
827
+ },
828
+ required: ["title", "goal_description", "repo_path"],
829
+ },
830
+ },
831
+ {
832
+ name: "list_goals",
833
+ description:
834
+ "v0.8.0: List all Goal Sessions with completion summaries. Returns goal_id, title, status, subgoal counts, and last update time. Sorted by updated_at descending.",
835
+ inputSchema: {
836
+ type: "object",
837
+ properties: {},
838
+ },
839
+ },
840
+ {
841
+ name: "read_goal",
842
+ description:
843
+ "v0.8.0: Read full Goal Session details including GOAL.md content, goal_status.json, and all subgoals with dependency info. Use after list_goals to inspect a specific goal.",
844
+ inputSchema: {
845
+ type: "object",
846
+ properties: {
847
+ goal_id: { type: "string", description: "Goal ID from list_goals or create_goal." },
848
+ },
849
+ required: ["goal_id"],
850
+ },
851
+ },
852
+ {
853
+ name: "create_subgoal_task",
854
+ description:
855
+ "v0.8.0: Create a subgoal within a Goal Session and immediately launch an associated task. Atomically: addSubgoal → create_task → linkTask → mark subgoal running. The subgoal depends_on other subgoals (by id) which must be accepted before suggest_next_subgoal returns it.",
856
+ inputSchema: {
857
+ type: "object",
858
+ properties: {
859
+ goal_id: { type: "string", description: "Goal ID to add the subgoal to." },
860
+ subgoal_title: { type: "string", description: "Title of the new subgoal." },
861
+ depends_on: {
862
+ type: "array",
863
+ items: { type: "string" },
864
+ description: "Subgoal IDs this subgoal depends on. Dependencies must be accepted before this subgoal is suggested.",
865
+ },
866
+ plan_id: { type: "string", description: "Plan ID from save_plan. One of plan_id/inline_plan/template+goal is required." },
867
+ inline_plan: { type: "string", description: "Inline Markdown plan." },
868
+ plan_title: { type: "string", description: "Optional title for inline_plan." },
869
+ template: {
870
+ type: "string",
871
+ enum: [...TASK_TEMPLATE_NAMES],
872
+ description: "Built-in task template. Use with goal.",
873
+ },
874
+ goal: { type: "string", description: "Task goal when template is supplied." },
875
+ agent: { type: "string", description: "Agent name." },
876
+ repo_path: { type: "string", description: "Repository path inside workspaceRoot." },
877
+ test_command: { type: "string", description: "Verification command." },
878
+ verify_commands: {
879
+ type: "array",
880
+ maxItems: 20,
881
+ items: { type: "string" },
882
+ description: "Additional verification commands.",
883
+ },
884
+ timeout_seconds: { type: "integer", minimum: 1, description: "Task timeout in seconds." },
885
+ scope: { type: "array", items: { type: "string" }, description: "Allowed file/directory scope." },
886
+ forbidden: { type: "array", items: { type: "string" }, description: "Forbidden file/directory paths." },
887
+ verification: { type: "array", items: { type: "string" }, description: "Acceptance verification commands." },
888
+ done_evidence: { type: "array", items: { type: "string" }, description: "Required done evidence files." },
889
+ isolate_worktree: { type: "boolean", default: true, description: "v1.0.0: If true (default), create the task in an isolated git worktree under _workspacetrees/. Set false to run in the main workspace (v0.8.0 behavior)." },
890
+ },
891
+ required: ["goal_id", "subgoal_title", "repo_path"],
268
892
  },
269
893
  },
270
894
  {
271
- name: "list_tasks",
895
+ name: "accept_subgoal",
272
896
  description:
273
- "List recent tasks with status/repo/active filters plus watcher state and computed pending reasons.",
897
+ "v0.8.0: Accept a subgoal after all its associated tasks are accepted (via audit_task). Validates every task in subgoal.task_ids has status 'accepted'. Throws subgoal_not_ready if any task is not yet accepted.",
274
898
  inputSchema: {
275
899
  type: "object",
276
900
  properties: {
277
- status: {
278
- type: "string",
279
- description: "Filter by status: pending, running, done, failed, failed_verification, failed_scope_violation, failed_policy_violation, canceled",
280
- },
281
- limit: {
282
- type: "number",
283
- description: "Max tasks to return (default 20, max 100)",
284
- },
285
- repo_path: {
286
- type: "string",
287
- description: "Optional exact repo_path or resolved_repo_path filter.",
288
- },
289
- active_only: {
290
- type: "boolean",
291
- description: "When true, return only pending and running tasks.",
292
- },
901
+ goal_id: { type: "string", description: "Goal ID." },
902
+ subgoal_id: { type: "string", description: "Subgoal ID to accept." },
293
903
  },
904
+ required: ["goal_id", "subgoal_id"],
294
905
  },
295
906
  },
296
907
  {
297
- name: "cancel_task",
908
+ name: "reject_subgoal",
298
909
  description:
299
- "Request graceful cancellation. The runner that owns the child process performs termination; the MCP server never kills a PID read from task files.",
910
+ "v0.8.0: Reject a subgoal with a reason. Allowed from any non-terminal status (ready/running/done_by_agent/needs_fix). Records rejected_reason in goal_status.json.",
300
911
  inputSchema: {
301
912
  type: "object",
302
913
  properties: {
303
- task_id: { type: "string", description: "Task ID to cancel" },
914
+ goal_id: { type: "string", description: "Goal ID." },
915
+ subgoal_id: { type: "string", description: "Subgoal ID to reject." },
916
+ reason: { type: "string", description: "Rejection reason (required)." },
304
917
  },
305
- required: ["task_id"],
918
+ required: ["goal_id", "subgoal_id", "reason"],
306
919
  },
307
920
  },
308
921
  {
309
- name: "kill_task",
922
+ name: "suggest_next_subgoal",
310
923
  description:
311
- "Request immediate termination of a pending or running task. The runner validates and kills only the child process it owns.",
924
+ "v0.8.0: Suggest the next executable subgoal based on the dependency graph. Returns a ready subgoal whose dependencies are all accepted. If none ready, returns blocked_by list. Use to drive goal-directed task sequencing.",
312
925
  inputSchema: {
313
926
  type: "object",
314
927
  properties: {
315
- task_id: { type: "string", description: "Task ID to terminate" },
928
+ goal_id: { type: "string", description: "Goal ID." },
316
929
  },
317
- required: ["task_id"],
930
+ required: ["goal_id"],
318
931
  },
319
932
  },
320
933
  {
321
- name: "retry_task",
934
+ name: "summarize_goal_progress",
322
935
  description:
323
- "Create a new task with the same plan, agent, repo_path, and test_command as an existing task. The original task is unchanged.",
936
+ "v0.8.0: Summarize goal completion: counts by status (accepted/rejected/running/ready/needs_fix/done_by_agent), completion_rate percentage, blocked_subgoals, and risks (needs_fix or running subgoals).",
324
937
  inputSchema: {
325
938
  type: "object",
326
939
  properties: {
327
- task_id: { type: "string", description: "Task ID to retry" },
940
+ goal_id: { type: "string", description: "Goal ID." },
328
941
  },
329
- required: ["task_id"],
942
+ required: ["goal_id"],
330
943
  },
331
944
  },
332
945
  {
333
- name: "get_task_stdout_tail",
946
+ name: "export_handoff",
334
947
  description:
335
- "Read the last N lines of agent stdout/stderr. Reads from real-time stdout.log/stderr.log during execution, falls back to result.md after completion. Works on pending, running, and completed tasks. Default 80 lines.",
948
+ "v0.8.0: Export a handoff.md document for transferring a Goal Session to a new conversation. Includes current goal, completed/pending subgoals, recent diff/test results, blockers, next steps, and risks. Writes to .patchwarden/goals/{goal_id}/handoff.md.",
336
949
  inputSchema: {
337
950
  type: "object",
338
951
  properties: {
339
- task_id: { type: "string", description: "Task ID" },
340
- lines: { type: "number", description: "Tail line count (default 80, max 200)" },
952
+ goal_id: { type: "string", description: "Goal ID." },
341
953
  },
342
- required: ["task_id"],
954
+ required: ["goal_id"],
343
955
  },
344
956
  },
345
957
  {
346
- name: "get_task_log_tail",
958
+ name: "check_release_gate",
347
959
  description:
348
- "Read the last N lines of a task log file (stdout/stderr/test/verify) with automatic secret redaction. Default 80 lines, max 200. Always returns tail only never the full file. Use this instead of read_workspace_file to avoid triggering platform content filters on log output.",
960
+ "v1.0.0: Verify release readiness across five sequential stages: local_ready packed_ready → published_verified → github_release_verified → ci_verified. Remote stages (published/github/ci) query npm registry and GitHub API via node:https read-only GET; network errors return 'not_checked' (not 'failed'). Never claims release complete before published_verified passes. Does not execute shell commands for remote queries.",
349
961
  inputSchema: {
350
962
  type: "object",
351
963
  properties: {
352
- task_id: { type: "string", description: "Task ID" },
353
- file: {
964
+ repo_path: { type: "string", description: "Repository path inside workspaceRoot." },
965
+ target_stage: {
354
966
  type: "string",
355
- enum: ["stdout", "stderr", "test", "verify"],
356
- description: "Log file to read: stdout (stdout.log), stderr (stderr.log), test (test.log), verify (verify.log)",
967
+ enum: ["local_ready", "packed_ready", "published_verified", "github_release_verified", "ci_verified"],
968
+ description: "Target stage to verify. Stages before target are checked; stages after a failure are 'not_checked'.",
357
969
  },
358
- lines: { type: "number", description: "Tail line count (default 80, max 200)" },
359
- redact: { type: "boolean", description: "Apply secret redaction (default true)" },
970
+ package_name: { type: "string", description: "npm package name for published_verified (e.g. 'patchwarden'). Required for published_verified stage." },
971
+ version: { type: "string", description: "Version string for published_verified (e.g. '1.0.0'). Required for published_verified stage." },
972
+ github_repo: { type: "string", description: "GitHub repo in 'owner/repo' form for github_release_verified and ci_verified." },
973
+ branch: { type: "string", description: "Git branch for ci_verified (e.g. 'main')." },
360
974
  },
361
- required: ["task_id", "file"],
975
+ required: ["repo_path", "target_stage"],
362
976
  },
363
977
  },
364
978
  {
365
- name: "get_task_progress",
979
+ name: "release_check",
366
980
  description:
367
- "Read progress.md for task phases and the most recent heartbeat/current command.",
981
+ "v1.3.0: Run a bounded release readiness check by wrapping the existing release gate. Local stages use existing guarded release-gate commands; remote stages are read-only. Does not publish, push, tag, or create a GitHub Release.",
368
982
  inputSchema: {
369
983
  type: "object",
370
984
  properties: {
371
- task_id: { type: "string", description: "Task ID" },
985
+ repo_path: { type: "string", description: "Repository path inside workspaceRoot." },
986
+ target_stage: {
987
+ type: "string",
988
+ enum: ["local_ready", "packed_ready", "published_verified", "github_release_verified", "ci_verified"],
989
+ default: "local_ready",
990
+ description: "Release gate stage to check. Defaults to local_ready.",
991
+ },
992
+ package_name: { type: "string", description: "npm package name for remote verification." },
993
+ version: { type: "string", description: "Version to verify. Defaults to project policy/package.json when omitted." },
994
+ github_repo: { type: "string", description: "GitHub repo in owner/repo form for release/CI verification." },
995
+ branch: { type: "string", default: "main", description: "Branch for CI verification." },
372
996
  },
373
- required: ["task_id"],
997
+ required: ["repo_path"],
374
998
  },
375
999
  },
376
1000
  {
377
- name: "wait_for_task",
1001
+ name: "release_prepare",
378
1002
  description:
379
- "Long-poll a task for up to 30 seconds. If continuation_required=true, call wait_for_task again immediately and do not finish the assistant turn. Terminal responses include get_task_summary acceptance evidence.",
1003
+ "v1.3.0: Run project-policy release preparation commands only when each command is already accepted by the existing PatchWarden command guard. Returns command status only, never stdout/stderr. Does not publish, push, tag, or create a GitHub Release.",
380
1004
  inputSchema: {
381
1005
  type: "object",
382
1006
  properties: {
383
- task_id: { type: "string", description: "Task ID from create_task" },
384
- wait_seconds: { type: "integer", minimum: 1, maximum: 30, default: 25 },
1007
+ repo_path: { type: "string", description: "Repository path inside workspaceRoot." },
1008
+ required_commands: {
1009
+ type: "array",
1010
+ maxItems: 10,
1011
+ items: {
1012
+ type: "string",
1013
+ ...(testCommands.length > 0 ? { enum: testCommands } : {}),
1014
+ },
1015
+ description: "Optional exact-match release preparation commands. Defaults to project policy release_mode.required_commands.",
1016
+ },
385
1017
  timeout_seconds: {
386
1018
  type: "integer",
387
1019
  minimum: 1,
388
- maximum: 30,
389
- description: "Preferred alias for wait_seconds. Maximum 30 seconds to stay within connector request limits.",
1020
+ maximum: config.maxTaskTimeoutSeconds,
1021
+ default: 300,
1022
+ description: "Per-command timeout in seconds.",
390
1023
  },
391
1024
  },
392
- required: ["task_id"],
1025
+ required: ["repo_path"],
393
1026
  },
394
1027
  },
395
1028
  {
396
- name: "get_task_summary",
1029
+ name: "release_verify",
397
1030
  description:
398
- "Return structured acceptance evidence. Use view=compact first for bounded counts and risk excerpts; use standard only when full changed-file and log-tail detail is required.",
1031
+ "v1.3.0: Verify npm/GitHub/CI release facts with read-only HTTPS requests. Does not run local shell commands and does not publish, push, tag, or create a GitHub Release.",
399
1032
  inputSchema: {
400
1033
  type: "object",
401
1034
  properties: {
402
- task_id: { type: "string", description: "Task ID" },
403
- view: { type: "string", enum: ["compact", "standard"], default: "standard", description: "Compact returns bounded acceptance evidence; standard preserves the full legacy summary." },
404
- max_items: { type: "integer", minimum: 1, maximum: 50, default: 8, description: "Maximum entries per compact evidence group." },
1035
+ repo_path: { type: "string", description: "Repository path inside workspaceRoot." },
1036
+ package_name: { type: "string", description: "npm package name. Defaults to package.json name." },
1037
+ version: { type: "string", description: "Version to verify. Defaults to project policy/package.json." },
1038
+ github_repo: { type: "string", description: "GitHub repo in owner/repo form. Defaults to package.json repository when available." },
1039
+ branch: { type: "string", default: "main", description: "Branch for CI verification." },
405
1040
  },
406
- required: ["task_id"],
1041
+ required: ["repo_path"],
407
1042
  },
408
1043
  },
409
1044
  {
410
- name: "audit_task",
1045
+ name: "release_cleanup",
411
1046
  description:
412
- "Independently audit a task's outputs. Verifies status, result.md, test.log, git.diff, repo_path consistency, cross-references agent claims with package.json scripts, and flags unverified release/publish claims. Evidence-backed failures, possible heuristic false positives, and manual-verification items are returned separately. Writes independent-review.md to the task directory.",
1047
+ "v1.3.0: Clean up release artifacts using project-policy auto_cleanup rules. Defaults to dry_run=true. Non-dry-run cleanup only removes low-risk ignored/untracked artifacts under repo_path and writes an audit summary.",
413
1048
  inputSchema: {
414
1049
  type: "object",
415
1050
  properties: {
416
- task_id: { type: "string", description: "Task ID to audit" },
1051
+ repo_path: { type: "string", description: "Repository path inside workspaceRoot." },
1052
+ dry_run: { type: "boolean", default: true, description: "Preview cleanup by default. Set false to remove eligible artifacts." },
1053
+ patterns: {
1054
+ type: "array",
1055
+ maxItems: 20,
1056
+ items: { type: "string" },
1057
+ description: "Optional cleanup patterns. Defaults to project policy auto_cleanup.patterns.",
1058
+ },
417
1059
  },
418
- required: ["task_id"],
1060
+ required: ["repo_path"],
419
1061
  },
420
1062
  },
421
1063
  {
422
- name: "safe_status",
1064
+ name: "merge_worktree",
423
1065
  description:
424
- "Return minimal task lifecycle status without exposing diff, log content, file contents, or sensitive paths. Use this when only task state is needed and content-bearing tools may be blocked by upper-layer security.",
1066
+ "v1.0.0: Merge an isolated git worktree's changes back into the main workspace. Use after a subgoal task (created with isolate_worktree=true) is accepted. Updates worktree_status.json to status='merged'. Merge failures do NOT delete the worktree (preserved for manual inspection).",
425
1067
  inputSchema: {
426
1068
  type: "object",
427
1069
  properties: {
428
- task_id: { type: "string", description: "Task ID to check" },
1070
+ worktree_id: { type: "string", description: "Worktree ID (wt_...) from create_subgoal_task." },
1071
+ repo_path: { type: "string", description: "Main workspace repository path inside workspaceRoot." },
429
1072
  },
430
- required: ["task_id"],
1073
+ required: ["worktree_id", "repo_path"],
1074
+ },
1075
+ },
1076
+ {
1077
+ name: "discard_worktree",
1078
+ description:
1079
+ "v1.0.0: Discard an isolated git worktree safely. Removes the worktree (git worktree remove --force), deletes its branch, and archives status as 'discarded'. Use when a subgoal is rejected or abandoned. All paths pass guardWorkspacePath + sensitiveGuard.",
1080
+ inputSchema: {
1081
+ type: "object",
1082
+ properties: {
1083
+ worktree_id: { type: "string", description: "Worktree ID (wt_...) to discard." },
1084
+ repo_path: { type: "string", description: "Main workspace repository path inside workspaceRoot." },
1085
+ },
1086
+ required: ["worktree_id", "repo_path"],
431
1087
  },
432
1088
  },
433
1089
  ];
434
1090
 
435
1091
  // Direct session tools
436
- const directCommands = getAllConfiguredDirectCommands(config);
437
1092
  tools.push({
438
1093
  name: "create_direct_session",
439
1094
  description:
@@ -523,6 +1178,30 @@ export function getToolDefs(): ToolDef[] {
523
1178
  },
524
1179
  });
525
1180
 
1181
+ tools.push({
1182
+ name: "run_direct_verification_bundle",
1183
+ description:
1184
+ "Run multiple allowlisted Direct verification commands sequentially and return only bounded structured status. Omits stdout/stderr tails and log content.",
1185
+ inputSchema: {
1186
+ type: "object",
1187
+ properties: {
1188
+ session_id: { type: "string", description: "Direct session ID" },
1189
+ commands: {
1190
+ type: "array",
1191
+ minItems: 1,
1192
+ maxItems: 20,
1193
+ items: {
1194
+ type: "string",
1195
+ ...(directCommands.length > 0 ? { enum: directCommands } : {}),
1196
+ },
1197
+ description: "Verification commands to run in order. Each command must be accepted by the existing Direct command guard.",
1198
+ },
1199
+ timeout_seconds: { type: "integer", minimum: 1, maximum: Math.min(config.maxTaskTimeoutSeconds, config.directSessionTtlSeconds), default: 120 },
1200
+ },
1201
+ required: ["session_id", "commands"],
1202
+ },
1203
+ });
1204
+
526
1205
  tools.push({
527
1206
  name: "finalize_direct_session",
528
1207
  description:
@@ -549,6 +1228,47 @@ export function getToolDefs(): ToolDef[] {
549
1228
  },
550
1229
  });
551
1230
 
1231
+ tools.push({
1232
+ name: "safe_direct_summary",
1233
+ description:
1234
+ "Return a low-noise Direct session summary without diff content or verification stdout/stderr tails.",
1235
+ inputSchema: {
1236
+ type: "object",
1237
+ properties: {
1238
+ session_id: { type: "string", description: "Direct session ID" },
1239
+ max_items: { type: "integer", minimum: 1, maximum: 50, default: 8, description: "Maximum list entries to return per evidence group." },
1240
+ },
1241
+ required: ["session_id"],
1242
+ },
1243
+ });
1244
+
1245
+ tools.push({
1246
+ name: "safe_finalize_direct_session",
1247
+ description:
1248
+ "Finalize a Direct session and return only bounded structured evidence, omitting diff and verification log content.",
1249
+ inputSchema: {
1250
+ type: "object",
1251
+ properties: {
1252
+ session_id: { type: "string", description: "Direct session ID to finalize" },
1253
+ max_items: { type: "integer", minimum: 1, maximum: 50, default: 8, description: "Maximum list entries to return per evidence group." },
1254
+ },
1255
+ required: ["session_id"],
1256
+ },
1257
+ });
1258
+
1259
+ tools.push({
1260
+ name: "safe_audit_direct_session",
1261
+ description:
1262
+ "Audit a Direct session and return only bounded structured evidence without verification stdout/stderr tails.",
1263
+ inputSchema: {
1264
+ type: "object",
1265
+ properties: {
1266
+ session_id: { type: "string", description: "Direct session ID to audit" },
1267
+ max_items: { type: "integer", minimum: 1, maximum: 50, default: 8, description: "Maximum list entries to return per evidence group." },
1268
+ },
1269
+ required: ["session_id"],
1270
+ },
1271
+ });
552
1272
  tools.push({
553
1273
  name: "sync_file",
554
1274
  description:
@@ -665,6 +1385,67 @@ async function handleToolCallInternal(name: string, args: Record<string, unknown
665
1385
  );
666
1386
  }
667
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
+
668
1449
  case "get_task_status": {
669
1450
  return toResult(getTaskStatus(String(args?.task_id ?? "")));
670
1451
  }
@@ -775,6 +1556,83 @@ async function handleToolCallInternal(name: string, args: Record<string, unknown
775
1556
  return toResult(safeStatus(String(args?.task_id ?? "")));
776
1557
  }
777
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
+
778
1636
  case "run_task": {
779
1637
  const config = getConfig();
780
1638
  if ((config as any).enableRunTaskTool !== true) {
@@ -826,6 +1684,15 @@ async function handleToolCallInternal(name: string, args: Record<string, unknown
826
1684
  }));
827
1685
  }
828
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
+
829
1696
  case "finalize_direct_session": {
830
1697
  guardDirectProfileEnabled();
831
1698
  return toResult(finalizeDirectSession({
@@ -840,6 +1707,26 @@ async function handleToolCallInternal(name: string, args: Record<string, unknown
840
1707
  }));
841
1708
  }
842
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
+ }
843
1730
  case "sync_file": {
844
1731
  guardDirectProfileEnabled();
845
1732
  return toResult(syncFile(
@@ -853,6 +1740,139 @@ async function handleToolCallInternal(name: string, args: Record<string, unknown
853
1740
  ));
854
1741
  }
855
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
+
856
1876
  default:
857
1877
  throw new Error(`Unknown tool: ${name}`);
858
1878
  }