patchwarden 1.5.1 → 1.6.1

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 (573) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +152 -25
  3. package/README.md +120 -23
  4. package/dist/assessments/agentAssessor.js +21 -12
  5. package/dist/assessments/assessmentStore.js +42 -34
  6. package/dist/config.d.ts +15 -0
  7. package/dist/config.js +66 -13
  8. package/dist/control/fileManager.d.ts +2 -0
  9. package/dist/control/fileManager.js +39 -0
  10. package/dist/control/middleware/auth.d.ts +6 -0
  11. package/dist/control/middleware/auth.js +14 -5
  12. package/dist/control/middleware/static.js +16 -3
  13. package/dist/control/routeTable.d.ts +24 -0
  14. package/dist/control/routeTable.js +263 -0
  15. package/dist/control/routes/audit.js +17 -7
  16. package/dist/control/routes/evidence.js +1 -1
  17. package/dist/control/routes/lineage.js +1 -1
  18. package/dist/control/routes/process.d.ts +7 -0
  19. package/dist/control/routes/process.js +329 -42
  20. package/dist/control/routes/sessions.d.ts +9 -1
  21. package/dist/control/routes/sessions.js +84 -21
  22. package/dist/control/routes/status.d.ts +46 -1
  23. package/dist/control/routes/status.js +72 -15
  24. package/dist/control/routes/taskActions.js +24 -60
  25. package/dist/control/routes/tasks.d.ts +4 -0
  26. package/dist/control/routes/tasks.js +61 -62
  27. package/dist/control/routes/workspace.js +50 -23
  28. package/dist/control/runtime.d.ts +3 -2
  29. package/dist/control/runtime.js +63 -30
  30. package/dist/control/server.js +36 -365
  31. package/dist/control/shared.d.ts +25 -1
  32. package/dist/control/shared.js +64 -11
  33. package/dist/direct/directAudit.js +234 -225
  34. package/dist/direct/directGuards.d.ts +2 -1
  35. package/dist/direct/directGuards.js +42 -29
  36. package/dist/direct/directPatch.d.ts +6 -1
  37. package/dist/direct/directPatch.js +55 -8
  38. package/dist/direct/directSessionStore.d.ts +16 -6
  39. package/dist/direct/directSessionStore.js +127 -35
  40. package/dist/direct/directVerification.js +8 -16
  41. package/dist/doctor.js +62 -52
  42. package/dist/goal/acceptanceEngine.d.ts +1 -1
  43. package/dist/goal/goalProgress.js +75 -69
  44. package/dist/goal/goalReport.js +5 -12
  45. package/dist/goal/goalStatus.d.ts +2 -0
  46. package/dist/goal/goalStatus.js +10 -3
  47. package/dist/goal/goalStore.d.ts +9 -2
  48. package/dist/goal/goalStore.js +152 -45
  49. package/dist/goal/handoffExport.js +6 -6
  50. package/dist/goal/specKitImport.js +97 -96
  51. package/dist/goal/subgoalSync.js +11 -9
  52. package/dist/goal/worktreeManager.d.ts +2 -0
  53. package/dist/goal/worktreeManager.js +209 -136
  54. package/dist/httpServer.js +111 -32
  55. package/dist/logging.js +5 -3
  56. package/dist/policy/projectPolicy.js +5 -2
  57. package/dist/release/releaseGate.d.ts +1 -1
  58. package/dist/release/releaseGate.js +78 -25
  59. package/dist/runner/agentInvocation.d.ts +14 -0
  60. package/dist/runner/agentInvocation.js +96 -4
  61. package/dist/runner/changeCapture.d.ts +13 -5
  62. package/dist/runner/changeCapture.js +243 -61
  63. package/dist/runner/postTaskCleanup.js +48 -19
  64. package/dist/runner/processSecurity.d.ts +56 -0
  65. package/dist/runner/processSecurity.js +373 -0
  66. package/dist/runner/runTask.d.ts +1 -1
  67. package/dist/runner/runTask.js +115 -87
  68. package/dist/runner/simpleProcess.d.ts +3 -0
  69. package/dist/runner/simpleProcess.js +50 -36
  70. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  71. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  72. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
  73. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  74. package/dist/runner/taskStatusStore.d.ts +11 -0
  75. package/dist/runner/taskStatusStore.js +24 -0
  76. package/dist/runner/watch.d.ts +14 -1
  77. package/dist/runner/watch.js +256 -48
  78. package/dist/security/contentRedaction.js +2 -2
  79. package/dist/security/discoveryTokenStore.d.ts +3 -2
  80. package/dist/security/discoveryTokenStore.js +17 -2
  81. package/dist/security/loopbackHost.d.ts +2 -0
  82. package/dist/security/loopbackHost.js +16 -0
  83. package/dist/security/pathGuard.js +32 -17
  84. package/dist/security/planGuard.js +2 -1
  85. package/dist/security/runtimeGuard.js +9 -6
  86. package/dist/security/secretComparison.d.ts +3 -0
  87. package/dist/security/secretComparison.js +11 -0
  88. package/dist/security/sensitiveGuard.d.ts +5 -0
  89. package/dist/security/sensitiveGuard.js +27 -8
  90. package/dist/security/toolInvocationGuard.d.ts +2 -2
  91. package/dist/security/toolInvocationGuard.js +1 -1
  92. package/dist/security/workspaceRootGuard.d.ts +8 -0
  93. package/dist/security/workspaceRootGuard.js +47 -0
  94. package/dist/smoke-test.js +24 -23
  95. package/dist/tools/catalog/index.d.ts +4 -0
  96. package/dist/tools/catalog/index.js +4 -0
  97. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  98. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  99. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +6 -15
  100. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  101. package/dist/tools/definitions/toolDefs.js +1177 -0
  102. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  103. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  104. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +1 -1
  105. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +99 -29
  106. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
  107. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +7 -7
  108. package/dist/tools/diagnostics/index.d.ts +6 -0
  109. package/dist/tools/diagnostics/index.js +6 -0
  110. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  111. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  112. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +12 -8
  113. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +23 -13
  114. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  115. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  116. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +6 -6
  117. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +1 -1
  118. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  119. package/dist/tools/direct/index.d.ts +3 -0
  120. package/dist/tools/direct/index.js +3 -0
  121. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  122. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  123. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  124. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  125. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  126. package/dist/tools/discovery/index.d.ts +3 -0
  127. package/dist/tools/discovery/index.js +3 -0
  128. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  129. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  130. package/dist/tools/dispatch/coreDispatch.js +32 -31
  131. package/dist/tools/dispatch/diagnosticDispatch.d.ts +3 -5
  132. package/dist/tools/dispatch/diagnosticDispatch.js +70 -68
  133. package/dist/tools/dispatch/directDispatch.js +11 -10
  134. package/dist/tools/dispatch/goalDispatch.js +5 -4
  135. package/dist/tools/dispatch/releaseDispatch.js +3 -2
  136. package/dist/tools/dispatch/validation.d.ts +7 -0
  137. package/dist/tools/dispatch/validation.js +71 -0
  138. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  139. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +1 -1
  140. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  141. package/dist/tools/goals/index.d.ts +3 -0
  142. package/dist/tools/goals/index.js +3 -0
  143. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  144. package/dist/tools/registry.d.ts +7 -11
  145. package/dist/tools/registry.js +17 -1178
  146. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  147. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  148. package/dist/tools/release/index.d.ts +2 -0
  149. package/dist/tools/release/index.js +2 -0
  150. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  151. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  152. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  153. package/dist/tools/tasks/cancelTask.js +85 -0
  154. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +10 -2
  155. package/dist/tools/{createTask.js → tasks/createTask.js} +42 -27
  156. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  157. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
  158. package/dist/tools/{evidencePack.js → tasks/evidencePack.js} +34 -22
  159. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  160. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  161. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
  162. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  163. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
  164. package/dist/tools/tasks/index.d.ts +18 -0
  165. package/dist/tools/tasks/index.js +18 -0
  166. package/dist/tools/tasks/killTask.d.ts +1 -0
  167. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  168. package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
  169. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
  170. package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
  171. package/dist/tools/tasks/retryTask.js +60 -0
  172. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  173. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +58 -30
  174. package/dist/tools/tasks/runVerification.js +38 -0
  175. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  176. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  177. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  178. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  179. package/dist/tools/workspace/applyPatch.js +53 -0
  180. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  181. package/dist/tools/workspace/index.d.ts +10 -0
  182. package/dist/tools/workspace/index.js +10 -0
  183. package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
  184. package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
  185. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  186. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  187. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  188. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  189. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  190. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  191. package/dist/tools/workspace/syncFile.js +134 -0
  192. package/dist/utils/atomicFile.d.ts +7 -0
  193. package/dist/utils/atomicFile.js +48 -0
  194. package/dist/utils/boundedFile.d.ts +8 -0
  195. package/dist/utils/boundedFile.js +93 -0
  196. package/dist/utils/lockedJsonFile.d.ts +15 -0
  197. package/dist/utils/lockedJsonFile.js +224 -0
  198. package/dist/utils/platform.d.ts +11 -0
  199. package/dist/utils/platform.js +26 -0
  200. package/dist/utils/stableJson.d.ts +2 -0
  201. package/dist/utils/stableJson.js +12 -0
  202. package/dist/version.d.ts +3 -2
  203. package/dist/version.js +3 -2
  204. package/dist/watcherStatus.d.ts +2 -2
  205. package/dist/watcherStatus.js +18 -0
  206. package/docs/CODE_WIKI.md +808 -143
  207. package/docs/architecture.md +15 -55
  208. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  209. package/docs/control-center/README.md +4 -4
  210. package/docs/dashboard-overview.md +4 -4
  211. package/docs/desktop-app.md +175 -0
  212. package/docs/evidence-pack-schema.md +3 -3
  213. package/docs/open-source-application.md +17 -11
  214. package/docs/release-checklist.md +1 -1
  215. package/docs/release-evidence.md +39 -29
  216. package/docs/task-safe-review-workflow.md +5 -5
  217. package/docs/threat-model.md +19 -1
  218. package/examples/config.example.json +4 -2
  219. package/package.json +27 -10
  220. package/scripts/README.md +15 -6
  221. package/scripts/build.js +11 -0
  222. package/scripts/checks/brand-check.js +3 -0
  223. package/scripts/checks/build-output-check.js +37 -0
  224. package/scripts/checks/control-center-smoke.js +91 -6
  225. package/scripts/checks/control-smoke.js +19 -8
  226. package/scripts/checks/http-mcp-smoke.js +82 -4
  227. package/scripts/checks/lifecycle-smoke.js +10 -10
  228. package/scripts/checks/mcp-manifest-check.js +1 -1
  229. package/scripts/checks/mcp-smoke.js +27 -3
  230. package/scripts/checks/package-manifest-check.js +20 -7
  231. package/scripts/checks/unit-tests.js +3 -5
  232. package/scripts/checks/watcher-supervisor-smoke.js +9 -0
  233. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  234. package/scripts/control/manage-patchwarden.ps1 +69 -4
  235. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  236. package/scripts/control/run-background-supervisor.ps1 +41 -0
  237. package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
  238. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  239. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  240. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  241. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  242. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  243. package/scripts/lib/clean-generated-output.js +11 -0
  244. package/scripts/lib/file-discovery.js +12 -0
  245. package/scripts/release/desktop-preflight.js +211 -0
  246. package/scripts/release/pack-clean.js +59 -14
  247. package/src/assessments/agentAssessor.ts +26 -12
  248. package/src/assessments/assessmentStore.ts +52 -40
  249. package/src/config.ts +70 -13
  250. package/src/control/fileManager.ts +40 -0
  251. package/src/control/middleware/auth.ts +22 -4
  252. package/src/control/middleware/static.ts +16 -3
  253. package/src/control/routeTable.ts +301 -0
  254. package/src/control/routes/audit.ts +14 -6
  255. package/src/control/routes/evidence.ts +1 -1
  256. package/src/control/routes/lineage.ts +1 -1
  257. package/src/control/routes/process.ts +341 -40
  258. package/src/control/routes/sessions.ts +88 -25
  259. package/src/control/routes/status.ts +97 -17
  260. package/src/control/routes/taskActions.ts +24 -58
  261. package/src/control/routes/tasks.ts +66 -58
  262. package/src/control/routes/workspace.ts +56 -25
  263. package/src/control/runtime.ts +69 -33
  264. package/src/control/server.ts +36 -342
  265. package/src/control/shared.ts +77 -11
  266. package/src/direct/directAudit.ts +17 -9
  267. package/src/direct/directGuards.ts +72 -44
  268. package/src/direct/directPatch.ts +95 -8
  269. package/src/direct/directSessionStore.ts +211 -55
  270. package/src/direct/directVerification.ts +9 -23
  271. package/src/doctor.ts +73 -59
  272. package/src/goal/acceptanceEngine.ts +1 -1
  273. package/src/goal/goalProgress.ts +17 -13
  274. package/src/goal/goalReport.ts +5 -13
  275. package/src/goal/goalStatus.ts +19 -3
  276. package/src/goal/goalStore.ts +241 -54
  277. package/src/goal/handoffExport.ts +6 -6
  278. package/src/goal/specKitImport.ts +12 -12
  279. package/src/goal/subgoalSync.ts +11 -8
  280. package/src/goal/worktreeManager.ts +139 -22
  281. package/src/httpServer.ts +113 -30
  282. package/src/logging.ts +10 -3
  283. package/src/policy/projectPolicy.ts +5 -2
  284. package/src/release/releaseGate.ts +74 -27
  285. package/src/runner/agentInvocation.ts +126 -4
  286. package/src/runner/changeCapture.ts +291 -57
  287. package/src/runner/postTaskCleanup.ts +41 -17
  288. package/src/runner/processSecurity.ts +433 -0
  289. package/src/runner/runTask.ts +138 -91
  290. package/src/runner/simpleProcess.ts +66 -31
  291. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  292. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
  293. package/src/runner/taskStatusStore.ts +54 -0
  294. package/src/runner/watch.ts +281 -51
  295. package/src/security/contentRedaction.ts +2 -2
  296. package/src/security/discoveryTokenStore.ts +18 -4
  297. package/src/security/loopbackHost.ts +19 -0
  298. package/src/security/pathGuard.ts +35 -16
  299. package/src/security/planGuard.ts +2 -1
  300. package/src/security/runtimeGuard.ts +9 -6
  301. package/src/security/secretComparison.ts +13 -0
  302. package/src/security/sensitiveGuard.ts +27 -8
  303. package/src/security/toolInvocationGuard.ts +2 -2
  304. package/src/security/workspaceRootGuard.ts +53 -0
  305. package/src/smoke-test.ts +25 -24
  306. package/src/tools/catalog/index.ts +4 -0
  307. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  308. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +7 -16
  309. package/src/tools/definitions/toolDefs.ts +1264 -0
  310. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  311. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +99 -32
  312. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +8 -8
  313. package/src/tools/diagnostics/index.ts +6 -0
  314. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  315. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +27 -17
  316. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  317. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +6 -6
  318. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +7 -5
  319. package/src/tools/direct/index.ts +3 -0
  320. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  321. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  322. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  323. package/src/tools/discovery/index.ts +3 -0
  324. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  325. package/src/tools/dispatch/coreDispatch.ts +36 -31
  326. package/src/tools/dispatch/diagnosticDispatch.ts +26 -21
  327. package/src/tools/dispatch/directDispatch.ts +11 -10
  328. package/src/tools/dispatch/goalDispatch.ts +5 -4
  329. package/src/tools/dispatch/releaseDispatch.ts +3 -2
  330. package/src/tools/dispatch/validation.ts +78 -0
  331. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  332. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +41 -26
  333. package/src/tools/goals/index.ts +3 -0
  334. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  335. package/src/tools/registry.ts +17 -1261
  336. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  337. package/src/tools/release/index.ts +2 -0
  338. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  339. package/src/tools/tasks/cancelTask.ts +109 -0
  340. package/src/tools/{createTask.ts → tasks/createTask.ts} +49 -26
  341. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
  342. package/src/tools/{evidencePack.ts → tasks/evidencePack.ts} +36 -22
  343. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  344. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
  345. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  346. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
  347. package/src/tools/tasks/index.ts +18 -0
  348. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
  349. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
  350. package/src/tools/tasks/retryTask.ts +69 -0
  351. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +69 -34
  352. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  353. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  354. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  355. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  356. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  357. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  358. package/src/tools/workspace/index.ts +10 -0
  359. package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
  360. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  361. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  362. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  363. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  364. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  365. package/src/tools/workspace/syncFile.ts +292 -0
  366. package/src/utils/atomicFile.ts +58 -0
  367. package/src/utils/boundedFile.ts +116 -0
  368. package/src/utils/lockedJsonFile.ts +251 -0
  369. package/src/utils/platform.ts +31 -0
  370. package/src/utils/stableJson.ts +12 -0
  371. package/src/version.ts +3 -2
  372. package/src/watcherStatus.ts +19 -1
  373. package/ui/desktop-bootstrap.js +11 -0
  374. package/ui/desktop-bridge.js +36 -0
  375. package/ui/desktop.css +160 -0
  376. package/ui/getting-started.js +93 -0
  377. package/ui/i18n.js +484 -0
  378. package/ui/log-parser.js +30 -0
  379. package/ui/pages/audit.html +12 -0
  380. package/ui/pages/dashboard.html +90 -147
  381. package/ui/pages/direct-sessions.html +123 -26
  382. package/ui/pages/getting-started.html +43 -0
  383. package/ui/pages/logs.html +105 -22
  384. package/ui/pages/settings.html +101 -0
  385. package/ui/pages/task-detail.html +12 -0
  386. package/ui/pages/tasks.html +54 -8
  387. package/ui/pages/workspace.html +38 -1
  388. package/ui/settings.js +245 -0
  389. package/PatchWarden-Control.cmd +0 -6
  390. package/Restart-PatchWarden-Control.cmd +0 -6
  391. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  392. package/dist/test/unit/acceptance-engine.test.js +0 -228
  393. package/dist/test/unit/agent-router.test.d.ts +0 -1
  394. package/dist/test/unit/agent-router.test.js +0 -287
  395. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  396. package/dist/test/unit/android-doctor.test.js +0 -118
  397. package/dist/test/unit/apply-patch.test.d.ts +0 -1
  398. package/dist/test/unit/apply-patch.test.js +0 -225
  399. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  400. package/dist/test/unit/audit-checks.test.js +0 -350
  401. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  402. package/dist/test/unit/chinese-path.test.js +0 -91
  403. package/dist/test/unit/command-guard.test.d.ts +0 -1
  404. package/dist/test/unit/command-guard.test.js +0 -160
  405. package/dist/test/unit/create-task.test.d.ts +0 -1
  406. package/dist/test/unit/create-task.test.js +0 -197
  407. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  408. package/dist/test/unit/diagnose-task.test.js +0 -457
  409. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  410. package/dist/test/unit/direct-guards.test.js +0 -328
  411. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  412. package/dist/test/unit/discovery-token-store.test.js +0 -139
  413. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  414. package/dist/test/unit/evidence-pack.test.js +0 -224
  415. package/dist/test/unit/get-task-status.test.d.ts +0 -1
  416. package/dist/test/unit/get-task-status.test.js +0 -174
  417. package/dist/test/unit/get-task-summary.test.d.ts +0 -1
  418. package/dist/test/unit/get-task-summary.test.js +0 -146
  419. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  420. package/dist/test/unit/goal-graph.test.js +0 -298
  421. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  422. package/dist/test/unit/goal-progress.test.js +0 -381
  423. package/dist/test/unit/goal-report.test.d.ts +0 -1
  424. package/dist/test/unit/goal-report.test.js +0 -159
  425. package/dist/test/unit/goal-status.test.d.ts +0 -1
  426. package/dist/test/unit/goal-status.test.js +0 -215
  427. package/dist/test/unit/goal-store.test.d.ts +0 -1
  428. package/dist/test/unit/goal-store.test.js +0 -253
  429. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  430. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  431. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  432. package/dist/test/unit/goal-tools-registry.test.js +0 -192
  433. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  434. package/dist/test/unit/handoff-export.test.js +0 -263
  435. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  436. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  437. package/dist/test/unit/logging.test.d.ts +0 -1
  438. package/dist/test/unit/logging.test.js +0 -397
  439. package/dist/test/unit/path-guard.test.d.ts +0 -1
  440. package/dist/test/unit/path-guard.test.js +0 -133
  441. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  442. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  443. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  444. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  445. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  446. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  447. package/dist/test/unit/release-gate.test.d.ts +0 -1
  448. package/dist/test/unit/release-gate.test.js +0 -242
  449. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  450. package/dist/test/unit/run-task-loop.test.js +0 -380
  451. package/dist/test/unit/safe-status.test.d.ts +0 -1
  452. package/dist/test/unit/safe-status.test.js +0 -165
  453. package/dist/test/unit/safe-views.test.d.ts +0 -1
  454. package/dist/test/unit/safe-views.test.js +0 -171
  455. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  456. package/dist/test/unit/schema-drift-check.test.js +0 -176
  457. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  458. package/dist/test/unit/sensitive-guard.test.js +0 -104
  459. package/dist/test/unit/spec-kit-import.test.d.ts +0 -1
  460. package/dist/test/unit/spec-kit-import.test.js +0 -341
  461. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  462. package/dist/test/unit/subgoal-sync.test.js +0 -183
  463. package/dist/test/unit/sync-file.test.d.ts +0 -1
  464. package/dist/test/unit/sync-file.test.js +0 -154
  465. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  466. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  467. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  468. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  469. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  470. package/dist/test/unit/toolSearch.test.js +0 -571
  471. package/dist/test/unit/wait-for-task.test.d.ts +0 -1
  472. package/dist/test/unit/wait-for-task.test.js +0 -144
  473. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  474. package/dist/test/unit/watcher-status.test.js +0 -169
  475. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  476. package/dist/test/unit/worktree-manager.test.js +0 -176
  477. package/dist/tools/applyPatch.js +0 -41
  478. package/dist/tools/cancelTask.d.ts +0 -30
  479. package/dist/tools/cancelTask.js +0 -64
  480. package/dist/tools/finalizeDirectSession.js +0 -84
  481. package/dist/tools/goalSubgoalTask.js +0 -110
  482. package/dist/tools/killTask.d.ts +0 -15
  483. package/dist/tools/retryTask.js +0 -45
  484. package/dist/tools/runVerification.js +0 -36
  485. package/dist/tools/syncFile.js +0 -65
  486. package/docs/control-center/control-center-daily-driver.md +0 -211
  487. package/docs/control-center/control-center-mvp.md +0 -205
  488. package/docs/control-center/control-center-phase2.md +0 -159
  489. package/docs/execution-plan-2026-07-09.md +0 -315
  490. package/docs/release-v0.6.0.md +0 -71
  491. package/docs/release-v0.6.1.md +0 -75
  492. package/docs/release-v0.6.4.md +0 -45
  493. package/docs/roadmap-execution-and-acceptance.md +0 -365
  494. package/src/test/unit/acceptance-engine.test.ts +0 -261
  495. package/src/test/unit/agent-router.test.ts +0 -342
  496. package/src/test/unit/android-doctor.test.ts +0 -158
  497. package/src/test/unit/apply-patch.test.ts +0 -293
  498. package/src/test/unit/audit-checks.test.ts +0 -567
  499. package/src/test/unit/chinese-path.test.ts +0 -106
  500. package/src/test/unit/command-guard.test.ts +0 -221
  501. package/src/test/unit/create-task.test.ts +0 -255
  502. package/src/test/unit/diagnose-task.test.ts +0 -544
  503. package/src/test/unit/direct-guards.test.ts +0 -467
  504. package/src/test/unit/discovery-token-store.test.ts +0 -181
  505. package/src/test/unit/evidence-pack.test.ts +0 -251
  506. package/src/test/unit/get-task-status.test.ts +0 -203
  507. package/src/test/unit/get-task-summary.test.ts +0 -173
  508. package/src/test/unit/goal-graph.test.ts +0 -347
  509. package/src/test/unit/goal-progress.test.ts +0 -538
  510. package/src/test/unit/goal-report.test.ts +0 -189
  511. package/src/test/unit/goal-status.test.ts +0 -270
  512. package/src/test/unit/goal-store.test.ts +0 -318
  513. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  514. package/src/test/unit/goal-tools-registry.test.ts +0 -245
  515. package/src/test/unit/handoff-export.test.ts +0 -295
  516. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  517. package/src/test/unit/logging.test.ts +0 -497
  518. package/src/test/unit/path-guard.test.ts +0 -180
  519. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  520. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  521. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  522. package/src/test/unit/release-gate.test.ts +0 -314
  523. package/src/test/unit/run-task-loop.test.ts +0 -425
  524. package/src/test/unit/safe-status.test.ts +0 -187
  525. package/src/test/unit/safe-views.test.ts +0 -184
  526. package/src/test/unit/schema-drift-check.test.ts +0 -260
  527. package/src/test/unit/sensitive-guard.test.ts +0 -124
  528. package/src/test/unit/spec-kit-import.test.ts +0 -429
  529. package/src/test/unit/subgoal-sync.test.ts +0 -236
  530. package/src/test/unit/sync-file.test.ts +0 -231
  531. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  532. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  533. package/src/test/unit/toolSearch.test.ts +0 -756
  534. package/src/test/unit/wait-for-task.test.ts +0 -176
  535. package/src/test/unit/watcher-status.test.ts +0 -190
  536. package/src/test/unit/worktree-manager.test.ts +0 -247
  537. package/src/tools/cancelTask.ts +0 -70
  538. package/src/tools/retryTask.ts +0 -53
  539. package/src/tools/syncFile.ts +0 -122
  540. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  541. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  542. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  543. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  544. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  545. /package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +0 -0
  546. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  547. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  548. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  549. /package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +0 -0
  550. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  551. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  552. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  553. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  554. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  555. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  556. /package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +0 -0
  557. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  558. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  559. /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
  560. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  561. /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
  562. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  563. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  564. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  565. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  566. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  567. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  568. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  569. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  570. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  571. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  572. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
  573. /package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +0 -0
