patchwarden 0.6.4 → 1.1.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 (212) hide show
  1. package/README.en.md +18 -18
  2. package/README.md +20 -20
  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 +15 -2
  8. package/dist/doctor.js +32 -1
  9. package/dist/goal/acceptanceEngine.d.ts +62 -0
  10. package/dist/goal/acceptanceEngine.js +103 -0
  11. package/dist/goal/acceptanceTemplate.d.ts +10 -0
  12. package/dist/goal/acceptanceTemplate.js +104 -0
  13. package/dist/goal/goalGraph.d.ts +58 -0
  14. package/dist/goal/goalGraph.js +189 -0
  15. package/dist/goal/goalProgress.d.ts +81 -0
  16. package/dist/goal/goalProgress.js +216 -0
  17. package/dist/goal/goalStatus.d.ts +60 -0
  18. package/dist/goal/goalStatus.js +137 -0
  19. package/dist/goal/goalStore.d.ts +79 -0
  20. package/dist/goal/goalStore.js +211 -0
  21. package/dist/goal/handoffExport.d.ts +34 -0
  22. package/dist/goal/handoffExport.js +183 -0
  23. package/dist/goal/subgoalSync.d.ts +40 -0
  24. package/dist/goal/subgoalSync.js +72 -0
  25. package/dist/goal/worktreeManager.d.ts +96 -0
  26. package/dist/goal/worktreeManager.js +292 -0
  27. package/dist/logging.d.ts +44 -0
  28. package/dist/logging.js +68 -0
  29. package/dist/release/releaseGate.d.ts +99 -0
  30. package/dist/release/releaseGate.js +475 -0
  31. package/dist/runner/postTaskCleanup.d.ts +13 -0
  32. package/dist/runner/postTaskCleanup.js +147 -0
  33. package/dist/runner/runTask.js +50 -10
  34. package/dist/security/discoveryTokenStore.d.ts +62 -0
  35. package/dist/security/discoveryTokenStore.js +100 -0
  36. package/dist/security/toolInvocationGuard.d.ts +35 -0
  37. package/dist/security/toolInvocationGuard.js +153 -0
  38. package/dist/smoke-test.js +18 -10
  39. package/dist/taskRuntime.d.ts +17 -0
  40. package/dist/test/unit/acceptance-engine.test.d.ts +1 -0
  41. package/dist/test/unit/acceptance-engine.test.js +228 -0
  42. package/dist/test/unit/agent-router.test.d.ts +1 -0
  43. package/dist/test/unit/agent-router.test.js +287 -0
  44. package/dist/test/unit/audit-checks.test.d.ts +1 -0
  45. package/dist/test/unit/audit-checks.test.js +350 -0
  46. package/dist/test/unit/diagnose-task.test.d.ts +1 -0
  47. package/dist/test/unit/diagnose-task.test.js +457 -0
  48. package/dist/test/unit/discovery-token-store.test.d.ts +1 -0
  49. package/dist/test/unit/discovery-token-store.test.js +139 -0
  50. package/dist/test/unit/goal-graph.test.d.ts +1 -0
  51. package/dist/test/unit/goal-graph.test.js +298 -0
  52. package/dist/test/unit/goal-progress.test.d.ts +1 -0
  53. package/dist/test/unit/goal-progress.test.js +381 -0
  54. package/dist/test/unit/goal-status.test.d.ts +1 -0
  55. package/dist/test/unit/goal-status.test.js +215 -0
  56. package/dist/test/unit/goal-store.test.d.ts +1 -0
  57. package/dist/test/unit/goal-store.test.js +253 -0
  58. package/dist/test/unit/goal-subgoal-task.test.d.ts +1 -0
  59. package/dist/test/unit/goal-subgoal-task.test.js +55 -0
  60. package/dist/test/unit/goal-tools-registry.test.d.ts +1 -0
  61. package/dist/test/unit/goal-tools-registry.test.js +190 -0
  62. package/dist/test/unit/handoff-export.test.d.ts +1 -0
  63. package/dist/test/unit/handoff-export.test.js +263 -0
  64. package/dist/test/unit/invoke-discovered-tool.test.d.ts +1 -0
  65. package/dist/test/unit/invoke-discovered-tool.test.js +167 -0
  66. package/dist/test/unit/logging.test.js +127 -5
  67. package/dist/test/unit/post-task-cleanup.test.d.ts +1 -0
  68. package/dist/test/unit/post-task-cleanup.test.js +48 -0
  69. package/dist/test/unit/reconcile-tasks.test.d.ts +1 -0
  70. package/dist/test/unit/reconcile-tasks.test.js +456 -0
  71. package/dist/test/unit/release-gate.test.d.ts +1 -0
  72. package/dist/test/unit/release-gate.test.js +242 -0
  73. package/dist/test/unit/safe-views.test.d.ts +1 -0
  74. package/dist/test/unit/safe-views.test.js +171 -0
  75. package/dist/test/unit/schema-drift-check.test.d.ts +1 -0
  76. package/dist/test/unit/schema-drift-check.test.js +175 -0
  77. package/dist/test/unit/subgoal-sync.test.d.ts +1 -0
  78. package/dist/test/unit/subgoal-sync.test.js +183 -0
  79. package/dist/test/unit/tool-invocation-guard.test.d.ts +1 -0
  80. package/dist/test/unit/tool-invocation-guard.test.js +432 -0
  81. package/dist/test/unit/tool-usage-stats.test.d.ts +1 -0
  82. package/dist/test/unit/tool-usage-stats.test.js +300 -0
  83. package/dist/test/unit/toolSearch.test.d.ts +1 -0
  84. package/dist/test/unit/toolSearch.test.js +571 -0
  85. package/dist/test/unit/worktree-manager.test.d.ts +1 -0
  86. package/dist/test/unit/worktree-manager.test.js +176 -0
  87. package/dist/tools/auditTask.d.ts +103 -1
  88. package/dist/tools/auditTask.js +461 -8
  89. package/dist/tools/cancelTask.js +1 -1
  90. package/dist/tools/checkReleaseGate.d.ts +21 -0
  91. package/dist/tools/checkReleaseGate.js +22 -0
  92. package/dist/tools/createTask.d.ts +18 -2
  93. package/dist/tools/createTask.js +29 -1
  94. package/dist/tools/diagnoseTask.d.ts +45 -0
  95. package/dist/tools/diagnoseTask.js +347 -0
  96. package/dist/tools/discardWorktree.d.ts +24 -0
  97. package/dist/tools/discardWorktree.js +27 -0
  98. package/dist/tools/discoverTools.d.ts +11 -0
  99. package/dist/tools/discoverTools.js +39 -0
  100. package/dist/tools/explainTool.d.ts +11 -0
  101. package/dist/tools/explainTool.js +21 -0
  102. package/dist/tools/getTaskSummary.js +2 -1
  103. package/dist/tools/goalSubgoalTask.d.ts +51 -0
  104. package/dist/tools/goalSubgoalTask.js +110 -0
  105. package/dist/tools/healthCheck.d.ts +1 -0
  106. package/dist/tools/healthCheck.js +5 -1
  107. package/dist/tools/invokeDiscoveredTool.d.ts +37 -0
  108. package/dist/tools/invokeDiscoveredTool.js +112 -0
  109. package/dist/tools/listTasks.d.ts +3 -1
  110. package/dist/tools/listTasks.js +14 -1
  111. package/dist/tools/mergeWorktree.d.ts +24 -0
  112. package/dist/tools/mergeWorktree.js +27 -0
  113. package/dist/tools/reconcileTasks.d.ts +41 -0
  114. package/dist/tools/reconcileTasks.js +352 -0
  115. package/dist/tools/registry.js +550 -0
  116. package/dist/tools/safeStatus.d.ts +31 -1
  117. package/dist/tools/safeStatus.js +43 -1
  118. package/dist/tools/safeViews.d.ts +256 -0
  119. package/dist/tools/safeViews.js +250 -0
  120. package/dist/tools/schemaDriftCheck.d.ts +46 -0
  121. package/dist/tools/schemaDriftCheck.js +80 -0
  122. package/dist/tools/toolCatalog.d.ts +4 -3
  123. package/dist/tools/toolCatalog.js +33 -11
  124. package/dist/tools/toolRegistry.d.ts +61 -0
  125. package/dist/tools/toolRegistry.js +724 -0
  126. package/dist/tools/toolSearch.d.ts +110 -0
  127. package/dist/tools/toolSearch.js +331 -0
  128. package/dist/tools/toolUsageStats.d.ts +41 -0
  129. package/dist/tools/toolUsageStats.js +116 -0
  130. package/dist/tools/waitForTask.js +1 -0
  131. package/dist/version.d.ts +2 -2
  132. package/dist/version.js +2 -2
  133. package/dist/watcherStatus.d.ts +29 -0
  134. package/dist/watcherStatus.js +92 -1
  135. package/package.json +3 -2
  136. package/scripts/checks/control-smoke.js +2 -2
  137. package/scripts/checks/doctor-smoke.js +1 -0
  138. package/scripts/checks/lifecycle-smoke.js +13 -9
  139. package/scripts/checks/mcp-manifest-check.js +53 -6
  140. package/scripts/checks/mcp-smoke.js +36 -9
  141. package/scripts/checks/watcher-supervisor-smoke.js +2 -2
  142. package/src/agents/agentRouter.ts +149 -0
  143. package/src/config.ts +9 -3
  144. package/src/controlCenter.ts +17 -3
  145. package/src/doctor.ts +36 -1
  146. package/src/goal/acceptanceEngine.ts +160 -0
  147. package/src/goal/acceptanceTemplate.ts +121 -0
  148. package/src/goal/goalGraph.ts +225 -0
  149. package/src/goal/goalProgress.ts +301 -0
  150. package/src/goal/goalStatus.ts +234 -0
  151. package/src/goal/goalStore.ts +306 -0
  152. package/src/goal/handoffExport.ts +211 -0
  153. package/src/goal/subgoalSync.ts +82 -0
  154. package/src/goal/worktreeManager.ts +404 -0
  155. package/src/logging.ts +91 -0
  156. package/src/release/releaseGate.ts +567 -0
  157. package/src/runner/postTaskCleanup.ts +154 -0
  158. package/src/runner/runTask.ts +49 -10
  159. package/src/security/discoveryTokenStore.ts +157 -0
  160. package/src/security/toolInvocationGuard.ts +251 -0
  161. package/src/smoke-test.ts +15 -7
  162. package/src/taskRuntime.ts +17 -0
  163. package/src/test/unit/acceptance-engine.test.ts +261 -0
  164. package/src/test/unit/agent-router.test.ts +342 -0
  165. package/src/test/unit/audit-checks.test.ts +567 -0
  166. package/src/test/unit/diagnose-task.test.ts +544 -0
  167. package/src/test/unit/discovery-token-store.test.ts +181 -0
  168. package/src/test/unit/goal-graph.test.ts +347 -0
  169. package/src/test/unit/goal-progress.test.ts +538 -0
  170. package/src/test/unit/goal-status.test.ts +270 -0
  171. package/src/test/unit/goal-store.test.ts +318 -0
  172. package/src/test/unit/goal-subgoal-task.test.ts +72 -0
  173. package/src/test/unit/goal-tools-registry.test.ts +243 -0
  174. package/src/test/unit/handoff-export.test.ts +295 -0
  175. package/src/test/unit/invoke-discovered-tool.test.ts +216 -0
  176. package/src/test/unit/logging.test.ts +177 -5
  177. package/src/test/unit/post-task-cleanup.test.ts +53 -0
  178. package/src/test/unit/reconcile-tasks.test.ts +551 -0
  179. package/src/test/unit/release-gate.test.ts +314 -0
  180. package/src/test/unit/safe-views.test.ts +184 -0
  181. package/src/test/unit/schema-drift-check.test.ts +258 -0
  182. package/src/test/unit/subgoal-sync.test.ts +236 -0
  183. package/src/test/unit/tool-invocation-guard.test.ts +542 -0
  184. package/src/test/unit/tool-usage-stats.test.ts +384 -0
  185. package/src/test/unit/toolSearch.test.ts +756 -0
  186. package/src/test/unit/worktree-manager.test.ts +247 -0
  187. package/src/tools/auditTask.ts +831 -402
  188. package/src/tools/cancelTask.ts +1 -1
  189. package/src/tools/checkReleaseGate.ts +45 -0
  190. package/src/tools/createTask.ts +64 -6
  191. package/src/tools/diagnoseTask.ts +460 -0
  192. package/src/tools/discardWorktree.ts +42 -0
  193. package/src/tools/discoverTools.ts +51 -0
  194. package/src/tools/explainTool.ts +26 -0
  195. package/src/tools/getTaskSummary.ts +2 -1
  196. package/src/tools/goalSubgoalTask.ts +170 -0
  197. package/src/tools/healthCheck.ts +4 -1
  198. package/src/tools/invokeDiscoveredTool.ts +163 -0
  199. package/src/tools/listTasks.ts +16 -2
  200. package/src/tools/mergeWorktree.ts +42 -0
  201. package/src/tools/reconcileTasks.ts +464 -0
  202. package/src/tools/registry.ts +618 -1
  203. package/src/tools/safeStatus.ts +73 -2
  204. package/src/tools/safeViews.ts +271 -0
  205. package/src/tools/schemaDriftCheck.ts +120 -0
  206. package/src/tools/toolCatalog.ts +34 -11
  207. package/src/tools/toolRegistry.ts +786 -0
  208. package/src/tools/toolSearch.ts +464 -0
  209. package/src/tools/toolUsageStats.ts +151 -0
  210. package/src/tools/waitForTask.ts +1 -0
  211. package/src/version.ts +2 -2
  212. package/src/watcherStatus.ts +97 -1
