patchwarden 1.5.1 → 1.6.2

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 (591) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +158 -26
  3. package/README.md +124 -24
  4. package/dist/assessments/agentAssessor.js +21 -12
  5. package/dist/assessments/assessmentDiagnostics.d.ts +29 -0
  6. package/dist/assessments/assessmentDiagnostics.js +132 -0
  7. package/dist/assessments/assessmentStore.d.ts +39 -0
  8. package/dist/assessments/assessmentStore.js +207 -45
  9. package/dist/assessments/securitySnapshot.d.ts +50 -0
  10. package/dist/assessments/securitySnapshot.js +158 -0
  11. package/dist/config.d.ts +15 -0
  12. package/dist/config.js +66 -13
  13. package/dist/control/fileManager.d.ts +2 -0
  14. package/dist/control/fileManager.js +39 -0
  15. package/dist/control/middleware/auth.d.ts +6 -0
  16. package/dist/control/middleware/auth.js +14 -5
  17. package/dist/control/middleware/static.js +16 -3
  18. package/dist/control/routeTable.d.ts +24 -0
  19. package/dist/control/routeTable.js +263 -0
  20. package/dist/control/routes/audit.js +17 -7
  21. package/dist/control/routes/evidence.js +1 -1
  22. package/dist/control/routes/lineage.js +1 -1
  23. package/dist/control/routes/process.d.ts +7 -0
  24. package/dist/control/routes/process.js +329 -42
  25. package/dist/control/routes/sessions.d.ts +9 -1
  26. package/dist/control/routes/sessions.js +84 -21
  27. package/dist/control/routes/status.d.ts +60 -1
  28. package/dist/control/routes/status.js +94 -15
  29. package/dist/control/routes/taskActions.js +24 -60
  30. package/dist/control/routes/tasks.d.ts +4 -0
  31. package/dist/control/routes/tasks.js +61 -62
  32. package/dist/control/routes/workspace.js +50 -23
  33. package/dist/control/runtime.d.ts +3 -2
  34. package/dist/control/runtime.js +65 -40
  35. package/dist/control/server.js +37 -365
  36. package/dist/control/shared.d.ts +25 -1
  37. package/dist/control/shared.js +64 -11
  38. package/dist/diagnostics/allowedTestCommandSafety.d.ts +1 -0
  39. package/dist/diagnostics/allowedTestCommandSafety.js +11 -0
  40. package/dist/direct/directAudit.js +234 -225
  41. package/dist/direct/directGuards.d.ts +2 -1
  42. package/dist/direct/directGuards.js +42 -29
  43. package/dist/direct/directPatch.d.ts +6 -1
  44. package/dist/direct/directPatch.js +55 -8
  45. package/dist/direct/directSessionStore.d.ts +16 -6
  46. package/dist/direct/directSessionStore.js +128 -35
  47. package/dist/direct/directVerification.js +8 -16
  48. package/dist/doctor.js +65 -57
  49. package/dist/goal/acceptanceEngine.d.ts +1 -1
  50. package/dist/goal/goalProgress.js +75 -69
  51. package/dist/goal/goalReport.js +5 -12
  52. package/dist/goal/goalStatus.d.ts +2 -0
  53. package/dist/goal/goalStatus.js +10 -3
  54. package/dist/goal/goalStore.d.ts +9 -2
  55. package/dist/goal/goalStore.js +152 -45
  56. package/dist/goal/handoffExport.js +6 -6
  57. package/dist/goal/specKitImport.js +97 -96
  58. package/dist/goal/subgoalSync.js +11 -9
  59. package/dist/goal/worktreeManager.d.ts +2 -0
  60. package/dist/goal/worktreeManager.js +209 -136
  61. package/dist/httpServer.js +111 -32
  62. package/dist/logging.js +5 -3
  63. package/dist/policy/projectPolicy.js +5 -2
  64. package/dist/release/releaseGate.d.ts +1 -1
  65. package/dist/release/releaseGate.js +78 -25
  66. package/dist/runner/agentInvocation.d.ts +14 -0
  67. package/dist/runner/agentInvocation.js +96 -4
  68. package/dist/runner/changeCapture.d.ts +13 -5
  69. package/dist/runner/changeCapture.js +243 -61
  70. package/dist/runner/postTaskCleanup.js +48 -19
  71. package/dist/runner/processSecurity.d.ts +56 -0
  72. package/dist/runner/processSecurity.js +373 -0
  73. package/dist/runner/runTask.d.ts +2 -1
  74. package/dist/runner/runTask.js +162 -93
  75. package/dist/runner/simpleProcess.d.ts +3 -0
  76. package/dist/runner/simpleProcess.js +50 -36
  77. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  78. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  79. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +3 -1
  80. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  81. package/dist/runner/taskStatusStore.d.ts +11 -0
  82. package/dist/runner/taskStatusStore.js +35 -0
  83. package/dist/runner/watch.d.ts +14 -1
  84. package/dist/runner/watch.js +320 -49
  85. package/dist/security/contentRedaction.js +2 -2
  86. package/dist/security/discoveryTokenStore.d.ts +3 -2
  87. package/dist/security/discoveryTokenStore.js +17 -2
  88. package/dist/security/loopbackHost.d.ts +2 -0
  89. package/dist/security/loopbackHost.js +16 -0
  90. package/dist/security/pathGuard.js +32 -17
  91. package/dist/security/planGuard.js +2 -1
  92. package/dist/security/runtimeGuard.js +9 -6
  93. package/dist/security/secretComparison.d.ts +3 -0
  94. package/dist/security/secretComparison.js +11 -0
  95. package/dist/security/sensitiveGuard.d.ts +5 -0
  96. package/dist/security/sensitiveGuard.js +27 -8
  97. package/dist/security/toolInvocationGuard.d.ts +2 -2
  98. package/dist/security/toolInvocationGuard.js +1 -1
  99. package/dist/security/workspaceRootGuard.d.ts +8 -0
  100. package/dist/security/workspaceRootGuard.js +47 -0
  101. package/dist/smoke-test.js +27 -26
  102. package/dist/tools/catalog/index.d.ts +4 -0
  103. package/dist/tools/catalog/index.js +4 -0
  104. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  105. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  106. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +6 -15
  107. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  108. package/dist/tools/definitions/toolDefs.js +1177 -0
  109. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  110. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  111. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +2 -1
  112. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +134 -36
  113. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +18 -6
  114. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +36 -8
  115. package/dist/tools/diagnostics/index.d.ts +6 -0
  116. package/dist/tools/diagnostics/index.js +6 -0
  117. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  118. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  119. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +12 -8
  120. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +23 -13
  121. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  122. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  123. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +6 -6
  124. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +1 -1
  125. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  126. package/dist/tools/direct/index.d.ts +3 -0
  127. package/dist/tools/direct/index.js +3 -0
  128. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  129. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  130. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  131. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  132. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  133. package/dist/tools/discovery/index.d.ts +3 -0
  134. package/dist/tools/discovery/index.js +3 -0
  135. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  136. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  137. package/dist/tools/dispatch/coreDispatch.js +32 -31
  138. package/dist/tools/dispatch/diagnosticDispatch.d.ts +3 -5
  139. package/dist/tools/dispatch/diagnosticDispatch.js +70 -68
  140. package/dist/tools/dispatch/directDispatch.js +11 -10
  141. package/dist/tools/dispatch/goalDispatch.js +5 -4
  142. package/dist/tools/dispatch/releaseDispatch.js +3 -2
  143. package/dist/tools/dispatch/validation.d.ts +7 -0
  144. package/dist/tools/dispatch/validation.js +71 -0
  145. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  146. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +1 -1
  147. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  148. package/dist/tools/goals/index.d.ts +3 -0
  149. package/dist/tools/goals/index.js +3 -0
  150. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  151. package/dist/tools/registry.d.ts +7 -11
  152. package/dist/tools/registry.js +17 -1178
  153. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  154. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  155. package/dist/tools/release/index.d.ts +2 -0
  156. package/dist/tools/release/index.js +2 -0
  157. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  158. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  159. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  160. package/dist/tools/tasks/cancelTask.js +86 -0
  161. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
  162. package/dist/tools/{createTask.js → tasks/createTask.js} +110 -34
  163. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  164. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +12 -16
  165. package/dist/tools/{evidencePack.js → tasks/evidencePack.js} +34 -22
  166. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  167. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  168. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +11 -6
  169. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  170. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +35 -36
  171. package/dist/tools/tasks/index.d.ts +18 -0
  172. package/dist/tools/tasks/index.js +18 -0
  173. package/dist/tools/tasks/killTask.d.ts +1 -0
  174. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  175. package/dist/tools/{listTasks.js → tasks/listTasks.js} +5 -4
  176. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +3 -2
  177. package/dist/tools/tasks/reconcileTasks.js +496 -0
  178. package/dist/tools/tasks/retryTask.js +60 -0
  179. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  180. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +63 -41
  181. package/dist/tools/tasks/runVerification.js +38 -0
  182. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  183. package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +2 -2
  184. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  185. package/dist/tools/tasks/taskStates.d.ts +6 -0
  186. package/dist/tools/tasks/taskStates.js +52 -0
  187. package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +3 -11
  188. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  189. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  190. package/dist/tools/workspace/applyPatch.js +53 -0
  191. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  192. package/dist/tools/workspace/index.d.ts +10 -0
  193. package/dist/tools/workspace/index.js +10 -0
  194. package/dist/tools/workspace/listAgents.d.ts +23 -0
  195. package/dist/tools/workspace/listAgents.js +56 -0
  196. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  197. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  198. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  199. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  200. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  201. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  202. package/dist/tools/workspace/syncFile.js +134 -0
  203. package/dist/utils/atomicFile.d.ts +7 -0
  204. package/dist/utils/atomicFile.js +67 -0
  205. package/dist/utils/boundedFile.d.ts +8 -0
  206. package/dist/utils/boundedFile.js +93 -0
  207. package/dist/utils/lockedJsonFile.d.ts +15 -0
  208. package/dist/utils/lockedJsonFile.js +224 -0
  209. package/dist/utils/platform.d.ts +11 -0
  210. package/dist/utils/platform.js +26 -0
  211. package/dist/utils/stableJson.d.ts +2 -0
  212. package/dist/utils/stableJson.js +12 -0
  213. package/dist/version.d.ts +3 -2
  214. package/dist/version.js +3 -2
  215. package/dist/watcherStatus.d.ts +2 -2
  216. package/dist/watcherStatus.js +18 -0
  217. package/docs/CODE_WIKI.md +808 -143
  218. package/docs/architecture.md +15 -55
  219. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  220. package/docs/control-center/README.md +4 -4
  221. package/docs/dashboard-overview.md +4 -4
  222. package/docs/desktop-app.md +175 -0
  223. package/docs/evidence-pack-schema.md +3 -3
  224. package/docs/open-source-application.md +16 -11
  225. package/docs/release-checklist.md +1 -1
  226. package/docs/release-evidence.md +35 -39
  227. package/docs/task-safe-review-workflow.md +5 -5
  228. package/docs/threat-model.md +19 -1
  229. package/examples/config.example.json +4 -2
  230. package/package.json +27 -10
  231. package/scripts/README.md +15 -6
  232. package/scripts/build.js +11 -0
  233. package/scripts/checks/brand-check.js +3 -0
  234. package/scripts/checks/build-output-check.js +37 -0
  235. package/scripts/checks/control-center-smoke.js +102 -10
  236. package/scripts/checks/control-smoke.js +19 -8
  237. package/scripts/checks/http-mcp-smoke.js +82 -4
  238. package/scripts/checks/lifecycle-smoke.js +15 -12
  239. package/scripts/checks/mcp-manifest-check.js +1 -1
  240. package/scripts/checks/mcp-smoke.js +58 -4
  241. package/scripts/checks/package-manifest-check.js +20 -7
  242. package/scripts/checks/unit-tests.js +3 -5
  243. package/scripts/checks/watcher-supervisor-smoke.js +38 -1
  244. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  245. package/scripts/control/manage-patchwarden.ps1 +69 -4
  246. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  247. package/scripts/control/run-background-supervisor.ps1 +41 -0
  248. package/scripts/control/start-patchwarden-tunnel.ps1 +191 -33
  249. package/scripts/e2e/demo-runtime.mjs +153 -0
  250. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  251. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  252. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  253. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  254. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  255. package/scripts/lib/clean-generated-output.js +11 -0
  256. package/scripts/lib/file-discovery.js +12 -0
  257. package/scripts/release/desktop-preflight.js +211 -0
  258. package/scripts/release/pack-clean.js +59 -14
  259. package/src/assessments/agentAssessor.ts +26 -12
  260. package/src/assessments/assessmentDiagnostics.ts +172 -0
  261. package/src/assessments/assessmentStore.ts +288 -51
  262. package/src/assessments/securitySnapshot.ts +231 -0
  263. package/src/config.ts +70 -13
  264. package/src/control/fileManager.ts +40 -0
  265. package/src/control/middleware/auth.ts +22 -4
  266. package/src/control/middleware/static.ts +16 -3
  267. package/src/control/routeTable.ts +301 -0
  268. package/src/control/routes/audit.ts +14 -6
  269. package/src/control/routes/evidence.ts +1 -1
  270. package/src/control/routes/lineage.ts +1 -1
  271. package/src/control/routes/process.ts +341 -40
  272. package/src/control/routes/sessions.ts +88 -25
  273. package/src/control/routes/status.ts +134 -17
  274. package/src/control/routes/taskActions.ts +24 -58
  275. package/src/control/routes/tasks.ts +66 -58
  276. package/src/control/routes/workspace.ts +56 -25
  277. package/src/control/runtime.ts +71 -43
  278. package/src/control/server.ts +37 -342
  279. package/src/control/shared.ts +77 -11
  280. package/src/diagnostics/allowedTestCommandSafety.ts +12 -0
  281. package/src/direct/directAudit.ts +17 -9
  282. package/src/direct/directGuards.ts +72 -44
  283. package/src/direct/directPatch.ts +95 -8
  284. package/src/direct/directSessionStore.ts +213 -55
  285. package/src/direct/directVerification.ts +9 -23
  286. package/src/doctor.ts +76 -64
  287. package/src/goal/acceptanceEngine.ts +1 -1
  288. package/src/goal/goalProgress.ts +17 -13
  289. package/src/goal/goalReport.ts +5 -13
  290. package/src/goal/goalStatus.ts +19 -3
  291. package/src/goal/goalStore.ts +241 -54
  292. package/src/goal/handoffExport.ts +6 -6
  293. package/src/goal/specKitImport.ts +12 -12
  294. package/src/goal/subgoalSync.ts +11 -8
  295. package/src/goal/worktreeManager.ts +139 -22
  296. package/src/httpServer.ts +113 -30
  297. package/src/logging.ts +10 -3
  298. package/src/policy/projectPolicy.ts +5 -2
  299. package/src/release/releaseGate.ts +74 -27
  300. package/src/runner/agentInvocation.ts +126 -4
  301. package/src/runner/changeCapture.ts +291 -57
  302. package/src/runner/postTaskCleanup.ts +41 -17
  303. package/src/runner/processSecurity.ts +433 -0
  304. package/src/runner/runTask.ts +197 -97
  305. package/src/runner/simpleProcess.ts +66 -31
  306. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  307. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +6 -3
  308. package/src/runner/taskStatusStore.ts +69 -0
  309. package/src/runner/watch.ts +348 -52
  310. package/src/security/contentRedaction.ts +2 -2
  311. package/src/security/discoveryTokenStore.ts +18 -4
  312. package/src/security/loopbackHost.ts +19 -0
  313. package/src/security/pathGuard.ts +35 -16
  314. package/src/security/planGuard.ts +2 -1
  315. package/src/security/runtimeGuard.ts +9 -6
  316. package/src/security/secretComparison.ts +13 -0
  317. package/src/security/sensitiveGuard.ts +27 -8
  318. package/src/security/toolInvocationGuard.ts +2 -2
  319. package/src/security/workspaceRootGuard.ts +53 -0
  320. package/src/smoke-test.ts +27 -26
  321. package/src/tools/catalog/index.ts +4 -0
  322. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  323. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +7 -16
  324. package/src/tools/definitions/toolDefs.ts +1264 -0
  325. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  326. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +136 -39
  327. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +37 -9
  328. package/src/tools/diagnostics/index.ts +6 -0
  329. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  330. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +27 -17
  331. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  332. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +6 -6
  333. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +7 -5
  334. package/src/tools/direct/index.ts +3 -0
  335. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  336. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  337. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  338. package/src/tools/discovery/index.ts +3 -0
  339. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  340. package/src/tools/dispatch/coreDispatch.ts +36 -31
  341. package/src/tools/dispatch/diagnosticDispatch.ts +26 -21
  342. package/src/tools/dispatch/directDispatch.ts +11 -10
  343. package/src/tools/dispatch/goalDispatch.ts +5 -4
  344. package/src/tools/dispatch/releaseDispatch.ts +3 -2
  345. package/src/tools/dispatch/validation.ts +78 -0
  346. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  347. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +41 -26
  348. package/src/tools/goals/index.ts +3 -0
  349. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  350. package/src/tools/registry.ts +17 -1261
  351. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  352. package/src/tools/release/index.ts +2 -0
  353. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  354. package/src/tools/tasks/cancelTask.ts +110 -0
  355. package/src/tools/{createTask.ts → tasks/createTask.ts} +161 -33
  356. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +12 -16
  357. package/src/tools/{evidencePack.ts → tasks/evidencePack.ts} +36 -22
  358. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  359. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +13 -6
  360. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  361. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +46 -44
  362. package/src/tools/tasks/index.ts +18 -0
  363. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +6 -5
  364. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +228 -77
  365. package/src/tools/tasks/retryTask.ts +69 -0
  366. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +73 -46
  367. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  368. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  369. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  370. package/src/tools/tasks/taskStates.ts +54 -0
  371. package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +3 -12
  372. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  373. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  374. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  375. package/src/tools/workspace/index.ts +10 -0
  376. package/src/tools/workspace/listAgents.ts +75 -0
  377. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  378. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  379. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  380. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  381. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  382. package/src/tools/workspace/syncFile.ts +292 -0
  383. package/src/utils/atomicFile.ts +74 -0
  384. package/src/utils/boundedFile.ts +116 -0
  385. package/src/utils/lockedJsonFile.ts +251 -0
  386. package/src/utils/platform.ts +31 -0
  387. package/src/utils/stableJson.ts +12 -0
  388. package/src/version.ts +3 -2
  389. package/src/watcherStatus.ts +19 -1
  390. package/ui/desktop-bootstrap.js +11 -0
  391. package/ui/desktop-bridge.js +36 -0
  392. package/ui/desktop.css +160 -0
  393. package/ui/getting-started.js +93 -0
  394. package/ui/i18n.js +484 -0
  395. package/ui/log-parser.js +30 -0
  396. package/ui/pages/audit.html +12 -0
  397. package/ui/pages/dashboard.html +94 -147
  398. package/ui/pages/direct-sessions.html +123 -26
  399. package/ui/pages/getting-started.html +43 -0
  400. package/ui/pages/logs.html +134 -22
  401. package/ui/pages/settings.html +101 -0
  402. package/ui/pages/task-detail.html +12 -0
  403. package/ui/pages/tasks.html +54 -8
  404. package/ui/pages/workspace.html +38 -1
  405. package/ui/settings.js +254 -0
  406. package/PatchWarden-Control.cmd +0 -6
  407. package/Restart-PatchWarden-Control.cmd +0 -6
  408. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  409. package/dist/test/unit/acceptance-engine.test.js +0 -228
  410. package/dist/test/unit/agent-router.test.d.ts +0 -1
  411. package/dist/test/unit/agent-router.test.js +0 -287
  412. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  413. package/dist/test/unit/android-doctor.test.js +0 -118
  414. package/dist/test/unit/apply-patch.test.d.ts +0 -1
  415. package/dist/test/unit/apply-patch.test.js +0 -225
  416. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  417. package/dist/test/unit/audit-checks.test.js +0 -350
  418. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  419. package/dist/test/unit/chinese-path.test.js +0 -91
  420. package/dist/test/unit/command-guard.test.d.ts +0 -1
  421. package/dist/test/unit/command-guard.test.js +0 -160
  422. package/dist/test/unit/create-task.test.d.ts +0 -1
  423. package/dist/test/unit/create-task.test.js +0 -197
  424. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  425. package/dist/test/unit/diagnose-task.test.js +0 -457
  426. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  427. package/dist/test/unit/direct-guards.test.js +0 -328
  428. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  429. package/dist/test/unit/discovery-token-store.test.js +0 -139
  430. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  431. package/dist/test/unit/evidence-pack.test.js +0 -224
  432. package/dist/test/unit/get-task-status.test.d.ts +0 -1
  433. package/dist/test/unit/get-task-status.test.js +0 -174
  434. package/dist/test/unit/get-task-summary.test.d.ts +0 -1
  435. package/dist/test/unit/get-task-summary.test.js +0 -146
  436. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  437. package/dist/test/unit/goal-graph.test.js +0 -298
  438. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  439. package/dist/test/unit/goal-progress.test.js +0 -381
  440. package/dist/test/unit/goal-report.test.d.ts +0 -1
  441. package/dist/test/unit/goal-report.test.js +0 -159
  442. package/dist/test/unit/goal-status.test.d.ts +0 -1
  443. package/dist/test/unit/goal-status.test.js +0 -215
  444. package/dist/test/unit/goal-store.test.d.ts +0 -1
  445. package/dist/test/unit/goal-store.test.js +0 -253
  446. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  447. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  448. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  449. package/dist/test/unit/goal-tools-registry.test.js +0 -192
  450. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  451. package/dist/test/unit/handoff-export.test.js +0 -263
  452. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  453. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  454. package/dist/test/unit/logging.test.d.ts +0 -1
  455. package/dist/test/unit/logging.test.js +0 -397
  456. package/dist/test/unit/path-guard.test.d.ts +0 -1
  457. package/dist/test/unit/path-guard.test.js +0 -133
  458. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  459. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  460. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  461. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  462. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  463. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  464. package/dist/test/unit/release-gate.test.d.ts +0 -1
  465. package/dist/test/unit/release-gate.test.js +0 -242
  466. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  467. package/dist/test/unit/run-task-loop.test.js +0 -380
  468. package/dist/test/unit/safe-status.test.d.ts +0 -1
  469. package/dist/test/unit/safe-status.test.js +0 -165
  470. package/dist/test/unit/safe-views.test.d.ts +0 -1
  471. package/dist/test/unit/safe-views.test.js +0 -171
  472. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  473. package/dist/test/unit/schema-drift-check.test.js +0 -176
  474. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  475. package/dist/test/unit/sensitive-guard.test.js +0 -104
  476. package/dist/test/unit/spec-kit-import.test.d.ts +0 -1
  477. package/dist/test/unit/spec-kit-import.test.js +0 -341
  478. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  479. package/dist/test/unit/subgoal-sync.test.js +0 -183
  480. package/dist/test/unit/sync-file.test.d.ts +0 -1
  481. package/dist/test/unit/sync-file.test.js +0 -154
  482. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  483. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  484. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  485. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  486. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  487. package/dist/test/unit/toolSearch.test.js +0 -571
  488. package/dist/test/unit/wait-for-task.test.d.ts +0 -1
  489. package/dist/test/unit/wait-for-task.test.js +0 -144
  490. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  491. package/dist/test/unit/watcher-status.test.js +0 -169
  492. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  493. package/dist/test/unit/worktree-manager.test.js +0 -176
  494. package/dist/tools/applyPatch.js +0 -41
  495. package/dist/tools/cancelTask.d.ts +0 -30
  496. package/dist/tools/cancelTask.js +0 -64
  497. package/dist/tools/finalizeDirectSession.js +0 -84
  498. package/dist/tools/goalSubgoalTask.js +0 -110
  499. package/dist/tools/killTask.d.ts +0 -15
  500. package/dist/tools/listAgents.d.ts +0 -12
  501. package/dist/tools/listAgents.js +0 -42
  502. package/dist/tools/reconcileTasks.js +0 -352
  503. package/dist/tools/retryTask.js +0 -45
  504. package/dist/tools/runVerification.js +0 -36
  505. package/dist/tools/syncFile.js +0 -65
  506. package/docs/control-center/control-center-daily-driver.md +0 -211
  507. package/docs/control-center/control-center-mvp.md +0 -205
  508. package/docs/control-center/control-center-phase2.md +0 -159
  509. package/docs/execution-plan-2026-07-09.md +0 -315
  510. package/docs/release-v0.6.0.md +0 -71
  511. package/docs/release-v0.6.1.md +0 -75
  512. package/docs/release-v0.6.4.md +0 -45
  513. package/docs/roadmap-execution-and-acceptance.md +0 -365
  514. package/src/test/unit/acceptance-engine.test.ts +0 -261
  515. package/src/test/unit/agent-router.test.ts +0 -342
  516. package/src/test/unit/android-doctor.test.ts +0 -158
  517. package/src/test/unit/apply-patch.test.ts +0 -293
  518. package/src/test/unit/audit-checks.test.ts +0 -567
  519. package/src/test/unit/chinese-path.test.ts +0 -106
  520. package/src/test/unit/command-guard.test.ts +0 -221
  521. package/src/test/unit/create-task.test.ts +0 -255
  522. package/src/test/unit/diagnose-task.test.ts +0 -544
  523. package/src/test/unit/direct-guards.test.ts +0 -467
  524. package/src/test/unit/discovery-token-store.test.ts +0 -181
  525. package/src/test/unit/evidence-pack.test.ts +0 -251
  526. package/src/test/unit/get-task-status.test.ts +0 -203
  527. package/src/test/unit/get-task-summary.test.ts +0 -173
  528. package/src/test/unit/goal-graph.test.ts +0 -347
  529. package/src/test/unit/goal-progress.test.ts +0 -538
  530. package/src/test/unit/goal-report.test.ts +0 -189
  531. package/src/test/unit/goal-status.test.ts +0 -270
  532. package/src/test/unit/goal-store.test.ts +0 -318
  533. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  534. package/src/test/unit/goal-tools-registry.test.ts +0 -245
  535. package/src/test/unit/handoff-export.test.ts +0 -295
  536. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  537. package/src/test/unit/logging.test.ts +0 -497
  538. package/src/test/unit/path-guard.test.ts +0 -180
  539. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  540. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  541. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  542. package/src/test/unit/release-gate.test.ts +0 -314
  543. package/src/test/unit/run-task-loop.test.ts +0 -425
  544. package/src/test/unit/safe-status.test.ts +0 -187
  545. package/src/test/unit/safe-views.test.ts +0 -184
  546. package/src/test/unit/schema-drift-check.test.ts +0 -260
  547. package/src/test/unit/sensitive-guard.test.ts +0 -124
  548. package/src/test/unit/spec-kit-import.test.ts +0 -429
  549. package/src/test/unit/subgoal-sync.test.ts +0 -236
  550. package/src/test/unit/sync-file.test.ts +0 -231
  551. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  552. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  553. package/src/test/unit/toolSearch.test.ts +0 -756
  554. package/src/test/unit/wait-for-task.test.ts +0 -176
  555. package/src/test/unit/watcher-status.test.ts +0 -190
  556. package/src/test/unit/worktree-manager.test.ts +0 -247
  557. package/src/tools/cancelTask.ts +0 -70
  558. package/src/tools/listAgents.ts +0 -54
  559. package/src/tools/retryTask.ts +0 -53
  560. package/src/tools/syncFile.ts +0 -122
  561. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  562. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  563. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  564. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  565. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  566. /package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +0 -0
  567. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  568. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  569. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  570. /package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +0 -0
  571. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  572. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  573. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  574. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  575. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  576. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  577. /package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +0 -0
  578. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  579. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  580. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  581. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  582. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  583. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  584. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  585. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  586. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  587. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  588. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  589. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  590. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  591. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