@@ -1,10 +1,7 @@
1
1
  import { spawn, spawnSync, type ChildProcess } from "node:child_process";
2
2
  import {
3
- createWriteStream,
4
3
  existsSync,
5
4
  readFileSync,
6
- writeFileSync,
7
- type WriteStream,
8
5
  } from "node:fs";
9
6
  import { isAbsolute, join, relative, resolve } from "node:path";
10
7
  import { getTasksDir, getPlansDir, getConfig, resolveWorkspaceRoot } from "../config.js";
@@ -12,11 +9,17 @@ import { guardPath, guardWorkspacePath } from "../security/pathGuard.js";
12
9
  import {
13
10
  guardTestCommand,
14
11
  } from "../security/commandGuard.js";
15
- import { writeTaskProgress } from "../taskProgress.js";
16
- import { writeTaskRuntime } from "../taskRuntime.js";
12
+ import { writeTaskProgress } from "./taskProgress.js";
13
+ import { writeTaskRuntime } from "./taskRuntime.js";
14
+ import {
15
+ claimPendingTask,
16
+ readTaskStatusFile,
17
+ updateTaskStatusFile,
18
+ type TaskStatusRecord,
19
+ } from "./taskStatusStore.js";
17
20
  import { validateAssessmentFreshness } from "../assessments/assessmentStore.js";