@@ -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
  */
@@ -29,6 +29,20 @@ import { waitForTask } from "../tools/waitForTask.js";
29
29
  import { errorPayload, PatchWardenError } from "../errors.js";
30
30
  import { auditTask } from "../tools/auditTask.js";
31
31
  import { safeStatus } from "../tools/safeStatus.js";
32
+ import {
33
+ safeAudit,
34
+ safeAuditDirectSession,
35
+ safeDiffSummary,
36
+ safeDirectSummary,
37
+ safeFinalizeDirectSession,
38
+ safeResult,
39
+ safeTestSummary,
40
+ } from "../tools/safeViews.js";
41
+ import { diagnoseTask } from "../tools/diagnoseTask.js";
42
+ import { reconcileTasks } from "../tools/reconcileTasks.js";
43
+ import { discoverTools } from "../tools/discoverTools.js";
44
+ import { explainTool } from "../tools/explainTool.js";
45
+ import { invokeDiscoveredTool } from "./invokeDiscoveredTool.js";
32
46
  import { logger } from "../logging.js";
33
47
  import { runTask } from "../runner/runTask.js";
34
48
  import { createDirectSession } from "../tools/createDirectSession.js";
@@ -38,6 +52,14 @@ import { runVerification } from "../tools/runVerification.js";
38
52
  import { finalizeDirectSession } from "../tools/finalizeDirectSession.js";
