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
@@ -3,7 +3,7 @@ import { resolve, join } from "node:path";
3
3
  import { getTasksDir, getPlansDir, getConfig } from "../config.js";
4
4
  import { guardPath } from "../security/pathGuard.js";
5
5
  import { readTaskRuntime } from "../taskRuntime.js";
6
- import type { TaskPhase, TaskStatus } from "./createTask.js";
6
+ import type { TaskPhase, TaskStatus, AcceptanceStatus } from "./createTask.js";
7
7
  import {
8
8
  derivePendingReason,
9
9
  readWatcherStatus,
@@ -19,6 +19,7 @@ export interface TaskEntry {
19
19
  agent: string;
20
20
  status: TaskStatus;
21
21
  phase: TaskPhase;
22
+ acceptance_status: AcceptanceStatus;
22
23
  created_at: string;
23
24
  updated_at: string;
24
25
  workspace_root: string;
@@ -38,6 +39,7 @@ export interface ListTasksInput {
38
39
  status?: string;
39
40
  repo_path?: string;
40
41
  active_only?: boolean;
42
+ acceptance_status?: string;
41
43
  limit?: number;
42
44
  }
43
45
 
@@ -54,6 +56,7 @@ export function listTasks(input?: ListTasksInput): ListTasksOutput {
54
56
  const plansDir = getPlansDir(config);
55
57
  const limit = input?.limit && input.limit > 0 ? Math.min(input.limit, 100) : 20;
56
58
  const filterStatus = input?.status || null;
59
+ const filterAcceptance = input?.acceptance_status || null;
57
60
  const filterRepo = input?.repo_path?.trim().replace(/\\/g, "/") || null;
58
61
  const watcher = readWatcherStatus(config);
59
62
 
@@ -88,7 +91,13 @@ export function listTasks(input?: ListTasksInput): ListTasksOutput {
88
91
  const data = JSON.parse(readFileSync(statusFile, "utf-8"));
89
92
  const runtime = readTaskRuntime(taskDir);
90
93
  if (filterStatus && data.status !== filterStatus) continue;
91
- if (input?.active_only && data.status !== "pending" && data.status !== "running") continue;
94
+ if (filterAcceptance) {
95
+ const taskAcceptance = data.status === "done_by_agent"
96
+ ? (typeof data.acceptance_status === "string" ? data.acceptance_status : "pending")
97
+ : null;
98
+ if (taskAcceptance !== filterAcceptance) continue;
99
+ }
100
+ if (input?.active_only && !["pending", "running", "collecting_artifacts"].includes(String(data.status))) continue;
92
101
  const normalizedRepo = String(data.repo_path || ".").replace(/\\/g, "/");
93
102
  const normalizedResolvedRepo = String(data.resolved_repo_path || "").replace(/\\/g, "/");
94
103
  if (filterRepo && normalizedRepo !== filterRepo && normalizedResolvedRepo !== filterRepo) continue;
@@ -109,6 +118,10 @@ export function listTasks(input?: ListTasksInput): ListTasksOutput {
109
118
  }
110
119
 
111
120
  const phase = runtime.phase || data.phase || "queued";
121
+ const VALID_ACCEPTANCE = ["pending", "accepted", "rejected", "needs_fix", "blocked"];
122
+ const acceptanceStatus: AcceptanceStatus = data.status === "done_by_agent"
123
+ ? (typeof data.acceptance_status === "string" && VALID_ACCEPTANCE.includes(data.acceptance_status) ? data.acceptance_status : "pending")
124
+ : null;
112
125
  tasks.push({
113
126
  task_id: taskId,
114
127
  plan_id: data.plan_id || "",
@@ -116,6 +129,7 @@ export function listTasks(input?: ListTasksInput): ListTasksOutput {
116
129
  agent: data.agent || "",
117
130
  status: data.status || "pending",
118
131
  phase,
132
+ acceptance_status: acceptanceStatus,
119
133
  created_at: data.created_at || "",
120
134
  updated_at: data.updated_at || "",
121
135
  workspace_root: data.workspace_root || config.workspaceRoot,
@@ -1,71 +1,71 @@
1
- import { readdirSync, statSync, existsSync } from "node:fs";
2
- import { resolve } from "node:path";
3
- import { getConfig } from "../config.js";
4
- import { guardPath } from "../security/pathGuard.js";
5
- import { guardSensitivePath } from "../security/sensitiveGuard.js";
6
-
7
- export interface ListEntry {
8
- name: string;
9
- type: "file" | "directory";
10
- size?: number;
11
- }
12
-
13
- export interface ListWorkspaceOutput {
14
- path: string;
15
- entries: ListEntry[];
16
- }
17
-
18
- const MAX_LIST_FILES = 200;
19
-
20
- export function listWorkspace(relativePath?: string): ListWorkspaceOutput {
21
- const config = getConfig();
22
- const targetPath = relativePath
23
- ? resolve(config.workspaceRoot, relativePath)
24
- : config.workspaceRoot;
25
-
26
- const guarded = guardPath(targetPath, config.workspaceRoot);
27
-
28
- if (!existsSync(guarded)) {
29
- throw new Error(`Path not found: "${relativePath || "."}"`);
30
- }
31
-
32
- const stat = statSync(guarded);
33
- if (!stat.isDirectory()) {
34
- throw new Error(`Not a directory: "${relativePath || "."}"`);
35
- }
36
-
37
- const entries: ListEntry[] = [];
38
- const raw = readdirSync(guarded);
39
-
40
- for (const name of raw) {
41
- // Skip hidden files and sensitive paths
42
- const fullPath = resolve(guarded, name);
43
- try {
44
- guardSensitivePath(fullPath);
45
- } catch {
46
- continue; // skip sensitive files silently
47
- }
48
-
49
- try {
50
- const s = statSync(fullPath);
51
- entries.push({
52
- name,
53
- type: s.isDirectory() ? "directory" : "file",
54
- size: s.isFile() ? s.size : undefined,
55
- });
56
- } catch {
57
- // can't stat, skip
58
- }
59
-
60
- if (entries.length >= MAX_LIST_FILES) break;
61
- }
62
-
63
- return {
64
- path: relativePath || ".",
65
- entries: entries.sort((a, b) => {
66
- // directories first, then alphabetically
67
- if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
68
- return a.name.localeCompare(b.name);
69
- }),
70
- };
71
- }
1
+ import { readdirSync, statSync, existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { getConfig } from "../config.js";
4
+ import { guardPath } from "../security/pathGuard.js";
5
+ import { guardSensitivePath } from "../security/sensitiveGuard.js";
6
+
7
+ export interface ListEntry {
8
+ name: string;
9
+ type: "file" | "directory";
10
+ size?: number;
11
+ }
12
+
13
+ export interface ListWorkspaceOutput {
14
+ path: string;
15
+ entries: ListEntry[];
16
+ }
17
+
18
+ const MAX_LIST_FILES = 200;
19
+
20
+ export function listWorkspace(relativePath?: string): ListWorkspaceOutput {
21
+ const config = getConfig();
22
+ const targetPath = relativePath
23
+ ? resolve(config.workspaceRoot, relativePath)
24
+ : config.workspaceRoot;
25
+
26
+ const guarded = guardPath(targetPath, config.workspaceRoot);
27
+
28
+ if (!existsSync(guarded)) {
29
+ throw new Error(`Path not found: "${relativePath || "."}"`);
30
+ }
31
+
32
+ const stat = statSync(guarded);
33
+ if (!stat.isDirectory()) {
34
+ throw new Error(`Not a directory: "${relativePath || "."}"`);
35
+ }
36
+
37
+ const entries: ListEntry[] = [];
38
+ const raw = readdirSync(guarded);
39
+
40
+ for (const name of raw) {
41
+ // Skip hidden files and sensitive paths
42
+ const fullPath = resolve(guarded, name);
43
+ try {
44
+ guardSensitivePath(fullPath);
45
+ } catch {
46
+ continue; // skip sensitive files silently
47
+ }
48
+
49
+ try {
50
+ const s = statSync(fullPath);
51
+ entries.push({
52
+ name,
53
+ type: s.isDirectory() ? "directory" : "file",
54
+ size: s.isFile() ? s.size : undefined,
55
+ });
56
+ } catch {
57
+ // can't stat, skip
58
+ }
59
+
60
+ if (entries.length >= MAX_LIST_FILES) break;
61
+ }
62
+
63
+ return {
64
+ path: relativePath || ".",
65
+ entries: entries.sort((a, b) => {
66
+ // directories first, then alphabetically
67
+ if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
68
+ return a.name.localeCompare(b.name);
69
+ }),
70
+ };
71
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * v1.0.0 Part B: merge_worktree MCP 工具 handler — 把隔离 worktree 的 branch
3
+ * 合并回主工作区,并将 worktree 状态置为 "merged"。
4
+ *
5
+ * 参考 src/tools/auditTask.ts 的导出/返回风格。
6
+ */
7
+
8
+ import { getConfig } from "../config.js";
9
+ import { guardWorkspacePath } from "../security/pathGuard.js";
10
+ import { mergeWorktree } from "../goal/worktreeManager.js";
11
+
12
+ export interface MergeWorktreeInput {
13
+ worktree_id: string;
14
+ repo_path: string;
15
+ }
16
+
17
+ export interface MergeWorktreeOutput {
18
+ worktree_id: string;
19
+ status: "merged";
20
+ merged_at: string;
21
+ }
22
+
23
+ /**
24
+ * 合并指定 worktree 的 branch 回 repo_path(主工作区)。
25
+ *
26
+ * @throws PatchWardenError("workspace_path_escape") 当 repo_path 逃逸出 workspaceRoot
27
+ * @throws PatchWardenError("worktree_not_found") 当 worktree 不存在
28
+ * @throws PatchWardenError("invalid_worktree_state") 当 worktree 非 active
29
+ * @throws PatchWardenError("worktree_merge_failed") 当 git merge 失败
30
+ */
31
+ export function mergeWorktreeTool(input: MergeWorktreeInput): MergeWorktreeOutput {
32
+ const config = getConfig();
33
+ const repoPathSafe = guardWorkspacePath(input.repo_path, config.workspaceRoot);
34
+
35
+ mergeWorktree(input.worktree_id, repoPathSafe);
36
+
37
+ return {
38
+ worktree_id: input.worktree_id,
39
+ status: "merged",
40
+ merged_at: new Date().toISOString(),
41
+ };
42
+ }
@@ -0,0 +1,79 @@
1
+ import { getConfig } from "../config.js";
2
+ import { guardWorkspacePath } from "../security/pathGuard.js";
3
+ import { routeAgent } from "../agents/agentRouter.js";
4
+
5
+ export interface RecommendAgentForTaskInput {
6
+ repo_path: string;
7
+ goal: string;
8
+ scope_files?: string[];
9
+ template?: string;
10
+ risk_hint?: string;
11
+ }
12
+
13
+ export interface RecommendAgentForTaskOutput {
14
+ repo_path: string;
15
+ resolved_repo_path: string;
16
+ recommended_agent: string;
17
+ fallback_agent: string | null;
18
+ fallback: boolean;
19
+ reason: string;
20
+ risk_notes: string[];
21
+ suggested_verify_commands: string[];
22
+ bounded: true;
23
+ }
24
+
25
+ export function recommendAgentForTask(input: RecommendAgentForTaskInput): RecommendAgentForTaskOutput {
26
+ const config = getConfig();
27
+ const repoPath = String(input.repo_path || "").trim();
28
+ const goal = String(input.goal || "").trim();
29
+ if (!repoPath) throw new Error("repo_path is required.");
30
+ if (!goal) throw new Error("goal is required.");
31
+
32
+ const resolvedRepoPath = guardWorkspacePath(repoPath, config.workspaceRoot);
33
+ const configuredAgents = Object.keys(config.agents);
34
+ const route = routeAgent({
35
+ goal,
36
+ scope: normalizeScope(input.scope_files),
37
+ inline_plan: input.risk_hint,
38
+ template: input.template,
39
+ configuredAgents,
40
+ });
41
+
42
+ return {
43
+ repo_path: repoPath,
44
+ resolved_repo_path: resolvedRepoPath,
45
+ recommended_agent: route.recommended_agent,
46
+ fallback_agent: route.fallback ? route.recommended_agent : configuredAgents.find((name) => name !== route.recommended_agent) || null,
47
+ fallback: route.fallback,
48
+ reason: route.reason,
49
+ risk_notes: buildRiskNotes(goal, input.risk_hint),
50
+ suggested_verify_commands: suggestVerifyCommands(config.allowedTestCommands),
51
+ bounded: true,
52
+ };
53
+ }
54
+
55
+ function normalizeScope(value: string[] | undefined): string[] | undefined {
56
+ if (!Array.isArray(value)) return undefined;
57
+ return value.map((entry) => String(entry).trim()).filter(Boolean).slice(0, 50);
58
+ }
59
+
60
+ function buildRiskNotes(goal: string, riskHint: string | undefined): string[] {
61
+ const text = `${goal} ${riskHint || ""}`.toLowerCase();
62
+ const notes: string[] = [];
63
+ if (/release|publish|push|tag|npm/.test(text)) {
64
+ notes.push("release_or_remote_write_language_detected");
65
+ }
66
+ if (/secret|token|cookie|\.env|ssh/.test(text)) {
67
+ notes.push("sensitive_file_language_detected");
68
+ }
69
+ if (/refactor|rewrite|redesign|migration/.test(text)) {
70
+ notes.push("broad_change_language_detected");
71
+ }
72
+ return notes.slice(0, 8);
73
+ }
74
+
75
+ function suggestVerifyCommands(commands: string[]): string[] {
76
+ const preferred = ["npm test", "npm run test", "npm run build", "npm run lint"];
77
+ const configured = new Set(commands);
78
+ return preferred.filter((command) => configured.has(command)).slice(0, 4);
79
+ }