18
21
  import { buildAgentInvocation, buildExecutionPrompt } from "./agentInvocation.js";
19
- import type { TaskPhase, TaskStatus } from "../tools/createTask.js";
22
+ import type { TaskPhase, TaskStatus } from "../tools/tasks/createTask.js";
20
23
  import {
21
24
  buildChangeArtifacts,
22
25
  captureRepoSnapshot,
@@ -35,8 +38,18 @@ import {
35
38
  type ChangedFileGroups,
36
39
  } from "./changeCapture.js";
37
40
  import { PatchWardenError, errorPayload } from "../errors.js";
38
- import { diagnoseAndroidBuild } from "../tools/androidDoctor.js";
41
+ import { ARTIFACT_SCHEMA_VERSION } from "../version.js";
42
+ import { diagnoseAndroidBuild } from "../tools/workspace/androidDoctor.js";
39
43
  import { runPostTaskCleanup, type PostTaskCleanupReport } from "./postTaskCleanup.js";
44
+ import { atomicWriteFileSync, atomicWriteJsonFileSync } from "../utils/atomicFile.js";
45
+ import {
46
+ allowedEnvironmentValues,
47
+ buildChildEnvironment,
48
+ redactProcessOutput,
49
+ resolvePackageManagerInvocation,
50
+ resolveTrustedExecutable,
51
+ SecureProcessLogCapture,
52
+ } from "./processSecurity.js";
40
53
 
41
54
  const HEARTBEAT_INTERVAL_MS = 2000;
42
55
  const GRACEFUL_KILL_MS = 2000;
@@ -95,7 +108,7 @@ interface TaskContext {
95
108
  wsRoot: string;
96
109
  config: ReturnType<typeof getConfig>;
97
110
  plansDir: string;
98
- initialStatus: Record<string, any>;
111
+ initialStatus: TaskStatusRecord;
99
112
  planId: string;
100
113
  agentName: string;
101
114
  testCommand: string;
@@ -154,7 +167,25 @@ async function prepareTask(taskId: string): Promise<TaskContext | TaskRunResult>
154
167
  const statusFile = join(taskDir, "status.json");
155
168
  if (!existsSync(statusFile)) throw new Error(`Task not found: "${taskId}"`);
156
169
 
157
- const initialStatus = readStatus(statusFile);
170
+ const startedAtMs = Date.now();
171
+ const claim = claimPendingTask(statusFile, {
172
+ status: "running",
173
+ phase: "preparing",
174
+ started_at: new Date(startedAtMs).toISOString(),
175
+ last_heartbeat_at: new Date(startedAtMs).toISOString(),
176
+ current_command: null,
177
+ error: null,
178
+ });
179
+ if (!claim.claimed) {
180
+ const currentStatus = String(claim.status.status || "failed") as TaskStatus;
181
+ return {
182
+ task_id: taskId,
183
+ status: currentStatus,
184
+ error: `Task is ${currentStatus}; only pending tasks can be claimed for execution.`,
185
+ };
186
+ }
187
+
188
+ const initialStatus = claim.status;
158
189
  const planId = String(initialStatus.plan_id || "");
159
190
  const agentName = String(initialStatus.agent || "");
160
191
  const rawRepoPath = String(initialStatus.resolved_repo_path || initialStatus.repo_path || wsRoot);
@@ -167,7 +198,6 @@ async function prepareTask(taskId: string): Promise<TaskContext | TaskRunResult>
167
198
  } catch (error) {
168
199
  return failBeforeExecution(taskId, taskDir, errorMessage(error), error);
169
200
  }
170
- const startedAtMs = Date.now();
171
201
  const deadlineMs = startedAtMs + timeoutSeconds * 1000;
172
202
 
173
203
  let repoPath: string;
@@ -199,15 +229,7 @@ async function prepareTask(taskId: string): Promise<TaskContext | TaskRunResult>
199
229
  }