39
53
  import { auditSession } from "../tools/auditSession.js";
40
54
  import { syncFile } from "../tools/syncFile.js";
55
+ import { createGoal, listGoals, readGoal, readGoalStatus } from "../goal/goalStore.js";
56
+ import { suggestNextSubgoal } from "../goal/goalGraph.js";
57
+ import { exportHandoff } from "../goal/handoffExport.js";
58
+ import { acceptSubgoal, rejectSubgoal, summarizeGoalProgress } from "../goal/goalProgress.js";
59
+ import { createSubgoalTask } from "./goalSubgoalTask.js";
60
+ import { checkReleaseGate } from "./checkReleaseGate.js";
61
+ import { mergeWorktreeTool } from "./mergeWorktree.js";
62
+ import { discardWorktreeTool } from "./discardWorktree.js";
41
63
  import { TASK_TEMPLATE_NAMES } from "./taskTemplates.js";
42
64
  import {
43
65
  buildToolCatalogSnapshot,
@@ -430,6 +452,369 @@ export function getToolDefs(): ToolDef[] {
430
452
  required: ["task_id"],
431
453
  },
432
454
  },
455
+ {
456
+ name: "safe_result",
457
+ description:
458
+ "Return a low-noise structured task result summary without full logs, markdown, or diff content.",
459
+ inputSchema: {
460
+ type: "object",
461
+ properties: {
462
+ task_id: { type: "string", description: "Task ID to summarize" },
463
+ max_items: { type: "integer", minimum: 1, maximum: 50, default: 8, description: "Maximum list entries to return per evidence group." },
464
+ },
465
+ required: ["task_id"],
466
+ },
467
+ },
468
+ {
469
+ name: "safe_audit",
470
+ description:
471
+ "Run audit_task and return only bounded structured audit evidence without full review markdown.",
472
+ inputSchema: {
473
+ type: "object",
474
+ properties: {
475
+ task_id: { type: "string", description: "Task ID to audit" },
476
+ max_items: { type: "integer", minimum: 1, maximum: 50, default: 8, description: "Maximum list entries to return per evidence group." },
477
+ },
478
+ required: ["task_id"],
479
+ },
480
+ },
481
+ {
482
+ name: "safe_test_summary",
483
+ description:
484
+ "Return a compact verification summary for a task without stdout/stderr or test log content.",
485
+ inputSchema: {
486
+ type: "object",
487
+ properties: {
488
+ task_id: { type: "string", description: "Task ID to summarize" },
489
+ },
490
+ required: ["task_id"],
491
+ },
492
+ },
493
+ {
494
+ name: "safe_diff_summary",
495
+ description:
496
+ "Return changed-file counts and bounded path metadata without returning diff content.",
497
+ inputSchema: {
498
+ type: "object",
499
+ properties: {
500
+ task_id: { type: "string", description: "Task ID to summarize" },
501
+ max_items: { type: "integer", minimum: 1, maximum: 50, default: 8, description: "Maximum changed files to return." },
502
+ },
503
+ required: ["task_id"],
504
+ },
505
+ }, {
506
+ name: "diagnose_task",
507
+ description:
508
+ "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.",
509
+ inputSchema: {
510
+ type: "object",
511
+ properties: {
512
+ task_id: { type: "string", description: "Task ID to diagnose" },
513
+ include_logs: {
514
+ type: "boolean",
515
+ default: false,
516
+ description: "When true, include redacted stdout/stderr tails in the output. Default false to keep output minimal.",
517
+ },
518
+ },
519
+ required: ["task_id"],
520
+ },
521
+ },
522
+ {
523
+ name: "reconcile_tasks",
524
+ description:
525
+ "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.",
526
+ inputSchema: {
527
+ type: "object",
528
+ properties: {
529
+ mode: {
530
+ type: "string",
531
+ enum: ["report_only", "safe_fix"],
532
+ default: "report_only",
533
+ description: "report_only: read-only diagnosis report. safe_fix: additionally apply high-confidence status transitions with backup + atomic write + reconcile.log.",
534
+ },
535
+ max_age_minutes: {
536
+ type: "number",
537
+ minimum: 1,
538
+ maximum: 1440,
539
+ default: 30,
540
+ description: "Only consider tasks older than this (based on created_at or status.json mtime). Default 30 minutes.",
541
+ },
542
+ include_done_candidates: {
543
+ type: "boolean",
544
+ default: true,
545
+ description: "Include done_by_agent tasks as candidates (useful for auditing acceptance_status). Default true.",
546
+ },
547
+ },
548
+ },
549
+ },
550
+ {
551
+ name: "discover_tools",
552
+ description:
553
+ "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.",
554
+ inputSchema: {
555
+ type: "object",
556
+ properties: {
557
+ query: {
558
+ type: "string",
559
+ description: "Natural-language search query. Supports Chinese intent terms (验收/改文件/发布/状态/差异/卡住/旧任务/搜索/工具/诊断 etc.) and English keywords.",
560
+ },
561
+ profile: {
562
+ type: "string",
563
+ enum: ["full", "chatgpt_core", "chatgpt_direct", "chatgpt_search"],
564
+ description: "Filter tools by profile. Default: no filter (all profiles).",
565
+ },
566
+ mode: {
567
+ type: "string",
568
+ enum: ["delegate", "direct", "audit", "release", "diagnostic"],
569
+ description: "Filter tools by mode. Default: no filter.",
570
+ },
571
+ maxResults: {
572
+ type: "number",
573
+ minimum: 1,
574
+ maximum: 50,
575
+ default: 8,
576
+ description: "Maximum number of results. Default 8.",
577
+ },
578
+ riskCeiling: {
579
+ type: "string",
580
+ enum: ["readonly", "workspace_read_sensitive", "workspace_write", "command", "release", "credential_sensitive"],
581
+ description: "Maximum risk level to include. Tools above this level are hidden. Overrides includeHighRisk.",
582
+ },
583
+ includeHighRisk: {
584
+ type: "boolean",
585
+ default: false,
586
+ description: "When true, include high-risk tools (command/release/credential_sensitive) in results. Default false.",
587
+ },
588
+ },
589
+ required: ["query"],
590
+ },
591
+ },
592
+ {
593
+ name: "explain_tool",
594
+ description:
595
+ "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.",
596
+ inputSchema: {
597
+ type: "object",
598
+ properties: {
599
+ name: {
600
+ type: "string",
601
+ description: "Tool name or alias to explain. Accepts both the canonical name (e.g. 'create_task') and aliases (e.g. 'new_task').",
602
+ },
603
+ includeSchema: {
604
+ type: "boolean",
605
+ default: false,
606
+ description: "When true, include the full inputSchema in the response. Default false to keep output minimal.",
607
+ },
608
+ },
609
+ required: ["name"],
610
+ },
611
+ },
612
+ {
613
+ name: "invoke_discovered_tool",
614
+ description:
615
+ "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.",
616
+ inputSchema: {
617
+ type: "object",
618
+ properties: {
619
+ toolName: { type: "string", description: "Name of the tool to invoke (must match the discoveryToken's toolName)." },
620
+ arguments: {
621
+ type: "object",
622
+ description: "Arguments to pass to the tool. Must match the tool's inputSchema.",
623
+ additionalProperties: true,
624
+ },
625
+ discoveryToken: { type: "string", description: "Token id from discover_tools. Single-use, expires after 10 minutes." },
626
+ assessmentId: { type: "string", description: "Required for workspace_write/release risk tools. Obtained from the assessment flow." },
627
+ },
628
+ required: ["toolName", "arguments", "discoveryToken"],
629
+ },
630
+ },
631
+ {
632
+ name: "create_goal",
633
+ description:
634
+ "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.",
635
+ inputSchema: {
636
+ type: "object",
637
+ properties: {
638
+ title: { type: "string", description: "Goal title (human-readable)." },
639
+ goal_description: { type: "string", description: "Markdown description of the goal, success criteria, and context." },
640
+ repo_path: { type: "string", description: "Repository path inside workspaceRoot. Must be inside the configured workspace." },
641
+ },
642
+ required: ["title", "goal_description", "repo_path"],
643
+ },
644
+ },
645
+ {
646
+ name: "list_goals",
647
+ description:
648
+ "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.",
649
+ inputSchema: {
650
+ type: "object",
651
+ properties: {},
652
+ },
653
+ },
654
+ {
655
+ name: "read_goal",
656
+ description:
657
+ "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.",
658
+ inputSchema: {
659
+ type: "object",
660
+ properties: {
661
+ goal_id: { type: "string", description: "Goal ID from list_goals or create_goal." },
662
+ },
663
+ required: ["goal_id"],
664
+ },
665
+ },
666
+ {
667
+ name: "create_subgoal_task",
668
+ description:
669
+ "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.",
670
+ inputSchema: {
671
+ type: "object",
672
+ properties: {
673
+ goal_id: { type: "string", description: "Goal ID to add the subgoal to." },
674
+ subgoal_title: { type: "string", description: "Title of the new subgoal." },
675
+ depends_on: {
676
+ type: "array",
677
+ items: { type: "string" },
678
+ description: "Subgoal IDs this subgoal depends on. Dependencies must be accepted before this subgoal is suggested.",
679
+ },
680
+ plan_id: { type: "string", description: "Plan ID from save_plan. One of plan_id/inline_plan/template+goal is required." },
681
+ inline_plan: { type: "string", description: "Inline Markdown plan." },
682
+ plan_title: { type: "string", description: "Optional title for inline_plan." },
683
+ template: {
684
+ type: "string",
685
+ enum: [...TASK_TEMPLATE_NAMES],
686
+ description: "Built-in task template. Use with goal.",
687
+ },
688
+ goal: { type: "string", description: "Task goal when template is supplied." },
689
+ agent: { type: "string", description: "Agent name." },
690
+ repo_path: { type: "string", description: "Repository path inside workspaceRoot." },
691
+ test_command: { type: "string", description: "Verification command." },
692
+ verify_commands: {
693
+ type: "array",
694
+ maxItems: 20,
695
+ items: { type: "string" },
696
+ description: "Additional verification commands.",
697
+ },
698
+ timeout_seconds: { type: "integer", minimum: 1, description: "Task timeout in seconds." },
699
+ scope: { type: "array", items: { type: "string" }, description: "Allowed file/directory scope." },
700
+ forbidden: { type: "array", items: { type: "string" }, description: "Forbidden file/directory paths." },
701
+ verification: { type: "array", items: { type: "string" }, description: "Acceptance verification commands." },
702
+ done_evidence: { type: "array", items: { type: "string" }, description: "Required done evidence files." },
703
+ 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)." },
704
+ },
705
+ required: ["goal_id", "subgoal_title", "repo_path"],
706
+ },
707
+ },
708
+ {
709
+ name: "accept_subgoal",
710
+ description:
711
+ "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.",
712
+ inputSchema: {
713
+ type: "object",
714
+ properties: {
715
+ goal_id: { type: "string", description: "Goal ID." },
716
+ subgoal_id: { type: "string", description: "Subgoal ID to accept." },
717
+ },
718
+ required: ["goal_id", "subgoal_id"],
719
+ },
720
+ },
721
+ {
722
+ name: "reject_subgoal",
723
+ description:
724
+ "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.",
725
+ inputSchema: {
726
+ type: "object",
727
+ properties: {
728
+ goal_id: { type: "string", description: "Goal ID." },
729
+ subgoal_id: { type: "string", description: "Subgoal ID to reject." },
730
+ reason: { type: "string", description: "Rejection reason (required)." },
731
+ },
732
+ required: ["goal_id", "subgoal_id", "reason"],
733
+ },
734
+ },
735
+ {
736
+ name: "suggest_next_subgoal",
737
+ description:
738
+ "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.",
739
+ inputSchema: {
740
+ type: "object",
741
+ properties: {
742
+ goal_id: { type: "string", description: "Goal ID." },
743
+ },
744
+ required: ["goal_id"],
745
+ },
746
+ },
747
+ {
748
+ name: "summarize_goal_progress",
749
+ description:
750
+ "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).",
751
+ inputSchema: {
752
+ type: "object",
753
+ properties: {
754
+ goal_id: { type: "string", description: "Goal ID." },
755
+ },
756
+ required: ["goal_id"],
757
+ },
758
+ },
759
+ {
760
+ name: "export_handoff",
761
+ description:
762
+ "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.",
763
+ inputSchema: {
764
+ type: "object",
765
+ properties: {
766
+ goal_id: { type: "string", description: "Goal ID." },
767
+ },
768
+ required: ["goal_id"],
769
+ },
770
+ },
771
+ {
772
+ name: "check_release_gate",
773
+ description:
774
+ "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.",
775
+ inputSchema: {
776
+ type: "object",
777
+ properties: {
778
+ repo_path: { type: "string", description: "Repository path inside workspaceRoot." },
779
+ target_stage: {
780
+ type: "string",
781
+ enum: ["local_ready", "packed_ready", "published_verified", "github_release_verified", "ci_verified"],
782
+ description: "Target stage to verify. Stages before target are checked; stages after a failure are 'not_checked'.",
783
+ },
784
+ package_name: { type: "string", description: "npm package name for published_verified (e.g. 'patchwarden'). Required for published_verified stage." },
785
+ version: { type: "string", description: "Version string for published_verified (e.g. '1.0.0'). Required for published_verified stage." },
786
+ github_repo: { type: "string", description: "GitHub repo in 'owner/repo' form for github_release_verified and ci_verified." },
787
+ branch: { type: "string", description: "Git branch for ci_verified (e.g. 'main')." },
788
+ },
789
+ required: ["repo_path", "target_stage"],
790
+ },
791
+ },
792
+ {
793
+ name: "merge_worktree",
794
+ description:
795
+ "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).",
796
+ inputSchema: {
797
+ type: "object",
798
+ properties: {
799
+ worktree_id: { type: "string", description: "Worktree ID (wt_...) from create_subgoal_task." },
800
+ repo_path: { type: "string", description: "Main workspace repository path inside workspaceRoot." },
801
+ },
802
+ required: ["worktree_id", "repo_path"],
803
+ },
804
+ },
805
+ {
806
+ name: "discard_worktree",
807
+ description:
808
+ "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.",
809
+ inputSchema: {
810
+ type: "object",
811
+ properties: {
812
+ worktree_id: { type: "string", description: "Worktree ID (wt_...) to discard." },
813
+ repo_path: { type: "string", description: "Main workspace repository path inside workspaceRoot." },
814
+ },
815
+ required: ["worktree_id", "repo_path"],
816
+ },
817
+ },
433
818
  ];