@@ -1,9 +1,9 @@
1
- import { getConfig } from "../config.js";
2
- import { guardWorkspacePath } from "../security/pathGuard.js";
1
+ import { getConfig } from "../../config.js";
2
+ import { guardWorkspacePath } from "../../security/pathGuard.js";
3
3
  import {
4
4
  runReleaseGateCheck,
5
5
  type ReleaseStage,
6
- } from "../release/releaseGate.js";
6
+ } from "../../release/releaseGate.js";
7
7
 
8
8
  export interface CheckReleaseGateInput {
9
9
  repo_path: string;
@@ -0,0 +1,2 @@
1
+ export * from "./checkReleaseGate.js";
2
+ export * from "./releaseMode.js";
@@ -1,7 +1,7 @@
1
- import { execFileSync, execSync } from "node:child_process";
2
- import { existsSync, mkdirSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, readdirSync, rmSync, statSync } from "node:fs";
3
3
  import { join, relative, resolve, sep, isAbsolute } from "node:path";
4
- import { getConfig } from "../config.js";
4
+ import { getConfig } from "../../config.js";
5
5
  import {
6
6
  checkCiVerified,
7
7
  checkGitHubReleaseVerified,
@@ -9,9 +9,12 @@ import {
9
9
  runReleaseGateCheck,
10
10
  type ReleaseStage,
11
11
  type ReleaseStageStatus,
12
- } from "../release/releaseGate.js";
13
- import { guardTestCommand } from "../security/commandGuard.js";
14
- import { guardWorkspacePath } from "../security/pathGuard.js";
12
+ } from "../../release/releaseGate.js";
13
+ import { guardTestCommand } from "../../security/commandGuard.js";
14
+ import { guardWorkspacePath } from "../../security/pathGuard.js";
15
+ import { resolveTrustedCommandLine, buildGitEnvironment, resolveTrustedExecutable } from "../../runner/processSecurity.js";
16
+ import { atomicWriteJsonFileSync } from "../../utils/atomicFile.js";
17
+ import { runSimpleProcessSync } from "../../runner/simpleProcess.js";
15
18
  import {
16
19
  commandAllowedByProjectPolicy,
17
20
  getProjectPolicySummary,
@@ -19,7 +22,7 @@ import {
19
22
  readPackageJson,
20
23
  resolveVersionFromPolicy,
21
24
  type ProjectPolicySummary,
22
- } from "../policy/projectPolicy.js";
25
+ } from "../../policy/projectPolicy.js";
23
26
 
24
27
  export interface ReleaseCheckInput {
25
28
  repo_path: string;
@@ -146,12 +149,19 @@ export function releasePrepare(input: ReleasePrepareInput): ReleaseModeResult {
146
149
  continue;
147
150
  }
148
151
  try {
149
- execSync(command, {
152
+ const invocation = resolveTrustedCommandLine(command, repoPath);
153
+ const execution = runSimpleProcessSync({
154
+ command: invocation.command,
155
+ args: invocation.argsPrefix,
150
156
  cwd: repoPath,
151
- encoding: "utf-8",
152
- timeout: timeoutMs,
153
- stdio: ["ignore", "pipe", "pipe"],
157
+ timeoutMs,
154
158
  });
159
+ if (execution.spawnError) throw new Error(execution.spawnError);
160
+ if (execution.timedOut) throw new Error(`Command timed out after ${timeoutMs}ms`);
161
+ if (execution.exitCode !== 0) {
162
+ const status = execution.exitCode ?? -1;
163
+ throw Object.assign(new Error(`Command exited with code ${status}`), { status });
164
+ }
155
165
  results.push({ command, status: "passed", exit_code: 0, reason: null });
156
166
  } catch (err) {
157
167
  const code = err && typeof err === "object" && "status" in err
@@ -280,7 +290,7 @@ export function releaseCleanup(input: ReleaseCleanupInput): ReleaseModeResult {
280
290
  const reportDir = join(repoPath, CLEANUP_REPORT_DIR);
281
291
  mkdirSync(reportDir, { recursive: true });
282
292
  const reportPath = join(reportDir, `${report.generated_at.replace(/[-:.TZ]/g, "").slice(0, 14)}.json`);
283
- writeFileSync(reportPath, JSON.stringify(report, null, 2) + "\n", "utf-8");
293
+ atomicWriteJsonFileSync(reportPath, report);
284
294
 
285
295
  return {
286
296
  ok: skipped.length === 0 || dryRun,
@@ -379,10 +389,12 @@ function walkFrom(base: string, dir: string, visit: (path: string, relPath: stri
379
389
  function isTrackedByGit(root: string, relPath: string): boolean {
380
390
  if (!isGitWorktree(root)) return false;
381
391
  try {
382
- execFileSync("git", ["ls-files", "--error-unmatch", "--", relPath], {
392
+ const env = buildGitEnvironment(root);
393
+ execFileSync(resolveTrustedExecutable("git", root, { pathValue: env.PATH }), ["ls-files", "--error-unmatch", "--", relPath], {
383
394
  cwd: root,
384
395
  stdio: "ignore",
385
396
  windowsHide: true,
397
+ env,
386
398
  });
387
399
  return true;
388
400
  } catch {
@@ -393,32 +405,38 @@ function isTrackedByGit(root: string, relPath: string): boolean {
393
405
  function isIgnoredOrUntracked(root: string, relPath: string): boolean {
394
406
  if (!isGitWorktree(root)) return true;
395
407
  try {
396
- execFileSync("git", ["check-ignore", "-q", "--", relPath], {
397
- cwd: root,
398
- stdio: "ignore",
399
- windowsHide: true,
400
- });
401
- return true;
402
- } catch {
408
+ const env = buildGitEnvironment(root);
409
+ const git = resolveTrustedExecutable("git", root, { pathValue: env.PATH });
403
410
  try {
404
- const output = execFileSync("git", ["ls-files", "--others", "--exclude-standard", "--", relPath], {
411
+ execFileSync(git, ["check-ignore", "-q", "--", relPath], {
412
+ cwd: root,
413
+ stdio: "ignore",
414
+ windowsHide: true,
415
+ env,
416
+ });
417
+ return true;
418
+ } catch {
419
+ const output = execFileSync(git, ["ls-files", "--others", "--exclude-standard", "--", relPath], {
405
420
  cwd: root,
406
421
  encoding: "utf-8",
407
422
  windowsHide: true,
423
+ env,
408
424
  });
409
425
  return output.trim().split(/\r?\n/).filter(Boolean).includes(relPath);
410
- } catch {
411
- return false;
412
426
  }
427
+ } catch {
428
+ return false;
413
429
  }
414
430
  }
415
431
 
416
432
  function isGitWorktree(root: string): boolean {
417
433
  try {
418
- return execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
434
+ const env = buildGitEnvironment(root);
435
+ return execFileSync(resolveTrustedExecutable("git", root, { pathValue: env.PATH }), ["rev-parse", "--is-inside-work-tree"], {
419
436
  cwd: root,
420
437
  encoding: "utf-8",
421
438
  windowsHide: true,
439
+ env,
422
440
  }).trim() === "true";
423
441
  } catch {
424
442
  return false;
@@ -0,0 +1,110 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { getTasksDir, getConfig } from "../../config.js";
4
+ import { guardReadPath } from "../../security/pathGuard.js";
5
+ import { writeTaskProgress } from "../../runner/taskProgress.js";
6
+ import { mutateTaskStatus } from "../../runner/taskStatusStore.js";
7
+ import type { TaskStatus } from "./createTask.js";
8
+ import { isTerminalTaskStatus } from "./taskStates.js";
9
+
10
+ export interface TaskTerminationResponse {
11
+ task_id: string;
12
+ previous_status: TaskStatus;
13
+ new_status: TaskStatus;
14
+ message: string;
15
+ cancel_requested?: boolean;
16
+ force_kill_requested?: boolean;
17
+ }
18
+
19
+ interface TaskTerminationOutcome {
20
+ response: TaskTerminationResponse;
21
+ progress: {
22
+ phase: "canceled" | "canceling" | "terminating";
23
+ note: string;
24
+ } | null;
25
+ }
26
+
27
+ export function cancelTask(taskId: string) {
28
+ return requestTaskTermination(taskId, false);
29
+ }
30
+
31
+ export function requestTaskTermination(taskId: string, force: boolean) {
32
+ const config = getConfig();
33
+ const taskDir = join(getTasksDir(config), taskId);
34
+ const statusFile = join(taskDir, "status.json");
35
+ guardReadPath(statusFile, config.workspaceRoot, config.tasksDir);
36
+
37
+ if (!existsSync(statusFile)) throw new Error(`Task not found: "${taskId}"`);
38
+
39
+ const now = new Date().toISOString();
40
+ const outcome = mutateTaskStatus<TaskTerminationOutcome>(statusFile, (current) => {
41
+ const currentStatus = current.status as TaskStatus;
42
+ if (isTerminalTaskStatus(currentStatus)) {
43
+ return { result: {
44
+ response: {
45
+ task_id: taskId,
46
+ previous_status: currentStatus,
47
+ new_status: currentStatus,
48
+ message: `Task is already ${currentStatus}. No action taken.`,
49
+ },
50
+ progress: null,
51
+ } };
52
+ }
53
+
54
+ if (currentStatus === "pending") {
55
+ const reason = force ? "Killed before execution by user request." : "Canceled by user request.";
56
+ const next = {
57
+ ...current,
58
+ status: "canceled",
59
+ phase: "canceled",
60
+ canceled_at: now,
61
+ cancel_reason: reason,
62
+ updated_at: now,
63
+ };
64
+ return { next, result: {
65
+ response: {
66
+ task_id: taskId,
67
+ previous_status: "pending" as const,
68
+ new_status: "canceled" as const,
69
+ message: "Pending task canceled. It will not be executed by watcher.",
70
+ },
71
+ progress: { phase: "canceled" as const, note: reason },
72
+ } };
73
+ }
74
+
75
+ const phase = force ? "terminating" as const : "canceling" as const;
76
+ const next = {
77
+ ...current,
78
+ cancel_requested: true,
79
+ cancel_requested_at: now,
80
+ force_kill_requested: force,
81
+ ...(force ? { kill_requested_at: now } : {}),
82
+ phase,
83
+ updated_at: now,
84
+ };
85
+ return { next, result: {
86
+ response: {
87
+ task_id: taskId,
88
+ previous_status: "running" as const,
89
+ new_status: "running" as const,
90
+ cancel_requested: true,
91
+ force_kill_requested: force,
92
+ message: force
93
+ ? "Kill requested. The runner that owns the child process will terminate it."
94
+ : "Cancel requested. The runner will stop the child process safely.",
95
+ },
96
+ progress: {
97
+ phase,
98
+ note: force ? "Immediate termination requested." : "Graceful cancellation requested.",
99
+ },
100
+ } };
101
+ });
102
+
103
+ if (outcome.progress) {
104
+ writeTaskProgress(taskDir, outcome.progress.phase, {
105
+ note: outcome.progress.note,
106
+ heartbeatAt: now,
107
+ });
108
+ }
109
+ return outcome.response;
110
+ }
@@ -2,46 +2,54 @@ import { mkdirSync, writeFileSync, existsSync, statSync, readFileSync } from "no
2
2
  import { randomBytes } from "node:crypto";
3
3
  import { resolve, join, dirname } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { getTasksDir, getPlansDir, getConfig } from "../config.js";
6
- import { guardPath, guardWorkspacePath, guardReadPath } from "../security/pathGuard.js";
7
- import { guardTestCommand } from "../security/commandGuard.js";
8
- import { guardRuntimeSelfModification } from "../security/runtimeGuard.js";
9
- import { guardPlanContent } from "../security/planGuard.js";
10
- import { assessRisk } from "../security/riskEngine.js";
5
+ import { getTasksDir, getPlansDir, getConfig } from "../../config.js";
6
+ import { guardPath, guardWorkspacePath, guardReadPath } from "../../security/pathGuard.js";
7
+ import { guardTestCommand } from "../../security/commandGuard.js";
8
+ import { guardRuntimeSelfModification } from "../../security/runtimeGuard.js";
9
+ import { guardPlanContent } from "../../security/planGuard.js";
10
+ import { assessRisk } from "../../security/riskEngine.js";
11
11
  import {
12
12
  createAssessment,
13
13
  readAssessment,
14
14
  validateAssessmentFreshness,
15
+ markAssessmentUsed,
15
16
  generateAssessmentId,
16
17
  createAssessmentDir,
17
18
  type AssessmentRecord,
18
19
  type AgentAssessmentSummary,
19
- } from "../assessments/assessmentStore.js";
20
- import { runAgentAssessment } from "../assessments/agentAssessor.js";
21
- import { captureRepoSnapshot } from "../runner/changeCapture.js";
22
- import { writeTaskProgress } from "../taskProgress.js";
23
- import { PatchWardenError } from "../errors.js";
24
- import { savePlan } from "./savePlan.js";
20
+ } from "../../assessments/assessmentStore.js";
21
+ import { runAgentAssessment } from "../../assessments/agentAssessor.js";
22
+ import { recordAssessmentValidationFailure } from "../../assessments/assessmentDiagnostics.js";
23
+ import { ASSESSMENT_SECURITY_SNAPSHOT_VERSION } from "../../assessments/securitySnapshot.js";
24
+ import { captureRepoSnapshot } from "../../runner/changeCapture.js";
25
+ import { writeTaskProgress } from "../../runner/taskProgress.js";
26
+ import { PatchWardenError } from "../../errors.js";
27
+ import { savePlan } from "../goals/savePlan.js";
25
28
  import {
26
29
  expandTaskTemplate,
27
30
  TASK_TEMPLATE_NAMES,
28
31
  type ChangePolicy,
29
32
  type TaskTemplateName,
30
- } from "./taskTemplates.js";
31
- import { PATCHWARDEN_VERSION } from "../version.js";
32
- import { getLastToolCatalogSnapshot, resolveToolProfile } from "./toolCatalog.js";
33
+ } from "../taskTemplates.js";
34
+ import { PATCHWARDEN_VERSION, ARTIFACT_SCHEMA_VERSION, TOOL_SCHEMA_EPOCH } from "../../version.js";
35
+ import { getLastToolCatalogSnapshot, resolveToolProfile } from "../catalog/toolCatalog.js";
33
36
  import {
34
37
  derivePendingReason,
35
38
  readWatcherStatus,
36
39
  type PendingReason,
37
40
  type WatcherStatusSnapshot,
38
- } from "../watcherStatus.js";
39
- import { routeAgent, type AgentRouteResult } from "../agents/agentRouter.js";
41
+ } from "../../watcherStatus.js";
42
+ import { routeAgent, type AgentRouteResult } from "../../agents/agentRouter.js";
43
+ import { redactSensitiveValue } from "../../security/contentRedaction.js";
44
+ import { atomicWriteJsonFileSync } from "../../utils/atomicFile.js";
40
45
 
41
46
  export type TaskStatus =
42
47
  | "pending"
43
48
  | "running"
49
+ | "executing_agent"
50
+ | "verifying"
44
51
  | "collecting_artifacts"
52
+ | "cancel_requested"
45
53
  | "done_by_agent" // v0.7.0: agent self-reported done or status reconciled to done; acceptance_status defaults to "pending"
46
54
  | "accepted" // v0.7.2: audit_task confirmed acceptance
47
55
  | "rejected" // v0.7.2: audit_task confirmed rejection
@@ -54,6 +62,7 @@ export type TaskStatus =
54
62
  | "failed_policy_violation"
55
63
  | "failed_stale" // v0.7.0: process dead / heartbeat expired
56
64
  | "orphaned" // v0.7.0: watcher no longer owns the task
65
+ | "timeout"
57
66
  | "canceled";
58
67
 
59
68
  /**
@@ -81,6 +90,7 @@ export type TaskPhase =
81
90
  | "failed_policy_violation"
82
91
  | "failed_stale" // v0.7.0
83
92
  | "orphaned" // v0.7.0
93
+ | "timeout"
84
94
  | "done_by_agent" // v0.7.0
85
95
  | "accepted" // v0.7.2
86
96
  | "rejected" // v0.7.2
@@ -110,6 +120,14 @@ export interface CreateTaskInput {
110
120
  // v0.8.0: Goal Session 关联
111
121
  goal_id?: string;
112
122
  subgoal_id?: string;
123
+ /** Internal provenance supplied by retry_task; not exposed in MCP schemas. */
124
+ retry_metadata?: {
125
+ retry_of: string;
126
+ retry_count: number;
127
+ plan_source: "saved" | "inline" | "template";
128
+ template: string | null;
129
+ change_policy: ChangePolicy;
130
+ };
113
131
  }
114
132
 
115
133
  export interface AssessOnlyOutput {
@@ -195,6 +213,15 @@ export async function createTask(input: CreateTaskInput): Promise<CreateTaskResu
195
213
  let effectiveInput = input;
196
214
  if (executionMode === "execute" && input.assessment_id) {
197
215
  assessmentRecord = readAssessment(input.assessment_id);
216
+ if (assessmentRecord.used_at) {
217
+ throw new PatchWardenError(
218
+ "assessment_used",
219
+ `Assessment "${input.assessment_id}" has already been used.`,
220
+ "Run create_task with execution_mode=assess_only again to obtain a new assessment_id.",
221
+ true,
222
+ { assessment_id: input.assessment_id, used_at: assessmentRecord.used_at },
223
+ );
224
+ }
198
225
  // Parameter mismatch check: if ChatGPT passes params that differ from the assessment, reject
199
226
  effectiveInput = mergeAssessmentIntoInput(input, assessmentRecord);
200
227
  }
@@ -480,6 +507,10 @@ export async function createTask(input: CreateTaskInput): Promise<CreateTaskResu
480
507
  agent: effectiveInput.agent,
481
508
  timeout_seconds: timeoutSeconds,
482
509
  change_policy: changePolicy,
510
+ scope: effectiveInput.scope,
511
+ forbidden: effectiveInput.forbidden,
512
+ verification: effectiveInput.verification,
513
+ done_evidence: effectiveInput.done_evidence,
483
514
  snapshot,
484
515
  ...(preGeneratedAssessmentId ? { assessment_id: preGeneratedAssessmentId } : {}),
485
516
  ...(preGeneratedAssessmentDir ? { assessment_dir: preGeneratedAssessmentDir } : {}),
@@ -575,26 +606,67 @@ export async function createTask(input: CreateTaskInput): Promise<CreateTaskResu
575
606
  const snapshot = await captureRepoSnapshot(safeRepoPath);
576
607
  const validation = validateAssessmentFreshness(input.assessment_id!, snapshot);
577
608
  if (!validation.valid) {
609
+ recordAssessmentValidationFailure(validation);
578
610
  throw new PatchWardenError(
579
611
  validation.failure_reason || "assessment_validation_failed",
580
612
  `Assessment "${input.assessment_id}" is no longer valid: ${validation.failure_reason}`,
581
613
  "Re-run create_task with execution_mode=assess_only to get a fresh assessment_id.",
582
614
  true,
583
- { assessment_id: input.assessment_id, failure_reason: validation.failure_reason }
615
+ {
616
+ assessment_id: input.assessment_id,
617
+ reason_code: validation.failure_reason,
618
+ failure_reason: validation.failure_reason,
619
+ ...(validation.expected_hash ? { expected_hash: validation.expected_hash } : {}),
620
+ ...(validation.actual_hash ? { actual_hash: validation.actual_hash } : {}),
621
+ ...(validation.expected_hash ? { assessment_config_sha256: validation.expected_hash } : {}),
622
+ ...(validation.actual_hash ? { current_config_sha256: validation.actual_hash } : {}),
623
+ snapshot_version: validation.assessment?.assessment_security_snapshot_version || null,
624
+ assessment_created_at: validation.assessment?.created_at || null,
625
+ assessment_expires_at: validation.assessment?.expires_at || null,
626
+ config_changed: validation.failure_reason === "assessment_stale_config",
627
+ ...(validation.config_change_categories
628
+ ? {
629
+ config_change_categories: validation.config_change_categories,
630
+ changed_field_names: validation.config_change_categories,
631
+ changed_config_sections: validation.config_change_categories,
632
+ }
633
+ : {}),
634
+ assessment_fingerprint_version: validation.assessment?.assessment_security_snapshot_version || null,
635
+ current_fingerprint_version: ASSESSMENT_SECURITY_SNAPSHOT_VERSION,
636
+ assessment_schema_epoch: validation.assessment?.assessment_schema_epoch || null,
637
+ current_schema_epoch: TOOL_SCHEMA_EPOCH,
638
+ validator_module_path: "dist/assessments/assessmentStore.js",
639
+ validator_build_id: `patchwarden-${PATCHWARDEN_VERSION}:${TOOL_SCHEMA_EPOCH}:${ASSESSMENT_SECURITY_SNAPSHOT_VERSION}`,
640
+ watcher_instance_id: process.env.PATCHWARDEN_WATCHER_INSTANCE_ID || null,
641
+ }
584
642
  );
585
643
  }
644
+ markAssessmentUsed(input.assessment_id!);
586
645
  }
587
646
 
588
647
  const { taskId, taskDir } = createTaskDirectory(tasksDir, config.workspaceRoot, config.tasksDir);
589
648
 
590
649
  const status: TaskStatus = "pending";
591
650
  const statusFile = join(taskDir, "status.json");
651
+ const retryMetadata = effectiveInput.retry_metadata;
652
+ const safeAcceptance = redactSensitiveValue({
653
+ goal: effectiveInput.goal || null,
654
+ scope: effectiveInput.scope ?? null,
655
+ forbidden: effectiveInput.forbidden ?? null,
656
+ verification: effectiveInput.verification ?? null,
657
+ done_evidence: effectiveInput.done_evidence ?? null,
658
+ }).value;
592
659
  const statusData = {
660
+ schema_version: ARTIFACT_SCHEMA_VERSION,
593
661
  task_id: taskId,
594
662
  plan_id: planId,
595
- plan_source: planSource,
596
- template: effectiveInput.template || null,
597
- change_policy: changePolicy,
663
+ plan_source: retryMetadata?.plan_source ?? planSource,
664
+ template: retryMetadata?.template ?? effectiveInput.template ?? null,
665
+ change_policy: retryMetadata?.change_policy ?? changePolicy,
666
+ ...(retryMetadata ? {
667
+ retry_of: retryMetadata.retry_of,
668
+ retry_count: retryMetadata.retry_count,
669
+ } : {}),
598
670
  agent: effectiveInput.agent,
599
671
  workspace_root: resolve(config.workspaceRoot),
600
672
  repo_path: resolvedRepoPath,
@@ -611,11 +683,11 @@ export async function createTask(input: CreateTaskInput): Promise<CreateTaskResu
611
683
  current_command: null as string | null,
612
684
  error: null as string | null,
613
685
  // v0.7.2: 验收标准绑定到任务创建
614
- goal: effectiveInput.goal || null,
615
- scope: effectiveInput.scope ?? null,
616
- forbidden: effectiveInput.forbidden ?? null,
617
- verification: effectiveInput.verification ?? null,
618
- done_evidence: effectiveInput.done_evidence ?? null,
686
+ goal: safeAcceptance.goal,
687
+ scope: safeAcceptance.scope,
688
+ forbidden: safeAcceptance.forbidden,
689
+ verification: safeAcceptance.verification,
690
+ done_evidence: safeAcceptance.done_evidence,
619
691
  acceptance_status: null as AcceptanceStatus,
620
692
  // v0.8.0: Goal Session 关联
621
693
  goal_id: effectiveInput.goal_id ?? null,
@@ -624,7 +696,7 @@ export async function createTask(input: CreateTaskInput): Promise<CreateTaskResu
624
696
  ...(agentSelectionReason ? { agent_selection_reason: agentSelectionReason } : {}),
625
697
  };
626
698
 
627
- writeFileSync(statusFile, JSON.stringify(statusData, null, 2), "utf-8");
699
+ atomicWriteJsonFileSync(statusFile, statusData);
628
700
  writeTaskProgress(taskDir, "queued", {
629
701
  heartbeatAt: statusData.last_heartbeat_at,
630
702
  note: `Waiting for watcher. Timeout: ${timeoutSeconds} seconds.`,
@@ -677,18 +749,22 @@ function mergeAssessmentIntoInput(
677
749
  // Do NOT set template/inline_plan alongside plan_id — createTask requires exactly one plan source.
678
750
  const merged: CreateTaskInput = {
679
751
  ...input,
680
- plan_id: record.plan_id || input.plan_id,
752
+ plan_id: record.plan_id || undefined,
681
753
  template: undefined,
682
754
  inline_plan: undefined,
683
- goal: record.goal || input.goal,
755
+ goal: record.goal || undefined,
684
756
  agent: record.agent,
685
757
  repo_path: record.repo_path,
686
758
  test_command: record.test_command || undefined,
687
- verify_commands: record.verify_commands || input.verify_commands,
688
- timeout_seconds: record.timeout_seconds || input.timeout_seconds,
759
+ verify_commands: record.verify_commands || [],
760
+ timeout_seconds: record.timeout_seconds,
761
+ scope: record.scope || [],
762
+ forbidden: record.forbidden || [],
763
+ verification: record.verification || [],
764
+ done_evidence: record.done_evidence || [],
689
765
  };
690
766
  // Parameter mismatch check: if caller passed explicit params that differ from assessment
691
- if (input.template && record.template && input.template !== record.template) {
767
+ if (input.template && input.template !== record.template) {
692
768
  throw new PatchWardenError(
693
769
  "assessment_parameter_mismatch",
694
770
  `template mismatch: caller passed "${input.template}" but assessment has "${record.template}".`,
@@ -697,7 +773,7 @@ function mergeAssessmentIntoInput(
697
773
  { field: "template", assessment_value: record.template, caller_value: input.template }
698
774
  );
699
775
  }
700
- if (input.goal && record.goal && input.goal !== record.goal) {
776
+ if (input.goal && input.goal !== record.goal) {
701
777
  throw new PatchWardenError(
702
778
  "assessment_parameter_mismatch",
703
779
  `goal mismatch: caller passed a different goal than the assessment.`,
@@ -715,6 +791,58 @@ function mergeAssessmentIntoInput(
715
791
  { field: "repo_path", assessment_value: record.repo_path, caller_value: input.repo_path }
716
792
  );
717
793
  }
794
+ if (input.agent && input.agent !== record.agent) {
795
+ throw new PatchWardenError(
796
+ "assessment_parameter_mismatch",
797
+ `agent mismatch: caller passed "${input.agent}" but assessment has "${record.agent}".`,
798
+ "Do not override assessment-locked parameters. Use the same assessment_id as-is.",
799
+ true,
800
+ { field: "agent" },
801
+ );
802
+ }
803
+ if (
804
+ input.verify_commands
805
+ && JSON.stringify(input.verify_commands) !== JSON.stringify(record.verify_commands || [])
806
+ ) {
807
+ throw new PatchWardenError(
808
+ "assessment_parameter_mismatch",
809
+ "verify_commands mismatch: caller passed commands different from the assessment.",
810
+ "Do not override assessment-locked parameters. Use the same assessment_id as-is.",
811
+ true,
812
+ { field: "verify_commands" },
813
+ );
814
+ }
815
+ if (input.test_command && input.test_command !== record.test_command) {
816
+ throw new PatchWardenError(
817
+ "assessment_parameter_mismatch",
818
+ "test_command mismatch: caller passed a command different from the assessment.",
819
+ "Do not override assessment-locked parameters. Use the same assessment_id as-is.",
820
+ true,
821
+ { field: "test_command" },
822
+ );
823
+ }
824
+ if (input.timeout_seconds !== undefined && input.timeout_seconds !== record.timeout_seconds) {
825
+ throw new PatchWardenError(
826
+ "assessment_parameter_mismatch",
827
+ "timeout_seconds mismatch: caller passed a timeout different from the assessment.",
828
+ "Do not override assessment-locked parameters. Use the same assessment_id as-is.",
829
+ true,
830
+ { field: "timeout_seconds" },
831
+ );
832
+ }
833
+ for (const field of ["scope", "forbidden", "verification", "done_evidence"] as const) {
834
+ const callerValue = input[field];
835
+ const assessmentValue = record[field] || [];
836
+ if (callerValue && JSON.stringify(callerValue) !== JSON.stringify(assessmentValue)) {
837
+ throw new PatchWardenError(
838
+ "assessment_parameter_mismatch",
839
+ `${field} mismatch: caller passed values different from the assessment.`,
840
+ "Do not override assessment-locked parameters. Use the same assessment_id as-is.",
841
+ true,
842
+ { field },
843
+ );
844
+ }
845
+ }
718
846
  return merged;
719
847
  }
720
848
 
@@ -1,11 +1,13 @@
1
1
  import { existsSync, readFileSync, statSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
- import { getTasksDir, getConfig, type PatchWardenConfig } from "../config.js";
4
- import { guardReadPath } from "../security/pathGuard.js";
5
- import { guardSensitivePath } from "../security/sensitiveGuard.js";
6
- import { redactSensitiveContent } from "../security/contentRedaction.js";
7
- import { readTaskRuntime } from "../taskRuntime.js";
8
- import { isWatcherOwningTask, readWatcherInstanceId } from "../watcherStatus.js";
3
+ import { getTasksDir, getConfig, type PatchWardenConfig } from "../../config.js";
4
+ import { guardReadPath } from "../../security/pathGuard.js";
5
+ import { guardSensitivePath } from "../../security/sensitiveGuard.js";
6
+ import { redactSensitiveContent } from "../../security/contentRedaction.js";
7
+ import { readTextFileTailLinesSync } from "../../utils/boundedFile.js";
8
+ import { readTaskRuntime } from "../../runner/taskRuntime.js";
9
+ import { isWatcherOwningTask, readWatcherInstanceId } from "../../watcherStatus.js";
10
+ import { isActiveTaskStatus, isTerminalTaskStatus } from "./taskStates.js";
9
11
 
10
12
  // ── v0.7.0: Task diagnosis types ──────────────────────────────────
11
13
 
@@ -118,8 +120,7 @@ function fileAgeSeconds(filePath: string, nowMs: number): { age: number; size: n
118
120
  function readTailLines(filePath: string, lines: number): string | null {
119
121
  if (!existsSync(filePath)) return null;
120
122
  try {
121
- const raw = readFileSync(filePath, "utf-8");
122
- const tail = raw.split("\n").slice(-lines).join("\n");
123
+ const tail = readTextFileTailLinesSync(filePath, lines, 512 * 1024);
123
124
  return redactSensitiveContent(tail).content;
124
125
  } catch {
125
126
  return null;
@@ -153,17 +154,12 @@ export function diagnoseTask(
153
154
  const phaseStr = (runtime.phase || (typeof status.phase === "string" ? status.phase : "queued")) as string;
154
155
 
155
156
  // ── Terminal tasks: no diagnosis needed ──
156
- const terminalStatuses = new Set([
157
- "done", "done_by_agent", "failed", "failed_verification",
158
- "failed_scope_violation", "failed_policy_violation",
159
- "failed_stale", "orphaned", "canceled",
160
- ]);
161
- if (terminalStatuses.has(statusStr)) {
157
+ if (isTerminalTaskStatus(statusStr)) {
162
158
  return buildTerminalDiagnosis(taskId, statusStr, phaseStr);
163
159
  }
164
160
 
165
- // ── Only diagnose running / collecting_artifacts / pending ──
166
- if (statusStr !== "running" && statusStr !== "collecting_artifacts" && statusStr !== "pending") {
161
+ // Diagnose both current status spellings and legacy phase-as-status records.
162
+ if (!isActiveTaskStatus(statusStr)) {
167
163
  return buildUnknownDiagnosis(taskId, statusStr, phaseStr, [`unexpected status "${statusStr}" for diagnosis`]);
168
164
  }
169
165