200
230
  }
201
231
 
202
- updateStatus(taskDir, {
203
- status: "running",
204
- phase: "preparing",
205
- started_at: new Date(startedAtMs).toISOString(),
206
- last_heartbeat_at: new Date().toISOString(),
207
- timeout_seconds: timeoutSeconds,
208
- current_command: null,
209
- error: null,
210
- });
232
+ updateStatus(taskDir, { timeout_seconds: timeoutSeconds });
211
233
  writeTaskRuntime(taskDir, {
212
234
  task_started_at: new Date(startedAtMs).toISOString(),
213
235
  watcher_instance_id: process.env.PATCHWARDEN_WATCHER_INSTANCE_ID || undefined,
@@ -264,6 +286,8 @@ async function executeAgent(ctx: TaskContext): Promise<ExecutionState> {
264
286
  deadlineMs: ctx.deadlineMs,
265
287
  stdoutPath: join(ctx.taskDir, "stdout.log"),
266
288
  stderrPath: join(ctx.taskDir, "stderr.log"),
289
+ environmentVariableNames: invocation.environmentVariableNames,
290
+ blockedEnvironmentVariableNames: invocation.blockedEnvironmentVariableNames,
267
291
  });
268
292
 
269
293
  const agentResult = state.agentResult;
@@ -342,7 +366,7 @@ async function collectArtifacts(ctx: TaskContext, state: ExecutionState): Promis
342
366
  skipped: [{ path: ".", reason: "post_task_cleanup", skip_reason: errorMessage(error) }],
343
367
  source_files_touched: 0,
344
368
  };