434
819
 
435
820
  // Direct session tools
@@ -549,6 +934,47 @@ export function getToolDefs(): ToolDef[] {
549
934
  },
550
935
  });
551
936
 
937
+ tools.push({
938
+ name: "safe_direct_summary",
939
+ description:
940
+ "Return a low-noise Direct session summary without diff content or verification stdout/stderr tails.",
941
+ inputSchema: {
942
+ type: "object",
943
+ properties: {
944
+ session_id: { type: "string", description: "Direct session ID" },
945
+ max_items: { type: "integer", minimum: 1, maximum: 50, default: 8, description: "Maximum list entries to return per evidence group." },
946
+ },
947
+ required: ["session_id"],
948
+ },
949
+ });
950
+
951
+ tools.push({
952
+ name: "safe_finalize_direct_session",
953
+ description:
954
+ "Finalize a Direct session and return only bounded structured evidence, omitting diff and verification log content.",
955
+ inputSchema: {
956
+ type: "object",
957
+ properties: {
958
+ session_id: { type: "string", description: "Direct session ID to finalize" },
959
+ max_items: { type: "integer", minimum: 1, maximum: 50, default: 8, description: "Maximum list entries to return per evidence group." },
960
+ },
961
+ required: ["session_id"],
962
+ },
963
+ });
964
+
965
+ tools.push({
966
+ name: "safe_audit_direct_session",
967
+ description:
968
+ "Audit a Direct session and return only bounded structured evidence without verification stdout/stderr tails.",
969
+ inputSchema: {
970
+ type: "object",
971
+ properties: {
972
+ session_id: { type: "string", description: "Direct session ID to audit" },
973
+ max_items: { type: "integer", minimum: 1, maximum: 50, default: 8, description: "Maximum list entries to return per evidence group." },
974
+ },
975
+ required: ["session_id"],
976
+ },
977
+ });
552
978
  tools.push({
553
979
  name: "sync_file",
554
980
  description:
@@ -775,6 +1201,83 @@ async function handleToolCallInternal(name: string, args: Record<string, unknown
775
1201
  return toResult(safeStatus(String(args?.task_id ?? "")));
776
1202
  }
777
1203
 
1204
+ case "safe_result": {
1205
+ return toResult(safeResult(String(args?.task_id ?? ""), {
1206
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
1207
+ }));
1208
+ }
1209
+
1210
+ case "safe_audit": {
1211
+ return toResult(safeAudit(String(args?.task_id ?? ""), {
1212
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
1213
+ }));
1214
+ }
1215
+
1216
+ case "safe_test_summary": {
1217
+ return toResult(safeTestSummary(String(args?.task_id ?? "")));
1218
+ }
1219
+
1220
+ case "safe_diff_summary": {
1221
+ return toResult(safeDiffSummary(String(args?.task_id ?? ""), {
1222
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
1223
+ }));
1224
+ }
1225
+ case "diagnose_task": {
1226
+ return toResult(diagnoseTask({
1227
+ task_id: String(args?.task_id ?? ""),
1228
+ include_logs: args?.include_logs !== undefined ? Boolean(args.include_logs) : undefined,
1229
+ }));
1230
+ }
1231
+
1232
+ case "reconcile_tasks": {
1233
+ return toResult(reconcileTasks({
1234
+ mode: args?.mode === "safe_fix" ? "safe_fix" : "report_only",
1235
+ max_age_minutes: args?.max_age_minutes !== undefined ? Number(args.max_age_minutes) : undefined,
1236
+ include_done_candidates: args?.include_done_candidates !== undefined ? Boolean(args.include_done_candidates) : undefined,
1237
+ }));
1238
+ }
1239
+
1240
+ case "discover_tools": {
1241
+ const profile = args?.profile === "full" || args?.profile === "chatgpt_core" || args?.profile === "chatgpt_direct" || args?.profile === "chatgpt_search"
1242
+ ? args.profile : undefined;
1243
+ const mode = args?.mode === "delegate" || args?.mode === "direct" || args?.mode === "audit" || args?.mode === "release" || args?.mode === "diagnostic"
1244
+ ? args.mode : undefined;
1245
+ const riskCeiling = ["readonly", "workspace_read_sensitive", "workspace_write", "command", "release", "credential_sensitive"]
1246
+ .includes(String(args?.riskCeiling ?? "")) ? String(args?.riskCeiling) as any : undefined;
1247
+ return toResult(discoverTools({
1248
+ query: String(args?.query ?? ""),
1249
+ profile,
1250
+ mode,
1251
+ maxResults: args?.maxResults !== undefined ? Number(args.maxResults) : undefined,
1252
+ riskCeiling,
1253
+ includeHighRisk: args?.includeHighRisk !== undefined ? Boolean(args.includeHighRisk) : undefined,
1254
+ }, getToolDefs()));
1255
+ }
1256
+
1257
+ case "explain_tool": {
1258
+ return toResult(explainTool({
1259
+ name: String(args?.name ?? ""),
1260
+ includeSchema: args?.includeSchema !== undefined ? Boolean(args.includeSchema) : undefined,
1261
+ }, getToolDefs()));
1262
+ }
1263
+
1264
+ case "invoke_discovered_tool": {
1265
+ const profile = resolveToolProfile(getConfig().toolProfile);
1266
+ const result = await invokeDiscoveredTool({
1267
+ toolName: String(args?.toolName ?? ""),
1268
+ arguments: (args?.arguments && typeof args.arguments === "object" ? args.arguments : {}) as Record<string, unknown>,
1269
+ discoveryToken: String(args?.discoveryToken ?? ""),
1270
+ assessmentId: args?.assessmentId ? String(args.assessmentId) : undefined,
1271
+ }, {
1272
+ tools: getToolDefs(),
1273
+ profile,
1274
+ dispatch: async (name, dispatchArgs) => {
1275
+ return handleToolCall(name, dispatchArgs);
1276
+ },
1277
+ });
1278
+ return toResult(result);
1279
+ }
1280
+
778
1281
  case "run_task": {
779
1282
  const config = getConfig();
780
1283
  if ((config as any).enableRunTaskTool !== true) {
@@ -840,6 +1343,26 @@ async function handleToolCallInternal(name: string, args: Record<string, unknown
840
1343
  }));
841
1344
  }
842
1345
 
1346
+ case "safe_direct_summary": {
1347
+ guardDirectProfileEnabled();
1348
+ return toResult(safeDirectSummary(String(args?.session_id ?? ""), {
1349
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
1350
+ }));
1351
+ }
1352
+
1353
+ case "safe_finalize_direct_session": {
1354
+ guardDirectProfileEnabled();
1355
+ return toResult(safeFinalizeDirectSession(String(args?.session_id ?? ""), {
1356
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
1357
+ }));
1358
+ }
1359
+
1360
+ case "safe_audit_direct_session": {
1361
+ guardDirectProfileEnabled();
1362
+ return toResult(safeAuditDirectSession(String(args?.session_id ?? ""), {
1363
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
1364
+ }));
1365
+ }
843
1366
  case "sync_file": {
844
1367
  guardDirectProfileEnabled();
845
1368
  return toResult(syncFile(
@@ -853,6 +1376,100 @@ async function handleToolCallInternal(name: string, args: Record<string, unknown
853
1376
  ));
854
1377
  }
855
1378
 
1379
+ case "create_goal": {
1380
+ return toResult(createGoal(
1381
+ String(args?.repo_path ?? ""),
1382
+ String(args?.title ?? ""),
1383
+ String(args?.goal_description ?? "")
1384
+ ));
1385
+ }
1386
+
1387
+ case "list_goals": {
1388
+ return toResult({ goals: listGoals() });
1389
+ }
1390
+
1391
+ case "read_goal": {
1392
+ return toResult(readGoal(String(args?.goal_id ?? "")));
1393
+ }
1394
+
1395
+ case "create_subgoal_task": {
1396
+ return toResult(createSubgoalTask({
1397
+ goal_id: String(args?.goal_id ?? ""),
1398
+ subgoal_title: String(args?.subgoal_title ?? ""),
1399
+ depends_on: Array.isArray(args?.depends_on) ? args.depends_on.map(String) : undefined,
1400
+ plan_id: args?.plan_id ? String(args.plan_id) : undefined,
1401
+ inline_plan: args?.inline_plan ? String(args.inline_plan) : undefined,
1402
+ plan_title: args?.plan_title ? String(args.plan_title) : undefined,
1403
+ template: args?.template ? String(args.template) as any : undefined,
1404
+ goal: args?.goal ? String(args.goal) : undefined,
1405
+ agent: args?.agent ? String(args.agent) : undefined,
1406
+ repo_path: String(args?.repo_path ?? ""),
1407
+ test_command: args?.test_command ? String(args.test_command) : undefined,
1408
+ verify_commands: Array.isArray(args?.verify_commands) ? args.verify_commands.map(String) : undefined,
1409
+ timeout_seconds: args?.timeout_seconds ? Number(args.timeout_seconds) : undefined,
1410
+ scope: Array.isArray(args?.scope) ? args.scope.map(String) : undefined,
1411
+ forbidden: Array.isArray(args?.forbidden) ? args.forbidden.map(String) : undefined,
1412
+ verification: Array.isArray(args?.verification) ? args.verification.map(String) : undefined,
1413
+ done_evidence: Array.isArray(args?.done_evidence) ? args.done_evidence.map(String) : undefined,
1414
+ isolate_worktree: args?.isolate_worktree === undefined ? undefined : Boolean(args.isolate_worktree),
1415
+ }));
1416
+ }
1417
+
1418
+ case "accept_subgoal": {
1419
+ return toResult(acceptSubgoal(
1420
+ String(args?.goal_id ?? ""),
1421
+ String(args?.subgoal_id ?? "")
1422
+ ));
1423
+ }
1424
+
1425
+ case "reject_subgoal": {
1426
+ return toResult(rejectSubgoal(
1427
+ String(args?.goal_id ?? ""),
1428
+ String(args?.subgoal_id ?? ""),
1429
+ String(args?.reason ?? "")
1430
+ ));
1431
+ }
1432
+
1433
+ case "suggest_next_subgoal": {
1434
+ const goalStatus = readGoalStatus(String(args?.goal_id ?? ""));
1435
+ return toResult(suggestNextSubgoal(goalStatus));
1436
+ }
1437
+
1438
+ case "summarize_goal_progress": {
1439
+ return toResult(summarizeGoalProgress(String(args?.goal_id ?? "")));
1440
+ }
1441
+
1442
+ case "export_handoff": {
1443
+ const goalId = String(args?.goal_id ?? "");
1444
+ const goalStatus = readGoalStatus(goalId);
1445
+ return toResult(exportHandoff(goalId, goalStatus));
1446
+ }
1447
+
1448
+ case "check_release_gate": {
1449
+ return toResult(await checkReleaseGate({
1450
+ repo_path: String(args?.repo_path ?? ""),
1451
+ target_stage: String(args?.target_stage ?? "local_ready") as any,
1452
+ package_name: args?.package_name ? String(args.package_name) : undefined,
1453
+ version: args?.version ? String(args.version) : undefined,
1454
+ github_repo: args?.github_repo ? String(args.github_repo) : undefined,
1455
+ branch: args?.branch ? String(args.branch) : undefined,
1456
+ }));
1457
+ }
1458
+
1459
+ case "merge_worktree": {
1460
+ return toResult(mergeWorktreeTool({
1461
+ worktree_id: String(args?.worktree_id ?? ""),
1462
+ repo_path: String(args?.repo_path ?? ""),
1463
+ }));
1464
+ }
1465
+
1466
+ case "discard_worktree": {
1467
+ return toResult(discardWorktreeTool({
1468
+ worktree_id: String(args?.worktree_id ?? ""),
1469
+ repo_path: String(args?.repo_path ?? ""),
1470
+ }));
1471
+ }
1472
+
856
1473
  default:
857
1474
  throw new Error(`Unknown tool: ${name}`);
858
1475
  }