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,4 +1,4 @@
1
- import { type ReleaseStage } from "../release/releaseGate.js";
1
+ import { type ReleaseStage } from "../../release/releaseGate.js";
2
2
  export interface CheckReleaseGateInput {
3
3
  repo_path: string;
4
4
  target_stage: ReleaseStage;
@@ -1,6 +1,6 @@
1
- import { getConfig } from "../config.js";
2
- import { guardWorkspacePath } from "../security/pathGuard.js";
3
- import { runReleaseGateCheck, } from "../release/releaseGate.js";
1
+ import { getConfig } from "../../config.js";
2
+ import { guardWorkspacePath } from "../../security/pathGuard.js";
3
+ import { runReleaseGateCheck, } from "../../release/releaseGate.js";
4
4
  /**
5
5
  * MCP tool handler for the release gate.
6
6
  *
@@ -0,0 +1,2 @@
1
+ export * from "./checkReleaseGate.js";
2
+ export * from "./releaseMode.js";
@@ -0,0 +1,2 @@
1
+ export * from "./checkReleaseGate.js";
2
+ export * from "./releaseMode.js";
@@ -1,5 +1,5 @@
1
- import { type ReleaseStage } from "../release/releaseGate.js";
2
- import { type ProjectPolicySummary } from "../policy/projectPolicy.js";
1
+ import { type ReleaseStage } from "../../release/releaseGate.js";
2
+ import { type ProjectPolicySummary } from "../../policy/projectPolicy.js";
3
3
  export interface ReleaseCheckInput {
4
4
  repo_path: string;
5
5
  target_stage?: ReleaseStage;
@@ -1,11 +1,14 @@
1
- import { execFileSync, execSync } from "node:child_process";
2
- import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs";
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
3
3
  import { join, relative, resolve, sep, isAbsolute } from "node:path";
4
- import { getConfig } from "../config.js";
5
- import { checkCiVerified, checkGitHubReleaseVerified, checkPublishedVerified, runReleaseGateCheck, } from "../release/releaseGate.js";
6
- import { guardTestCommand } from "../security/commandGuard.js";
7
- import { guardWorkspacePath } from "../security/pathGuard.js";
8
- import { commandAllowedByProjectPolicy, getProjectPolicySummary, isProtectedByProjectPolicy, readPackageJson, resolveVersionFromPolicy, } from "../policy/projectPolicy.js";
4
+ import { getConfig } from "../../config.js";
5
+ import { checkCiVerified, checkGitHubReleaseVerified, checkPublishedVerified, runReleaseGateCheck, } from "../../release/releaseGate.js";
6
+ import { guardTestCommand } from "../../security/commandGuard.js";
7
+ import { guardWorkspacePath } from "../../security/pathGuard.js";
8
+ import { resolveTrustedCommandLine, buildGitEnvironment, resolveTrustedExecutable } from "../../runner/processSecurity.js";
9
+ import { atomicWriteJsonFileSync } from "../../utils/atomicFile.js";
10
+ import { runSimpleProcessSync } from "../../runner/simpleProcess.js";
11
+ import { commandAllowedByProjectPolicy, getProjectPolicySummary, isProtectedByProjectPolicy, readPackageJson, resolveVersionFromPolicy, } from "../../policy/projectPolicy.js";
9
12
  const LOCAL_TIMEOUT_MS = 300000;
10
13
  const CLEANUP_REPORT_DIR = ".patchwarden/release-cleanup";
11
14
  export function getProjectPolicyTool(repoPath) {
@@ -75,12 +78,21 @@ export function releasePrepare(input) {
75
78
  continue;
76
79
  }
77
80
  try {
78
- execSync(command, {
81
+ const invocation = resolveTrustedCommandLine(command, repoPath);
82
+ const execution = runSimpleProcessSync({
83
+ command: invocation.command,
84
+ args: invocation.argsPrefix,
79
85
  cwd: repoPath,
80
- encoding: "utf-8",
81
- timeout: timeoutMs,
82
- stdio: ["ignore", "pipe", "pipe"],
86
+ timeoutMs,
83
87
  });
88
+ if (execution.spawnError)
89
+ throw new Error(execution.spawnError);
90
+ if (execution.timedOut)
91
+ throw new Error(`Command timed out after ${timeoutMs}ms`);
92
+ if (execution.exitCode !== 0) {
93
+ const status = execution.exitCode ?? -1;
94
+ throw Object.assign(new Error(`Command exited with code ${status}`), { status });
95
+ }
84
96
  results.push({ command, status: "passed", exit_code: 0, reason: null });
85
97
  }
86
98
  catch (err) {
@@ -204,7 +216,7 @@ export function releaseCleanup(input) {
204
216
  const reportDir = join(repoPath, CLEANUP_REPORT_DIR);
205
217
  mkdirSync(reportDir, { recursive: true });
206
218
  const reportPath = join(reportDir, `${report.generated_at.replace(/[-:.TZ]/g, "").slice(0, 14)}.json`);
207
- writeFileSync(reportPath, JSON.stringify(report, null, 2) + "\n", "utf-8");
219
+ atomicWriteJsonFileSync(reportPath, report);
208
220
  return {
209
221
  ok: skipped.length === 0 || dryRun,
210
222
  mode: "release_cleanup",
@@ -297,10 +309,12 @@ function isTrackedByGit(root, relPath) {
297
309
  if (!isGitWorktree(root))
298
310
  return false;
299
311
  try {
300
- execFileSync("git", ["ls-files", "--error-unmatch", "--", relPath], {
312
+ const env = buildGitEnvironment(root);
313
+ execFileSync(resolveTrustedExecutable("git", root, { pathValue: env.PATH }), ["ls-files", "--error-unmatch", "--", relPath], {
301
314
  cwd: root,
302
315
  stdio: "ignore",
303
316
  windowsHide: true,
317
+ env,
304
318
  });
305
319
  return true;
306
320
  }
@@ -312,33 +326,39 @@ function isIgnoredOrUntracked(root, relPath) {
312
326
  if (!isGitWorktree(root))
313
327
  return true;
314
328
  try {
315
- execFileSync("git", ["check-ignore", "-q", "--", relPath], {
316
- cwd: root,
317
- stdio: "ignore",
318
- windowsHide: true,
319
- });
320
- return true;
321
- }
322
- catch {
329
+ const env = buildGitEnvironment(root);
330
+ const git = resolveTrustedExecutable("git", root, { pathValue: env.PATH });
323
331
  try {
324
- const output = execFileSync("git", ["ls-files", "--others", "--exclude-standard", "--", relPath], {
332
+ execFileSync(git, ["check-ignore", "-q", "--", relPath], {
325
333
  cwd: root,
326
- encoding: "utf-8",
334
+ stdio: "ignore",
327
335
  windowsHide: true,
336
+ env,
328
337
  });
329
- return output.trim().split(/\r?\n/).filter(Boolean).includes(relPath);
338
+ return true;
330
339
  }
331
340
  catch {
332
- return false;
341
+ const output = execFileSync(git, ["ls-files", "--others", "--exclude-standard", "--", relPath], {
342
+ cwd: root,
343
+ encoding: "utf-8",
344
+ windowsHide: true,
345
+ env,
346
+ });
347
+ return output.trim().split(/\r?\n/).filter(Boolean).includes(relPath);
333
348
  }
334
349
  }
350
+ catch {
351
+ return false;
352
+ }
335
353
  }
336
354
  function isGitWorktree(root) {
337
355
  try {
338
- return execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
356
+ const env = buildGitEnvironment(root);
357
+ return execFileSync(resolveTrustedExecutable("git", root, { pathValue: env.PATH }), ["rev-parse", "--is-inside-work-tree"], {
339
358
  cwd: root,
340
359
  encoding: "utf-8",
341
360
  windowsHide: true,
361
+ env,
342
362
  }).trim() === "true";
343
363
  }
344
364
  catch {
@@ -0,0 +1,11 @@
1
+ import type { TaskStatus } from "./createTask.js";
2
+ export interface TaskTerminationResponse {
3
+ task_id: string;
4
+ previous_status: TaskStatus;
5
+ new_status: TaskStatus;
6
+ message: string;
7
+ cancel_requested?: boolean;
8
+ force_kill_requested?: boolean;
9
+ }
10
+ export declare function cancelTask(taskId: string): TaskTerminationResponse;
11
+ export declare function requestTaskTermination(taskId: string, force: boolean): TaskTerminationResponse;
@@ -0,0 +1,86 @@
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 { isTerminalTaskStatus } from "./taskStates.js";
8
+ export function cancelTask(taskId) {
9
+ return requestTaskTermination(taskId, false);
10
+ }
11
+ export function requestTaskTermination(taskId, force) {
12
+ const config = getConfig();
13
+ const taskDir = join(getTasksDir(config), taskId);
14
+ const statusFile = join(taskDir, "status.json");
15
+ guardReadPath(statusFile, config.workspaceRoot, config.tasksDir);
16
+ if (!existsSync(statusFile))
17
+ throw new Error(`Task not found: "${taskId}"`);
18
+ const now = new Date().toISOString();
19
+ const outcome = mutateTaskStatus(statusFile, (current) => {
20
+ const currentStatus = current.status;
21
+ if (isTerminalTaskStatus(currentStatus)) {
22
+ return { result: {
23
+ response: {
24
+ task_id: taskId,
25
+ previous_status: currentStatus,
26
+ new_status: currentStatus,
27
+ message: `Task is already ${currentStatus}. No action taken.`,
28
+ },
29
+ progress: null,
30
+ } };
31
+ }
32
+ if (currentStatus === "pending") {
33
+ const reason = force ? "Killed before execution by user request." : "Canceled by user request.";
34
+ const next = {
35
+ ...current,
36
+ status: "canceled",
37
+ phase: "canceled",
38
+ canceled_at: now,
39
+ cancel_reason: reason,
40
+ updated_at: now,
41
+ };
42
+ return { next, result: {
43
+ response: {
44
+ task_id: taskId,
45
+ previous_status: "pending",
46
+ new_status: "canceled",
47
+ message: "Pending task canceled. It will not be executed by watcher.",
48
+ },
49
+ progress: { phase: "canceled", note: reason },
50
+ } };
51
+ }
52
+ const phase = force ? "terminating" : "canceling";
53
+ const next = {
54
+ ...current,
55
+ cancel_requested: true,
56
+ cancel_requested_at: now,
57
+ force_kill_requested: force,
58
+ ...(force ? { kill_requested_at: now } : {}),
59
+ phase,
60
+ updated_at: now,
61
+ };
62
+ return { next, result: {
63
+ response: {
64
+ task_id: taskId,
65
+ previous_status: "running",
66
+ new_status: "running",
67
+ cancel_requested: true,
68
+ force_kill_requested: force,
69
+ message: force
70
+ ? "Kill requested. The runner that owns the child process will terminate it."
71
+ : "Cancel requested. The runner will stop the child process safely.",
72
+ },
73
+ progress: {
74
+ phase,
75
+ note: force ? "Immediate termination requested." : "Graceful cancellation requested.",
76
+ },
77
+ } };
78
+ });
79
+ if (outcome.progress) {
80
+ writeTaskProgress(taskDir, outcome.progress.phase, {
81
+ note: outcome.progress.note,
82
+ heartbeatAt: now,
83
+ });
84
+ }
85
+ return outcome.response;
86
+ }
@@ -1,6 +1,6 @@
1
- import { type ChangePolicy, type TaskTemplateName } from "./taskTemplates.js";
2
- import { type PendingReason, type WatcherStatusSnapshot } from "../watcherStatus.js";
3
- export type TaskStatus = "pending" | "running" | "collecting_artifacts" | "done_by_agent" | "accepted" | "rejected" | "needs_fix" | "blocked" | "done" | "failed" | "failed_verification" | "failed_scope_violation" | "failed_policy_violation" | "failed_stale" | "orphaned" | "canceled";
1
+ import { type ChangePolicy, type TaskTemplateName } from "../taskTemplates.js";
2
+ import { type PendingReason, type WatcherStatusSnapshot } from "../../watcherStatus.js";
3
+ export type TaskStatus = "pending" | "running" | "executing_agent" | "verifying" | "collecting_artifacts" | "cancel_requested" | "done_by_agent" | "accepted" | "rejected" | "needs_fix" | "blocked" | "done" | "failed" | "failed_verification" | "failed_scope_violation" | "failed_policy_violation" | "failed_stale" | "orphaned" | "timeout" | "canceled";
4
4
  /**
5
5
  * v0.7.2 acceptance status — only meaningful for done_by_agent.
6
6
  * - "pending": done_by_agent reached but not yet audited/accepted
@@ -11,7 +11,7 @@ export type TaskStatus = "pending" | "running" | "collecting_artifacts" | "done_
11
11
  * - null: status has no acceptance semantics (running, failed, etc.)
12
12
  */
13
13
  export type AcceptanceStatus = "pending" | "accepted" | "rejected" | "needs_fix" | "blocked" | null;
14
- export type TaskPhase = "queued" | "preparing" | "executing_agent" | "running_tests" | "collecting_artifacts" | "canceling" | "terminating" | "completed" | "failed" | "failed_verification" | "failed_scope_violation" | "failed_policy_violation" | "failed_stale" | "orphaned" | "done_by_agent" | "accepted" | "rejected" | "needs_fix" | "blocked" | "canceled";
14
+ export type TaskPhase = "queued" | "preparing" | "executing_agent" | "running_tests" | "collecting_artifacts" | "canceling" | "terminating" | "completed" | "failed" | "failed_verification" | "failed_scope_violation" | "failed_policy_violation" | "failed_stale" | "orphaned" | "timeout" | "done_by_agent" | "accepted" | "rejected" | "needs_fix" | "blocked" | "canceled";
15
15
  export interface CreateTaskInput {
16
16
  plan_id?: string;
17
17
  inline_plan?: string;
@@ -32,6 +32,14 @@ export interface CreateTaskInput {
32
32
  done_evidence?: string[];
33
33
  goal_id?: string;
34
34
  subgoal_id?: string;
35
+ /** Internal provenance supplied by retry_task; not exposed in MCP schemas. */
36
+ retry_metadata?: {
37
+ retry_of: string;
38
+ retry_count: number;
39
+ plan_source: "saved" | "inline" | "template";
40
+ template: string | null;
41
+ change_policy: ChangePolicy;
42
+ };
35
43
  }
36
44
  export interface AssessOnlyOutput {
37
45
  assessment_id: string;
@@ -1,23 +1,27 @@
1
- import { mkdirSync, writeFileSync, existsSync, statSync, readFileSync } from "node:fs";
1
+ import { mkdirSync, existsSync, statSync, readFileSync } from "node:fs";
2
2
  import { randomBytes } from "node:crypto";
3
3
  import { resolve, join } from "node:path";
4
- import { getTasksDir, getPlansDir, getConfig } from "../config.js";
5
- import { guardPath, guardWorkspacePath, guardReadPath } from "../security/pathGuard.js";
6
- import { guardTestCommand } from "../security/commandGuard.js";
7
- import { guardRuntimeSelfModification } from "../security/runtimeGuard.js";
8
- import { guardPlanContent } from "../security/planGuard.js";
9
- import { assessRisk } from "../security/riskEngine.js";
10
- import { createAssessment, readAssessment, validateAssessmentFreshness, generateAssessmentId, createAssessmentDir, } from "../assessments/assessmentStore.js";
11
- import { runAgentAssessment } from "../assessments/agentAssessor.js";
12
- import { captureRepoSnapshot } from "../runner/changeCapture.js";
13
- import { writeTaskProgress } from "../taskProgress.js";
14
- import { PatchWardenError } from "../errors.js";
15
- import { savePlan } from "./savePlan.js";
16
- import { expandTaskTemplate, TASK_TEMPLATE_NAMES, } from "./taskTemplates.js";
17
- import { PATCHWARDEN_VERSION } from "../version.js";
18
- import { getLastToolCatalogSnapshot, resolveToolProfile } from "./toolCatalog.js";
19
- import { derivePendingReason, readWatcherStatus, } from "../watcherStatus.js";
20
- import { routeAgent } from "../agents/agentRouter.js";
4
+ import { getTasksDir, getPlansDir, getConfig } from "../../config.js";
5
+ import { guardPath, guardWorkspacePath, guardReadPath } from "../../security/pathGuard.js";
6
+ import { guardTestCommand } from "../../security/commandGuard.js";
7
+ import { guardRuntimeSelfModification } from "../../security/runtimeGuard.js";
8
+ import { guardPlanContent } from "../../security/planGuard.js";
9
+ import { assessRisk } from "../../security/riskEngine.js";
10
+ import { createAssessment, readAssessment, validateAssessmentFreshness, markAssessmentUsed, generateAssessmentId, createAssessmentDir, } from "../../assessments/assessmentStore.js";
11
+ import { runAgentAssessment } from "../../assessments/agentAssessor.js";
12
+ import { recordAssessmentValidationFailure } from "../../assessments/assessmentDiagnostics.js";
13
+ import { ASSESSMENT_SECURITY_SNAPSHOT_VERSION } from "../../assessments/securitySnapshot.js";
14
+ import { captureRepoSnapshot } from "../../runner/changeCapture.js";
15
+ import { writeTaskProgress } from "../../runner/taskProgress.js";
16
+ import { PatchWardenError } from "../../errors.js";
17
+ import { savePlan } from "../goals/savePlan.js";
18
+ import { expandTaskTemplate, TASK_TEMPLATE_NAMES, } from "../taskTemplates.js";
19
+ import { PATCHWARDEN_VERSION, ARTIFACT_SCHEMA_VERSION, TOOL_SCHEMA_EPOCH } from "../../version.js";
20
+ import { getLastToolCatalogSnapshot, resolveToolProfile } from "../catalog/toolCatalog.js";
21
+ import { derivePendingReason, readWatcherStatus, } from "../../watcherStatus.js";
22
+ import { routeAgent } from "../../agents/agentRouter.js";
23
+ import { redactSensitiveValue } from "../../security/contentRedaction.js";
24
+ import { atomicWriteJsonFileSync } from "../../utils/atomicFile.js";
21
25
  export async function createTask(input) {
22
26
  const config = getConfig();
23
27
  const tasksDir = getTasksDir(config);
@@ -28,6 +32,9 @@ export async function createTask(input) {
28
32
  let effectiveInput = input;
29
33
  if (executionMode === "execute" && input.assessment_id) {
30
34
  assessmentRecord = readAssessment(input.assessment_id);
35
+ if (assessmentRecord.used_at) {
36
+ throw new PatchWardenError("assessment_used", `Assessment "${input.assessment_id}" has already been used.`, "Run create_task with execution_mode=assess_only again to obtain a new assessment_id.", true, { assessment_id: input.assessment_id, used_at: assessmentRecord.used_at });
37
+ }
31
38
  // Parameter mismatch check: if ChatGPT passes params that differ from the assessment, reject
32
39
  effectiveInput = mergeAssessmentIntoInput(input, assessmentRecord);
33
40
  }
@@ -245,6 +252,10 @@ export async function createTask(input) {
245
252
  agent: effectiveInput.agent,
246
253
  timeout_seconds: timeoutSeconds,
247
254
  change_policy: changePolicy,
255
+ scope: effectiveInput.scope,
256
+ forbidden: effectiveInput.forbidden,
257
+ verification: effectiveInput.verification,
258
+ done_evidence: effectiveInput.done_evidence,
248
259
  snapshot,
249
260
  ...(preGeneratedAssessmentId ? { assessment_id: preGeneratedAssessmentId } : {}),
250
261
  ...(preGeneratedAssessmentDir ? { assessment_dir: preGeneratedAssessmentDir } : {}),
@@ -333,18 +344,59 @@ export async function createTask(input) {
333
344
  const snapshot = await captureRepoSnapshot(safeRepoPath);
334
345
  const validation = validateAssessmentFreshness(input.assessment_id, snapshot);
335
346
  if (!validation.valid) {
336
- throw new PatchWardenError(validation.failure_reason || "assessment_validation_failed", `Assessment "${input.assessment_id}" is no longer valid: ${validation.failure_reason}`, "Re-run create_task with execution_mode=assess_only to get a fresh assessment_id.", true, { assessment_id: input.assessment_id, failure_reason: validation.failure_reason });
347
+ recordAssessmentValidationFailure(validation);
348
+ throw new PatchWardenError(validation.failure_reason || "assessment_validation_failed", `Assessment "${input.assessment_id}" is no longer valid: ${validation.failure_reason}`, "Re-run create_task with execution_mode=assess_only to get a fresh assessment_id.", true, {
349
+ assessment_id: input.assessment_id,
350
+ reason_code: validation.failure_reason,
351
+ failure_reason: validation.failure_reason,
352
+ ...(validation.expected_hash ? { expected_hash: validation.expected_hash } : {}),
353
+ ...(validation.actual_hash ? { actual_hash: validation.actual_hash } : {}),
354
+ ...(validation.expected_hash ? { assessment_config_sha256: validation.expected_hash } : {}),
355
+ ...(validation.actual_hash ? { current_config_sha256: validation.actual_hash } : {}),
356
+ snapshot_version: validation.assessment?.assessment_security_snapshot_version || null,
357
+ assessment_created_at: validation.assessment?.created_at || null,
358
+ assessment_expires_at: validation.assessment?.expires_at || null,
359
+ config_changed: validation.failure_reason === "assessment_stale_config",
360
+ ...(validation.config_change_categories
361
+ ? {
362
+ config_change_categories: validation.config_change_categories,
363
+ changed_field_names: validation.config_change_categories,
364
+ changed_config_sections: validation.config_change_categories,
365
+ }
366
+ : {}),
367
+ assessment_fingerprint_version: validation.assessment?.assessment_security_snapshot_version || null,
368
+ current_fingerprint_version: ASSESSMENT_SECURITY_SNAPSHOT_VERSION,
369
+ assessment_schema_epoch: validation.assessment?.assessment_schema_epoch || null,
370
+ current_schema_epoch: TOOL_SCHEMA_EPOCH,
371
+ validator_module_path: "dist/assessments/assessmentStore.js",
372
+ validator_build_id: `patchwarden-${PATCHWARDEN_VERSION}:${TOOL_SCHEMA_EPOCH}:${ASSESSMENT_SECURITY_SNAPSHOT_VERSION}`,
373
+ watcher_instance_id: process.env.PATCHWARDEN_WATCHER_INSTANCE_ID || null,
374
+ });
337
375
  }
376
+ markAssessmentUsed(input.assessment_id);
338
377
  }
339
378
  const { taskId, taskDir } = createTaskDirectory(tasksDir, config.workspaceRoot, config.tasksDir);
340
379
  const status = "pending";
341
380
  const statusFile = join(taskDir, "status.json");
381
+ const retryMetadata = effectiveInput.retry_metadata;
382
+ const safeAcceptance = redactSensitiveValue({
383
+ goal: effectiveInput.goal || null,
384
+ scope: effectiveInput.scope ?? null,
385
+ forbidden: effectiveInput.forbidden ?? null,
386
+ verification: effectiveInput.verification ?? null,
387
+ done_evidence: effectiveInput.done_evidence ?? null,
388
+ }).value;
342
389
  const statusData = {
390
+ schema_version: ARTIFACT_SCHEMA_VERSION,
343
391
  task_id: taskId,
344
392
  plan_id: planId,
345
- plan_source: planSource,
346
- template: effectiveInput.template || null,
347
- change_policy: changePolicy,
393
+ plan_source: retryMetadata?.plan_source ?? planSource,
394
+ template: retryMetadata?.template ?? effectiveInput.template ?? null,
395
+ change_policy: retryMetadata?.change_policy ?? changePolicy,
396
+ ...(retryMetadata ? {
397
+ retry_of: retryMetadata.retry_of,
398
+ retry_count: retryMetadata.retry_count,
399
+ } : {}),
348
400
  agent: effectiveInput.agent,
349
401
  workspace_root: resolve(config.workspaceRoot),
350
402
  repo_path: resolvedRepoPath,
@@ -361,11 +413,11 @@ export async function createTask(input) {
361
413
  current_command: null,
362
414
  error: null,
363
415
  // v0.7.2: 验收标准绑定到任务创建
364
- goal: effectiveInput.goal || null,
365
- scope: effectiveInput.scope ?? null,
366
- forbidden: effectiveInput.forbidden ?? null,
367
- verification: effectiveInput.verification ?? null,
368
- done_evidence: effectiveInput.done_evidence ?? null,
416
+ goal: safeAcceptance.goal,
417
+ scope: safeAcceptance.scope,
418
+ forbidden: safeAcceptance.forbidden,
419
+ verification: safeAcceptance.verification,
420
+ done_evidence: safeAcceptance.done_evidence,
369
421
  acceptance_status: null,
370
422
  // v0.8.0: Goal Session 关联
371
423
  goal_id: effectiveInput.goal_id ?? null,
@@ -373,7 +425,7 @@ export async function createTask(input) {
373
425
  // v1.0.0: Agent routing decision (only present when agent was auto-routed)
374
426
  ...(agentSelectionReason ? { agent_selection_reason: agentSelectionReason } : {}),
375
427
  };
376
- writeFileSync(statusFile, JSON.stringify(statusData, null, 2), "utf-8");
428
+ atomicWriteJsonFileSync(statusFile, statusData);
377
429
  writeTaskProgress(taskDir, "queued", {
378
430
  heartbeatAt: statusData.last_heartbeat_at,
379
431
  note: `Waiting for watcher. Timeout: ${timeoutSeconds} seconds.`,
@@ -420,26 +472,50 @@ function mergeAssessmentIntoInput(input, record) {
420
472
  // Do NOT set template/inline_plan alongside plan_id — createTask requires exactly one plan source.
421
473
  const merged = {
422
474
  ...input,
423
- plan_id: record.plan_id || input.plan_id,
475
+ plan_id: record.plan_id || undefined,
424
476
  template: undefined,
425
477
  inline_plan: undefined,
426
- goal: record.goal || input.goal,
478
+ goal: record.goal || undefined,
427
479
  agent: record.agent,
428
480
  repo_path: record.repo_path,
429
481
  test_command: record.test_command || undefined,
430
- verify_commands: record.verify_commands || input.verify_commands,
431
- timeout_seconds: record.timeout_seconds || input.timeout_seconds,
482
+ verify_commands: record.verify_commands || [],
483
+ timeout_seconds: record.timeout_seconds,
484
+ scope: record.scope || [],
485
+ forbidden: record.forbidden || [],
486
+ verification: record.verification || [],
487
+ done_evidence: record.done_evidence || [],
432
488
  };
433
489
  // Parameter mismatch check: if caller passed explicit params that differ from assessment
434
- if (input.template && record.template && input.template !== record.template) {
490
+ if (input.template && input.template !== record.template) {
435
491
  throw new PatchWardenError("assessment_parameter_mismatch", `template mismatch: caller passed "${input.template}" but assessment has "${record.template}".`, "Do not override assessment-locked parameters. Use the same assessment_id as-is.", true, { field: "template", assessment_value: record.template, caller_value: input.template });
436
492
  }
437
- if (input.goal && record.goal && input.goal !== record.goal) {
493
+ if (input.goal && input.goal !== record.goal) {
438
494
  throw new PatchWardenError("assessment_parameter_mismatch", `goal mismatch: caller passed a different goal than the assessment.`, "Do not override assessment-locked parameters. Use the same assessment_id as-is.", true, { field: "goal" });
439
495
  }
440
496
  if (input.repo_path && record.repo_path && input.repo_path !== record.repo_path) {
441
497
  throw new PatchWardenError("assessment_parameter_mismatch", `repo_path mismatch: caller passed "${input.repo_path}" but assessment has "${record.repo_path}".`, "Do not override assessment-locked parameters. Use the same assessment_id as-is.", true, { field: "repo_path", assessment_value: record.repo_path, caller_value: input.repo_path });
442
498
  }
499
+ if (input.agent && input.agent !== record.agent) {
500
+ throw new PatchWardenError("assessment_parameter_mismatch", `agent mismatch: caller passed "${input.agent}" but assessment has "${record.agent}".`, "Do not override assessment-locked parameters. Use the same assessment_id as-is.", true, { field: "agent" });
501
+ }
502
+ if (input.verify_commands
503
+ && JSON.stringify(input.verify_commands) !== JSON.stringify(record.verify_commands || [])) {
504
+ throw new PatchWardenError("assessment_parameter_mismatch", "verify_commands mismatch: caller passed commands different from the assessment.", "Do not override assessment-locked parameters. Use the same assessment_id as-is.", true, { field: "verify_commands" });
505
+ }
506
+ if (input.test_command && input.test_command !== record.test_command) {
507
+ throw new PatchWardenError("assessment_parameter_mismatch", "test_command mismatch: caller passed a command different from the assessment.", "Do not override assessment-locked parameters. Use the same assessment_id as-is.", true, { field: "test_command" });
508
+ }
509
+ if (input.timeout_seconds !== undefined && input.timeout_seconds !== record.timeout_seconds) {
510
+ throw new PatchWardenError("assessment_parameter_mismatch", "timeout_seconds mismatch: caller passed a timeout different from the assessment.", "Do not override assessment-locked parameters. Use the same assessment_id as-is.", true, { field: "timeout_seconds" });
511
+ }
512
+ for (const field of ["scope", "forbidden", "verification", "done_evidence"]) {
513
+ const callerValue = input[field];
514
+ const assessmentValue = record[field] || [];
515
+ if (callerValue && JSON.stringify(callerValue) !== JSON.stringify(assessmentValue)) {
516
+ throw new PatchWardenError("assessment_parameter_mismatch", `${field} mismatch: caller passed values different from the assessment.`, "Do not override assessment-locked parameters. Use the same assessment_id as-is.", true, { field });
517
+ }
518
+ }
443
519
  return merged;
444
520
  }
445
521
  function createTaskDirectory(tasksDir, workspaceRoot, configuredTasksDir) {
@@ -1,4 +1,4 @@
1
- import { type PatchWardenConfig } from "../config.js";
1
+ import { type PatchWardenConfig } from "../../config.js";
2
2
  export type DiagnosisType = "active_running" | "stale_running" | "possibly_stale_running" | "orphaned_running" | "artifact_collection_stuck" | "done_candidate" | "unknown" | "terminal";
3
3
  export type DiagnosisConfidence = "high" | "medium" | "low";
4
4
  export type SafeAction = "leave_unchanged" | "mark_failed_stale" | "mark_orphaned" | "mark_done_by_agent" | "collect_artifacts" | "recollect_artifacts" | "needs_fix";
@@ -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 } 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 } 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
  // ── Thresholds (seconds) ──────────────────────────────────────────
10
12
  //
11
13
  // These are intentionally conservative. A task is only "stale" when multiple
@@ -55,8 +57,7 @@ function readTailLines(filePath, lines) {
55
57
  if (!existsSync(filePath))
56
58
  return null;
57
59
  try {
58
- const raw = readFileSync(filePath, "utf-8");
59
- const tail = raw.split("\n").slice(-lines).join("\n");
60
+ const tail = readTextFileTailLinesSync(filePath, lines, 512 * 1024);
60
61
  return redactSensitiveContent(tail).content;
61
62
  }
62
63
  catch {
@@ -81,16 +82,11 @@ export function diagnoseTask(input, config = getConfig()) {
81
82
  const statusStr = typeof status.status === "string" ? status.status : "unknown";
82
83
  const phaseStr = (runtime.phase || (typeof status.phase === "string" ? status.phase : "queued"));
83
84
  // ── Terminal tasks: no diagnosis needed ──
84
- const terminalStatuses = new Set([
85
- "done", "done_by_agent", "failed", "failed_verification",
86
- "failed_scope_violation", "failed_policy_violation",
87
- "failed_stale", "orphaned", "canceled",
88
- ]);
89
- if (terminalStatuses.has(statusStr)) {
85
+ if (isTerminalTaskStatus(statusStr)) {
90
86
  return buildTerminalDiagnosis(taskId, statusStr, phaseStr);
91
87
  }
92
- // ── Only diagnose running / collecting_artifacts / pending ──
93
- if (statusStr !== "running" && statusStr !== "collecting_artifacts" && statusStr !== "pending") {
88
+ // Diagnose both current status spellings and legacy phase-as-status records.
89
+ if (!isActiveTaskStatus(statusStr)) {
94
90
  return buildUnknownDiagnosis(taskId, statusStr, phaseStr, [`unexpected status "${statusStr}" for diagnosis`]);
95
91
  }
96
92
  // ── Gather evidence ──