345
- writeFileSync(join(ctx.taskDir, "post-task-cleanup.json"), JSON.stringify(cleanupReport, null, 2), "utf-8");
369
+ atomicWriteJsonFileSync(join(ctx.taskDir, "post-task-cleanup.json"), cleanupReport);
346
370
  updateStatus(ctx.taskDir, { cleanup: cleanupReport });
347
371
  }
348
372
  }
@@ -353,21 +377,20 @@ async function collectArtifacts(ctx: TaskContext, state: ExecutionState): Promis
353
377
  let artifactStatus: "collected" | "partial" | "failed" | "timeout" = "collected";
354
378
  let artifactCollectionError: string | null = null;
355
379
  let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
380
+ const artifactController = new AbortController();
356
381
  try {
357
- const collectionResult = await Promise.race([
358
- Promise.resolve().then(async () => {
359
- const afterSnapshot = await captureRepoSnapshot(ctx.repoPath);
360
- writeSnapshot(ctx.taskDir, "git-after.json", afterSnapshot);
361
- return await buildChangeArtifacts(ctx.repoPath, ctx.beforeSnapshot, afterSnapshot);
362
- }),
363
- new Promise<never>((_, reject) => {
364
- timeoutHandle = setTimeout(() => reject(new Error("Artifact collection timed out")), ARTIFACT_COLLECTION_TIMEOUT_MS);
365
- }),
366
- ]);
367
- if (timeoutHandle) clearTimeout(timeoutHandle);
368
- changes = collectionResult;
382
+ timeoutHandle = setTimeout(() => {
383
+ artifactController.abort(new Error("Artifact collection timed out"));
384
+ }, ARTIFACT_COLLECTION_TIMEOUT_MS);
385
+ const afterSnapshot = await captureRepoSnapshot(ctx.repoPath, artifactController.signal);
386
+ writeSnapshot(ctx.taskDir, "git-after.json", afterSnapshot);
387
+ changes = await buildChangeArtifacts(
388
+ ctx.repoPath,
389
+ ctx.beforeSnapshot,
390
+ afterSnapshot,
391
+ artifactController.signal,
392
+ );
369
393
  } catch (error) {
370
- if (timeoutHandle) clearTimeout(timeoutHandle);
371
394
  state.lastCaughtError = error;
372
395
  artifactCollectionError = errorMessage(error);
373
396
  artifactStatus = artifactCollectionError.includes("timed out") ? "timeout" : "failed";
@@ -386,7 +409,7 @@ async function collectArtifacts(ctx: TaskContext, state: ExecutionState): Promis
386
409
  unavailable_reason: `Change capture failed: ${artifactCollectionError}`,
387
410
  artifact_hygiene: emptyArtifactHygiene(),
388
411
  };
389
- writeFileSync(join(ctx.taskDir, "partial_result.md"), [
412
+ atomicWriteFileSync(join(ctx.taskDir, "partial_result.md"), [
390
413
  "# PatchWarden Partial Result",
391
414
  "",
392
415
  `Task: ${ctx.taskId}`,
@@ -406,13 +429,15 @@ async function collectArtifacts(ctx: TaskContext, state: ExecutionState): Promis
406
429
  "## Note",
407
430
  "Artifact collection did not complete. The task result may be incomplete. Review stdout.log, stderr.log, and verify.json for details.",
408
431
  "",
409
- ].join("\n"), "utf-8");
432
+ ].join("\n"));
410
433
  if (state.finalStatus === "done_by_agent") {
411
434
  state.finalError = `Change capture failed: ${artifactCollectionError}`;
412
435
  state.finalStatus = "failed";
413
436
  } else {
414
437
  state.finalError ||= `Change capture failed: ${artifactCollectionError}`;
415
438
  }
439
+ } finally {
440
+ if (timeoutHandle) clearTimeout(timeoutHandle);
416
441
  }
417
442
  const artifactCollectionFinishedAt = new Date().toISOString();
418
443
 
@@ -440,18 +465,18 @@ async function collectArtifacts(ctx: TaskContext, state: ExecutionState): Promis
440
465
 
441
466
  applyScopeViolationVerdict(ctx, state, changes, newOutOfScopeChanges, preexistingExternalDirty);
442
467
 
443
- writeFileSync(join(ctx.taskDir, "git.diff"), changes.diff, "utf-8");
444
- writeFileSync(join(ctx.taskDir, "diff.patch"), changes.diff, "utf-8");
445
- writeFileSync(join(ctx.taskDir, "changed-files.json"), JSON.stringify(changes, null, 2), "utf-8");
468
+ atomicWriteFileSync(join(ctx.taskDir, "git.diff"), changes.diff);
469
+ atomicWriteFileSync(join(ctx.taskDir, "diff.patch"), changes.diff);
470
+ atomicWriteJsonFileSync(join(ctx.taskDir, "changed-files.json"), { schema_version: ARTIFACT_SCHEMA_VERSION, ...changes });
446
471
  const artifactManifest = await buildArtifactManifest(changes.changed_files, ctx.repoPath, ctx.taskId);
447
- writeFileSync(join(ctx.taskDir, "artifact_manifest.json"), JSON.stringify(artifactManifest, null, 2), "utf-8");
472
+ atomicWriteJsonFileSync(join(ctx.taskDir, "artifact_manifest.json"), artifactManifest);
448
473
  const changedFileGroups = groupChangedFiles(changes.changed_files);
449
- writeFileSync(join(ctx.taskDir, "file-stats.json"), JSON.stringify({
474
+ atomicWriteJsonFileSync(join(ctx.taskDir, "file-stats.json"), {
450
475
  task_id: ctx.taskId,
451
476
  additions: changes.additions,
452
477
  deletions: changes.deletions,
453
478
  files: changes.file_stats,
454
- }, null, 2), "utf-8");
479
+ });
455
480
 
456
481
  return {
457
482
  changes, artifactStatus, artifactCollectionError, artifactCollectionStartedAt, artifactCollectionFinishedAt,
@@ -470,15 +495,15 @@ function applyScopeViolationVerdict(
470
495
  if (newOutOfScopeChanges.length > 0) {
471
496
  state.finalStatus = "failed_scope_violation";
472
497
  state.finalError = `Detected ${newOutOfScopeChanges.length} new change(s) outside resolved_repo_path during task execution.`;
473
- writeFileSync(join(ctx.taskDir, "rollback-plan.json"), JSON.stringify({
498
+ atomicWriteJsonFileSync(join(ctx.taskDir, "rollback-plan.json"), {
474
499
  task_id: ctx.taskId,
475
500
  status: "review_required",
476
501
  automatic_rollback_performed: false,
477
502
  warning: "Review ownership and concurrent edits before rollback. PatchWarden did not modify or restore these files.",
478
503
  out_of_scope_changes: newOutOfScopeChanges,
479
504
  preexisting_external_dirty_files: preexistingExternalDirty,
480
- }, null, 2), "utf-8");
481
- writeFileSync(join(ctx.taskDir, "rollback_scope_violation_plan.md"), [
505
+ });
506
+ atomicWriteFileSync(join(ctx.taskDir, "rollback_scope_violation_plan.md"), [
482
507
  "# Scope Violation Rollback Plan",
483
508
  "",
484
509
  `Task: ${ctx.taskId}`,
@@ -488,7 +513,10 @@ function applyScopeViolationVerdict(
488
513
  "## New out-of-scope files (caused by this task)",
489
514
  ...newOutOfScopeChanges.map((file) => `- ${file.change}: ${file.path}`),
490
515
  "",
491
- ].join("\n"), "utf-8");
516
+ ].join("\n"));
517
+ } else if (changes.diff_redacted) {
518
+ state.finalStatus = "failed_policy_violation";
519
+ state.finalError = "Credential-like content was detected in the task diff. PatchWarden redacted the evidence; remove the sensitive content before retrying.";
492
520
  } else if (ctx.changePolicy === "no_changes" && changes.changed_files.length > 0) {
493
521
  state.finalStatus = "failed_policy_violation";
494
522
  state.finalError = `Task policy requires no repository changes, but detected ${changes.changed_files.length} change(s).`;
@@ -500,17 +528,19 @@ function finalizeTask(ctx: TaskContext, state: ExecutionState, evidence: Artifac
500
528
  outOfScopeChanges, newOutOfScopeChanges, preexistingExternalDirty, preexistingWarnings, cleanupReport,
501
529
  artifactManifest, changedFileGroups } = evidence;
502
530
 
503
- writeFileSync(join(ctx.taskDir, "test.log"), buildTestLog(state.testResult), "utf-8");
531
+ atomicWriteFileSync(join(ctx.taskDir, "test.log"), buildTestLog(state.testResult));
504
532
  const verifyJson = buildVerifyJson(ctx.verifyCommands, state.verifyResults, ctx.repoPath);
505
533
  if (newOutOfScopeChanges.length > 0) {
506
534
  verifyJson.status = "failed";
507
535
  verifyJson.failure_reason = "scope_violation";
508
536
  } else if (state.finalStatus === "failed_policy_violation") {
509
537
  verifyJson.status = "failed";
510
- verifyJson.failure_reason = "change_policy_violation";
538
+ verifyJson.failure_reason = changes.diff_redacted
539
+ ? "sensitive_content_violation"
540
+ : "change_policy_violation";
511
541
  }
512
- writeFileSync(join(ctx.taskDir, "verify.json"), JSON.stringify(verifyJson, null, 2), "utf-8");
513
- writeFileSync(join(ctx.taskDir, "verify.log"), buildVerifyLog(verifyJson.commands), "utf-8");
542
+ atomicWriteJsonFileSync(join(ctx.taskDir, "verify.json"), verifyJson);
543
+ atomicWriteFileSync(join(ctx.taskDir, "verify.log"), buildVerifyLog(verifyJson.commands));
514
544
 
515
545
  if (!["canceled", "done_by_agent", "failed_verification", "failed_scope_violation", "failed_policy_violation"].includes(state.finalStatus)) state.finalStatus = "failed";
516
546
  const finalPhase: TaskPhase = state.finalStatus === "done_by_agent" ? "done_by_agent" : (state.finalStatus as TaskPhase);
@@ -547,19 +577,19 @@ function finalizeTask(ctx: TaskContext, state: ExecutionState, evidence: Artifac
547
577
  androidWarning,
548
578
  preexistingWarnings,
549
579
  });
550
- writeFileSync(join(ctx.taskDir, "result.md"), resultMd, "utf-8");
580
+ atomicWriteFileSync(join(ctx.taskDir, "result.md"), resultMd);
551
581
 
552
582
  const resultJson = buildResultJson({
553
583
  ctx, state, evidence, verifyJson, followup, androidDiagnostic, androidWarning,
554
584
  });
555
- writeFileSync(join(ctx.taskDir, "result.json"), JSON.stringify(resultJson, null, 2), "utf-8");
585
+ atomicWriteJsonFileSync(join(ctx.taskDir, "result.json"), resultJson);
556
586
 
557
587
  if (state.finalError) {
558
588
  const structuredError = errorPayload(state.lastCaughtError);
559
- writeFileSync(join(ctx.taskDir, "error.log"), JSON.stringify({
589
+ atomicWriteJsonFileSync(join(ctx.taskDir, "error.log"), {
560
590
  summary: state.finalError,
561
591
  ...structuredError,
562
- }, null, 2), "utf-8");
592
+ });
563
593
  }
564
594
 
565
595
  const finishedAt = new Date().toISOString();
@@ -584,6 +614,8 @@ function finalizeTask(ctx: TaskContext, state: ExecutionState, evidence: Artifac
584
614
  verify_commands: ctx.verifyCommands,
585
615
  diff_available: changes.diff_available,
586
616
  diff_truncated: changes.diff_truncated,
617
+ diff_redacted: changes.diff_redacted === true,
618
+ diff_redaction_categories: changes.diff_redaction_categories ?? [],
587
619
  workspace_dirty_before: changes.workspace_dirty_before,
588
620
  workspace_dirty_after: changes.workspace_dirty_after,
589
621
  workspace_dirty: changes.workspace_dirty_after,
@@ -619,6 +651,7 @@ function buildResultJson(input: {
619
651
  preexistingExternalDirty, cleanupReport, artifactManifest, changedFileGroups } = evidence;
620
652
 
621
653
  return {
654
+ schema_version: ARTIFACT_SCHEMA_VERSION,
622
655
  task_id: ctx.taskId,
623
656
  status: state.finalStatus,
624
657
  agent: ctx.agentName,
@@ -668,6 +701,9 @@ function buildResultJson(input: {
668
701
  warnings: [
669
702
  ...ctx.beforeSnapshot.warnings,
670
703
  ...(changes.diff_truncated ? ["diff.patch was truncated; changed-files.json retains file evidence."] : []),
704
+ ...(changes.diff_redacted ? [
705
+ `Credential-like diff content was redacted (${(changes.diff_redaction_categories ?? []).join(", ") || "sensitive_content"}).`,
706
+ ] : []),
671
707
  ...evidence.preexistingWarnings,
672
708
  ...(androidWarning ? [androidWarning] : []),
673
709
  ],
@@ -753,18 +789,33 @@ async function runManagedProcess(options: {
753
789
  deadlineMs: number;
754
790
  stdoutPath?: string;
755
791
  stderrPath?: string;
792
+ environmentVariableNames?: string[];
793
+ blockedEnvironmentVariableNames?: string[];
794
+ maxLogBytes?: number;
756
795
  }): Promise<ManagedProcessResult> {
757
796
  if (Date.now() >= options.deadlineMs) {
758
797
  return { exitCode: null, stdout: "", stderr: "", spawnError: null, terminationReason: "timeout" };
759
798
  }
760
799
 
800
+ const exactRedactionValues = allowedEnvironmentValues(options.environmentVariableNames);
801
+ const logCapture = new SecureProcessLogCapture(
802
+ [options.stdoutPath, options.stderrPath],
803
+ options.maxLogBytes,
804
+ );
761
805
  let child: ChildProcess;
762
806
  try {
763
- child = spawn(options.command, options.args, {
807
+ const env = buildChildEnvironment({
808
+ cwd: options.cwd,
809
+ allowedNames: options.environmentVariableNames,
810
+ blockedNames: options.blockedEnvironmentVariableNames,
811
+ });
812
+ const command = resolveTrustedExecutable(options.command, options.cwd, { pathValue: env.PATH });
813
+ child = spawn(command, options.args, {
764
814
  cwd: options.cwd,
765
815
  stdio: ["ignore", "pipe", "pipe"],
766
816
  detached: process.platform !== "win32",
767
817
  windowsHide: true,
818
+ env,
768
819
  });
769
820
  } catch (error) {
770
821
  return { exitCode: null, stdout: "", stderr: "", spawnError: errorMessage(error), terminationReason: null };
@@ -776,8 +827,6 @@ async function runManagedProcess(options: {
776
827
  child_started_at: new Date().toISOString(),
777
828
  });
778
829
 
779
- const stdoutStream = openStream(options.stdoutPath);
780
- const stderrStream = openStream(options.stderrPath);
781
830
  let stdout = "";
782
831
  let stderr = "";
783
832
  let spawnError: string | null = null;
@@ -836,12 +885,12 @@ async function runManagedProcess(options: {
836
885
  child.stdout?.on("data", (chunk: Buffer) => {
837
886
  const text = chunk.toString("utf-8");
838
887
  stdout = appendBounded(stdout, text);
839
- stdoutStream?.write(text);
888
+ logCapture.append(options.stdoutPath, chunk);
840
889
  });
841
890
  child.stderr?.on("data", (chunk: Buffer) => {
842
891
  const text = chunk.toString("utf-8");
843
892
  stderr = appendBounded(stderr, text);
844
- stderrStream?.write(text);
893
+ logCapture.append(options.stderrPath, chunk);
845
894
  });
846
895
 
847
896
  heartbeat();
@@ -857,12 +906,6 @@ async function runManagedProcess(options: {
857
906
  };
858
907
  childExitFinish = finish;
859
908
  child.once("close", (code) => finish(code));
860
- child.once("exit", (code) => {
861
- // On Windows, "close" can lag behind "exit" when stdio pipes drain
862
- // after taskkill. Resolve on exit to avoid hanging the runner, but
863
- // still let "close" fire if it arrives first.
864
- finish(code);
865
- });
866
909
  child.once("error", (error) => {
867
910
  spawnError = error.message;
868
911
  finish(null);
@@ -871,8 +914,7 @@ async function runManagedProcess(options: {
871
914
 
872
915
  clearInterval(heartbeatTimer);
873
916
  if (forceTimer) clearTimeout(forceTimer);
874
- stdoutStream?.destroy();
875
- stderrStream?.destroy();
917
+ logCapture.flush(exactRedactionValues);
876
918
  writeTaskRuntime(options.taskDir, {
877
919
  phase: options.phase,
878
920
  last_heartbeat_at: new Date().toISOString(),
@@ -880,8 +922,16 @@ async function runManagedProcess(options: {
880
922
  runner_pid: process.pid,
881
923
  child_pid: undefined,
882
924
  });
925
+ const safeStdout = redactProcessOutput(stdout, exactRedactionValues);
926
+ const safeStderr = redactProcessOutput(stderr, exactRedactionValues);
883
927
 
884
- return { exitCode, stdout, stderr, spawnError, terminationReason };
928
+ return {
929
+ exitCode,
930
+ stdout: safeStdout.length > MAX_CAPTURE_CHARS ? safeStdout.slice(-MAX_CAPTURE_CHARS) : safeStdout,
931
+ stderr: safeStderr.length > MAX_CAPTURE_CHARS ? safeStderr.slice(-MAX_CAPTURE_CHARS) : safeStderr,
932
+ spawnError,
933
+ terminationReason,
934
+ };
885
935
  }
886
936
 
887
937
  async function runTrustedTestCommand(
@@ -899,9 +949,9 @@ async function runTrustedTestCommand(
899
949
  let command = parts[0];
900
950
  let args = parts.slice(1);
901
951
  if (process.platform === "win32" && /^(npm|npm\.cmd|pnpm|pnpm\.cmd)$/i.test(command)) {
902
- const shim = /^pnpm/i.test(command) ? "pnpm.cmd" : "npm.cmd";
903
- command = process.env.ComSpec || "cmd.exe";
904
- args = ["/d", "/s", "/c", shim, ...args];
952
+ const packageManager = resolvePackageManagerInvocation(command, repoPath);
953
+ command = packageManager.command;
954
+ args = [...packageManager.argsPrefix, ...args];
905
955
  }
906
956
  const result = await runManagedProcess({
907
957
  command,
@@ -1161,10 +1211,10 @@ function normalizeTimeout(value: unknown, config: ReturnType<typeof getConfig>):
1161
1211
  function failBeforeExecution(taskId: string, taskDir: string, message: string, caughtError?: unknown): TaskRunResult {
1162
1212
  // Phase 9: Preserve PatchWardenError structured info in error.log
1163
1213
  const structuredError = caughtError ? errorPayload(caughtError) : { error: message };
1164
- writeFileSync(join(taskDir, "error.log"), JSON.stringify({
1214
+ atomicWriteJsonFileSync(join(taskDir, "error.log"), {
1165
1215
  summary: message,
1166
1216
  ...structuredError,
1167
- }, null, 2), "utf-8");
1217
+ });
1168
1218
  const current = readStatus(join(taskDir, "status.json"));
1169
1219
  const now = new Date().toISOString();
1170
1220
  const verify: VerifyReport = {
@@ -1172,17 +1222,17 @@ function failBeforeExecution(taskId: string, taskDir: string, message: string, c
1172
1222
  requested_commands: Array.isArray(current.verify_commands) ? current.verify_commands : [],
1173
1223
  commands: [],
1174
1224
  };
1175
- writeFileSync(join(taskDir, "verify.json"), JSON.stringify(verify, null, 2), "utf-8");
1176
- writeFileSync(join(taskDir, "verify.log"), `Verification did not run: ${message}\n`, "utf-8");
1177
- writeFileSync(join(taskDir, "test.log"), `(not run)\nExit code: not run\n${message}\n`, "utf-8");
1178
- writeFileSync(join(taskDir, "git.diff"), "(task failed before change capture)\n", "utf-8");
1179
- writeFileSync(join(taskDir, "diff.patch"), "(task failed before change capture)\n", "utf-8");
1180
- writeFileSync(join(taskDir, "file-stats.json"), JSON.stringify({
1225
+ atomicWriteJsonFileSync(join(taskDir, "verify.json"), verify);
1226
+ atomicWriteFileSync(join(taskDir, "verify.log"), `Verification did not run: ${message}\n`);
1227
+ atomicWriteFileSync(join(taskDir, "test.log"), `(not run)\nExit code: not run\n${message}\n`);
1228
+ atomicWriteFileSync(join(taskDir, "git.diff"), "(task failed before change capture)\n");
1229
+ atomicWriteFileSync(join(taskDir, "diff.patch"), "(task failed before change capture)\n");
1230
+ atomicWriteJsonFileSync(join(taskDir, "file-stats.json"), {
1181
1231
  task_id: taskId,
1182
1232
  additions: 0,
1183
1233
  deletions: 0,
1184
1234
  files: [],
1185
- }, null, 2), "utf-8");
1235
+ });
1186
1236
  const result = {
1187
1237
  task_id: taskId,
1188
1238
  status: "failed",
@@ -1208,8 +1258,8 @@ function failBeforeExecution(taskId: string, taskDir: string, message: string, c
1208
1258
  safe_followup_prompt: "Inspect result.json and error.log, correct the task metadata or configuration, and retry without widening repository scope.",
1209
1259
  next_steps: ["Fix task metadata or configuration and retry the task."],
1210
1260
  };
1211
- writeFileSync(join(taskDir, "result.json"), JSON.stringify(result, null, 2), "utf-8");
1212
- writeFileSync(join(taskDir, "result.md"), `# PatchWarden Task Result\n\n## Status\nfailed\n\n## Summary\n${message}\n`, "utf-8");
1261
+ atomicWriteJsonFileSync(join(taskDir, "result.json"), result);
1262
+ atomicWriteFileSync(join(taskDir, "result.md"), `# PatchWarden Task Result\n\n## Status\nfailed\n\n## Summary\n${message}\n`);
1213
1263
  updateStatus(taskDir, {
1214
1264
  status: "failed",
1215
1265
  phase: "failed",
@@ -1242,13 +1292,11 @@ function setTaskPhase(
1242
1292
 
1243
1293
  function updateStatus(taskDir: string, patch: Record<string, unknown>): void {
1244
1294
  const statusFile = join(taskDir, "status.json");
1245
- const current = readStatus(statusFile);
1246
- const next = { ...current, ...patch, updated_at: new Date().toISOString() };
1247
- writeFileSync(statusFile, JSON.stringify(next, null, 2), "utf-8");
1295
+ updateTaskStatusFile(statusFile, patch);
1248
1296
  }
1249
1297
 
1250
- function readStatus(statusFile: string): Record<string, any> {
1251
- return JSON.parse(readFileSync(statusFile, "utf-8"));
1298
+ function readStatus(statusFile: string): TaskStatusRecord {
1299
+ return readTaskStatusFile(statusFile);
1252
1300
  }
1253
1301
 
1254
1302
  function gracefulKill(child: ChildProcess): void {
@@ -1262,10 +1310,13 @@ function forceKill(child: ChildProcess): void {
1262
1310
  if (!child.pid) return;
1263
1311
  try {
1264
1312
  if (process.platform === "win32") {
1265
- const result = spawnSync("taskkill.exe", ["/PID", String(child.pid), "/T", "/F"], {
1313
+ const systemRoot = process.env.SystemRoot || process.env.WINDIR || "C:\\Windows";
1314
+ const taskkill = resolveTrustedExecutable(`${systemRoot}\\System32\\taskkill.exe`, process.cwd());
1315
+ const result = spawnSync(taskkill, ["/PID", String(child.pid), "/T", "/F"], {
1266
1316
  stdio: "ignore",
1267
1317
  timeout: 5000,
1268
1318
  windowsHide: true,
1319
+ env: buildChildEnvironment({ cwd: process.cwd() }),
1269
1320
  });
1270
1321
  if (result.status !== 0) child.kill("SIGKILL");
1271
1322
  } else {
@@ -1276,10 +1327,6 @@ function forceKill(child: ChildProcess): void {
1276
1327
  }
1277
1328
  }
1278
1329
 
1279
- function openStream(path?: string): WriteStream | null {
1280
- return path ? createWriteStream(path, { flags: "a" }) : null;
1281
- }
1282
-
1283
1330
  function appendBounded(current: string, next: string): string {
1284
1331
  const combined = current + next;
1285
1332
  return combined.length <= MAX_CAPTURE_CHARS