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,17 +1,21 @@
1
1
  import { spawn, spawnSync } from "node:child_process";
2
- import { createWriteStream, existsSync, readFileSync, writeFileSync, } from "node:fs";
2
+ import { existsSync, readFileSync, } from "node:fs";
3
3
  import { isAbsolute, join, relative, resolve } from "node:path";
4
4
  import { getTasksDir, getPlansDir, getConfig, resolveWorkspaceRoot } from "../config.js";
5
5
  import { guardPath, guardWorkspacePath } from "../security/pathGuard.js";
6
6
  import { guardTestCommand, } from "../security/commandGuard.js";
7
- import { writeTaskProgress } from "../taskProgress.js";
8
- import { writeTaskRuntime } from "../taskRuntime.js";
7
+ import { writeTaskProgress } from "./taskProgress.js";
8
+ import { writeTaskRuntime } from "./taskRuntime.js";
9
+ import { claimPendingTask, readTaskStatusFile, updateTaskStatusFile, } from "./taskStatusStore.js";
9
10
  import { validateAssessmentFreshness } from "../assessments/assessmentStore.js";
10
11
  import { buildAgentInvocation, buildExecutionPrompt } from "./agentInvocation.js";
11
12
  import { buildChangeArtifacts, captureRepoSnapshot, compareSnapshots, emptyArtifactHygiene, writeSnapshot, extractExternalDirtyFiles, findNewExternalDirtyFiles, buildArtifactManifest, groupChangedFiles, } from "./changeCapture.js";
12
13
  import { errorPayload } from "../errors.js";
13
- import { diagnoseAndroidBuild } from "../tools/androidDoctor.js";
14
+ import { ARTIFACT_SCHEMA_VERSION } from "../version.js";
15
+ import { diagnoseAndroidBuild } from "../tools/workspace/androidDoctor.js";
14
16
  import { runPostTaskCleanup } from "./postTaskCleanup.js";
17
+ import { atomicWriteFileSync, atomicWriteJsonFileSync } from "../utils/atomicFile.js";
18
+ import { allowedEnvironmentValues, buildChildEnvironment, redactProcessOutput, resolvePackageManagerInvocation, resolveTrustedExecutable, SecureProcessLogCapture, } from "./processSecurity.js";
15
19
  const HEARTBEAT_INTERVAL_MS = 2000;
16
20
  const GRACEFUL_KILL_MS = 2000;
17
21
  const MAX_CAPTURE_CHARS = 100_000;
@@ -36,7 +40,24 @@ async function prepareTask(taskId) {
36
40
  const statusFile = join(taskDir, "status.json");
37
41
  if (!existsSync(statusFile))
38
42
  throw new Error(`Task not found: "${taskId}"`);
39
- const initialStatus = readStatus(statusFile);
43
+ const startedAtMs = Date.now();
44
+ const claim = claimPendingTask(statusFile, {
45
+ status: "running",
46
+ phase: "preparing",
47
+ started_at: new Date(startedAtMs).toISOString(),
48
+ last_heartbeat_at: new Date(startedAtMs).toISOString(),
49
+ current_command: null,
50
+ error: null,
51
+ });
52
+ if (!claim.claimed) {
53
+ const currentStatus = String(claim.status.status || "failed");
54
+ return {
55
+ task_id: taskId,
56
+ status: currentStatus,
57
+ error: `Task is ${currentStatus}; only pending tasks can be claimed for execution.`,
58
+ };
59
+ }
60
+ const initialStatus = claim.status;
40
61
  const planId = String(initialStatus.plan_id || "");
41
62
  const agentName = String(initialStatus.agent || "");
42
63
  const rawRepoPath = String(initialStatus.resolved_repo_path || initialStatus.repo_path || wsRoot);
@@ -50,7 +71,6 @@ async function prepareTask(taskId) {
50
71
  catch (error) {
51
72
  return failBeforeExecution(taskId, taskDir, errorMessage(error), error);
52
73
  }
53
- const startedAtMs = Date.now();
54
74
  const deadlineMs = startedAtMs + timeoutSeconds * 1000;
55
75
  let repoPath;
56
76
  try {
@@ -82,15 +102,7 @@ async function prepareTask(taskId) {
82
102
  return failBeforeExecution(taskId, taskDir, message, error);
83
103
  }
84
104
  }
85
- updateStatus(taskDir, {
86
- status: "running",
87
- phase: "preparing",
88
- started_at: new Date(startedAtMs).toISOString(),
89
- last_heartbeat_at: new Date().toISOString(),
90
- timeout_seconds: timeoutSeconds,
91
- current_command: null,
92
- error: null,
93
- });
105
+ updateStatus(taskDir, { timeout_seconds: timeoutSeconds });
94
106
  writeTaskRuntime(taskDir, {
95
107
  task_started_at: new Date(startedAtMs).toISOString(),
96
108
  watcher_instance_id: process.env.PATCHWARDEN_WATCHER_INSTANCE_ID || undefined,
@@ -144,6 +156,8 @@ async function executeAgent(ctx) {
144
156
  deadlineMs: ctx.deadlineMs,
145
157
  stdoutPath: join(ctx.taskDir, "stdout.log"),
146
158
  stderrPath: join(ctx.taskDir, "stderr.log"),
159
+ environmentVariableNames: invocation.environmentVariableNames,
160
+ blockedEnvironmentVariableNames: invocation.blockedEnvironmentVariableNames,
147
161
  });
148
162
  const agentResult = state.agentResult;
149
163
  if (agentResult.terminationReason === "canceled" || agentResult.terminationReason === "killed") {
@@ -229,7 +243,7 @@ async function collectArtifacts(ctx, state) {
229
243
  skipped: [{ path: ".", reason: "post_task_cleanup", skip_reason: errorMessage(error) }],
230
244
  source_files_touched: 0,
231
245
  };
232
- writeFileSync(join(ctx.taskDir, "post-task-cleanup.json"), JSON.stringify(cleanupReport, null, 2), "utf-8");
246
+ atomicWriteJsonFileSync(join(ctx.taskDir, "post-task-cleanup.json"), cleanupReport);
233
247
  updateStatus(ctx.taskDir, { cleanup: cleanupReport });
234
248
  }
235
249
  }
@@ -239,24 +253,16 @@ async function collectArtifacts(ctx, state) {
239
253
  let artifactStatus = "collected";
240
254
  let artifactCollectionError = null;
241
255
  let timeoutHandle;
256
+ const artifactController = new AbortController();
242
257
  try {
243
- const collectionResult = await Promise.race([
244
- Promise.resolve().then(async () => {
245
- const afterSnapshot = await captureRepoSnapshot(ctx.repoPath);
246
- writeSnapshot(ctx.taskDir, "git-after.json", afterSnapshot);
247
- return await buildChangeArtifacts(ctx.repoPath, ctx.beforeSnapshot, afterSnapshot);
248
- }),
249
- new Promise((_, reject) => {
250
- timeoutHandle = setTimeout(() => reject(new Error("Artifact collection timed out")), ARTIFACT_COLLECTION_TIMEOUT_MS);
251
- }),
252
- ]);
253
- if (timeoutHandle)
254
- clearTimeout(timeoutHandle);
255
- changes = collectionResult;
258
+ timeoutHandle = setTimeout(() => {
259
+ artifactController.abort(new Error("Artifact collection timed out"));
260
+ }, ARTIFACT_COLLECTION_TIMEOUT_MS);
261
+ const afterSnapshot = await captureRepoSnapshot(ctx.repoPath, artifactController.signal);
262
+ writeSnapshot(ctx.taskDir, "git-after.json", afterSnapshot);
263
+ changes = await buildChangeArtifacts(ctx.repoPath, ctx.beforeSnapshot, afterSnapshot, artifactController.signal);
256
264
  }
257
265
  catch (error) {
258
- if (timeoutHandle)
259
- clearTimeout(timeoutHandle);
260
266
  state.lastCaughtError = error;
261
267
  artifactCollectionError = errorMessage(error);
262
268
  artifactStatus = artifactCollectionError.includes("timed out") ? "timeout" : "failed";
@@ -275,7 +281,7 @@ async function collectArtifacts(ctx, state) {
275
281
  unavailable_reason: `Change capture failed: ${artifactCollectionError}`,
276
282
  artifact_hygiene: emptyArtifactHygiene(),
277
283
  };
278
- writeFileSync(join(ctx.taskDir, "partial_result.md"), [
284
+ atomicWriteFileSync(join(ctx.taskDir, "partial_result.md"), [
279
285
  "# PatchWarden Partial Result",
280
286
  "",
281
287
  `Task: ${ctx.taskId}`,
@@ -295,7 +301,7 @@ async function collectArtifacts(ctx, state) {
295
301
  "## Note",
296
302
  "Artifact collection did not complete. The task result may be incomplete. Review stdout.log, stderr.log, and verify.json for details.",
297
303
  "",
298
- ].join("\n"), "utf-8");
304
+ ].join("\n"));
299
305
  if (state.finalStatus === "done_by_agent") {
300
306
  state.finalError = `Change capture failed: ${artifactCollectionError}`;
301
307
  state.finalStatus = "failed";
@@ -304,6 +310,10 @@ async function collectArtifacts(ctx, state) {
304
310
  state.finalError ||= `Change capture failed: ${artifactCollectionError}`;
305
311
  }
306
312
  }
313
+ finally {
314
+ if (timeoutHandle)
315
+ clearTimeout(timeoutHandle);
316
+ }
307
317
  const artifactCollectionFinishedAt = new Date().toISOString();
308
318
  let outOfScopeChanges = [];
309
319
  const preexistingExternalDirty = ctx.externalDirtyBaseline;
@@ -326,18 +336,18 @@ async function collectArtifacts(ctx, state) {
326
336
  ? [`Pre-existing external dirty files (not caused by this task): ${preexistingExternalDirty.length} file(s)`]
327
337
  : [];
328
338
  applyScopeViolationVerdict(ctx, state, changes, newOutOfScopeChanges, preexistingExternalDirty);
329
- writeFileSync(join(ctx.taskDir, "git.diff"), changes.diff, "utf-8");
330
- writeFileSync(join(ctx.taskDir, "diff.patch"), changes.diff, "utf-8");
331
- writeFileSync(join(ctx.taskDir, "changed-files.json"), JSON.stringify(changes, null, 2), "utf-8");
339
+ atomicWriteFileSync(join(ctx.taskDir, "git.diff"), changes.diff);
340
+ atomicWriteFileSync(join(ctx.taskDir, "diff.patch"), changes.diff);
341
+ atomicWriteJsonFileSync(join(ctx.taskDir, "changed-files.json"), { schema_version: ARTIFACT_SCHEMA_VERSION, ...changes });
332
342
  const artifactManifest = await buildArtifactManifest(changes.changed_files, ctx.repoPath, ctx.taskId);
333
- writeFileSync(join(ctx.taskDir, "artifact_manifest.json"), JSON.stringify(artifactManifest, null, 2), "utf-8");
343
+ atomicWriteJsonFileSync(join(ctx.taskDir, "artifact_manifest.json"), artifactManifest);
334
344
  const changedFileGroups = groupChangedFiles(changes.changed_files);
335
- writeFileSync(join(ctx.taskDir, "file-stats.json"), JSON.stringify({
345
+ atomicWriteJsonFileSync(join(ctx.taskDir, "file-stats.json"), {
336
346
  task_id: ctx.taskId,
337
347
  additions: changes.additions,
338
348
  deletions: changes.deletions,
339
349
  files: changes.file_stats,
340
- }, null, 2), "utf-8");
350
+ });
341
351
  return {
342
352
  changes, artifactStatus, artifactCollectionError, artifactCollectionStartedAt, artifactCollectionFinishedAt,
343
353
  outOfScopeChanges, newOutOfScopeChanges, preexistingExternalDirty, preexistingWarnings, cleanupReport,
@@ -348,15 +358,15 @@ function applyScopeViolationVerdict(ctx, state, changes, newOutOfScopeChanges, p
348
358
  if (newOutOfScopeChanges.length > 0) {
349
359
  state.finalStatus = "failed_scope_violation";
350
360
  state.finalError = `Detected ${newOutOfScopeChanges.length} new change(s) outside resolved_repo_path during task execution.`;
351
- writeFileSync(join(ctx.taskDir, "rollback-plan.json"), JSON.stringify({
361
+ atomicWriteJsonFileSync(join(ctx.taskDir, "rollback-plan.json"), {
352
362
  task_id: ctx.taskId,
353
363
  status: "review_required",
354
364
  automatic_rollback_performed: false,
355
365
  warning: "Review ownership and concurrent edits before rollback. PatchWarden did not modify or restore these files.",
356
366
  out_of_scope_changes: newOutOfScopeChanges,
357
367
  preexisting_external_dirty_files: preexistingExternalDirty,
358
- }, null, 2), "utf-8");
359
- writeFileSync(join(ctx.taskDir, "rollback_scope_violation_plan.md"), [
368
+ });
369
+ atomicWriteFileSync(join(ctx.taskDir, "rollback_scope_violation_plan.md"), [
360
370
  "# Scope Violation Rollback Plan",
361
371
  "",
362
372
  `Task: ${ctx.taskId}`,
@@ -366,7 +376,11 @@ function applyScopeViolationVerdict(ctx, state, changes, newOutOfScopeChanges, p
366
376
  "## New out-of-scope files (caused by this task)",
367
377
  ...newOutOfScopeChanges.map((file) => `- ${file.change}: ${file.path}`),
368
378
  "",
369
- ].join("\n"), "utf-8");
379
+ ].join("\n"));
380
+ }
381
+ else if (changes.diff_redacted) {
382
+ state.finalStatus = "failed_policy_violation";
383
+ state.finalError = "Credential-like content was detected in the task diff. PatchWarden redacted the evidence; remove the sensitive content before retrying.";
370
384
  }
371
385
  else if (ctx.changePolicy === "no_changes" && changes.changed_files.length > 0) {
372
386
  state.finalStatus = "failed_policy_violation";
@@ -375,7 +389,7 @@ function applyScopeViolationVerdict(ctx, state, changes, newOutOfScopeChanges, p
375
389
  }
376
390
  function finalizeTask(ctx, state, evidence) {
377
391
  const { changes, artifactStatus, artifactCollectionError, artifactCollectionStartedAt, artifactCollectionFinishedAt, outOfScopeChanges, newOutOfScopeChanges, preexistingExternalDirty, preexistingWarnings, cleanupReport, artifactManifest, changedFileGroups } = evidence;
378
- writeFileSync(join(ctx.taskDir, "test.log"), buildTestLog(state.testResult), "utf-8");
392
+ atomicWriteFileSync(join(ctx.taskDir, "test.log"), buildTestLog(state.testResult));
379
393
  const verifyJson = buildVerifyJson(ctx.verifyCommands, state.verifyResults, ctx.repoPath);
380
394
  if (newOutOfScopeChanges.length > 0) {
381
395
  verifyJson.status = "failed";
@@ -383,10 +397,12 @@ function finalizeTask(ctx, state, evidence) {
383
397
  }
384
398
  else if (state.finalStatus === "failed_policy_violation") {
385
399
  verifyJson.status = "failed";
386
- verifyJson.failure_reason = "change_policy_violation";
400
+ verifyJson.failure_reason = changes.diff_redacted
401
+ ? "sensitive_content_violation"
402
+ : "change_policy_violation";
387
403
  }
388
- writeFileSync(join(ctx.taskDir, "verify.json"), JSON.stringify(verifyJson, null, 2), "utf-8");
389
- writeFileSync(join(ctx.taskDir, "verify.log"), buildVerifyLog(verifyJson.commands), "utf-8");
404
+ atomicWriteJsonFileSync(join(ctx.taskDir, "verify.json"), verifyJson);
405
+ atomicWriteFileSync(join(ctx.taskDir, "verify.log"), buildVerifyLog(verifyJson.commands));
390
406
  if (!["canceled", "done_by_agent", "failed_verification", "failed_scope_violation", "failed_policy_violation"].includes(state.finalStatus))
391
407
  state.finalStatus = "failed";
392
408
  const finalPhase = state.finalStatus === "done_by_agent" ? "done_by_agent" : state.finalStatus;
@@ -422,17 +438,17 @@ function finalizeTask(ctx, state, evidence) {
422
438
  androidWarning,
423
439
  preexistingWarnings,
424
440
  });
425
- writeFileSync(join(ctx.taskDir, "result.md"), resultMd, "utf-8");
441
+ atomicWriteFileSync(join(ctx.taskDir, "result.md"), resultMd);
426
442
  const resultJson = buildResultJson({
427
443
  ctx, state, evidence, verifyJson, followup, androidDiagnostic, androidWarning,
428
444
  });
429
- writeFileSync(join(ctx.taskDir, "result.json"), JSON.stringify(resultJson, null, 2), "utf-8");
445
+ atomicWriteJsonFileSync(join(ctx.taskDir, "result.json"), resultJson);
430
446
  if (state.finalError) {
431
447
  const structuredError = errorPayload(state.lastCaughtError);
432
- writeFileSync(join(ctx.taskDir, "error.log"), JSON.stringify({
448
+ atomicWriteJsonFileSync(join(ctx.taskDir, "error.log"), {
433
449
  summary: state.finalError,
434
450
  ...structuredError,
435
- }, null, 2), "utf-8");
451
+ });
436
452
  }
437
453
  const finishedAt = new Date().toISOString();
438
454
  updateStatus(ctx.taskDir, {
@@ -456,6 +472,8 @@ function finalizeTask(ctx, state, evidence) {
456
472
  verify_commands: ctx.verifyCommands,
457
473
  diff_available: changes.diff_available,
458
474
  diff_truncated: changes.diff_truncated,
475
+ diff_redacted: changes.diff_redacted === true,
476
+ diff_redaction_categories: changes.diff_redaction_categories ?? [],
459
477
  workspace_dirty_before: changes.workspace_dirty_before,
460
478
  workspace_dirty_after: changes.workspace_dirty_after,
461
479
  workspace_dirty: changes.workspace_dirty_after,
@@ -478,6 +496,7 @@ function buildResultJson(input) {
478
496
  const { ctx, state, evidence, verifyJson, followup, androidDiagnostic, androidWarning } = input;
479
497
  const { changes, artifactStatus, artifactCollectionError, artifactCollectionStartedAt, artifactCollectionFinishedAt, outOfScopeChanges, newOutOfScopeChanges, preexistingExternalDirty, cleanupReport, artifactManifest, changedFileGroups } = evidence;
480
498
  return {
499
+ schema_version: ARTIFACT_SCHEMA_VERSION,
481
500
  task_id: ctx.taskId,
482
501
  status: state.finalStatus,
483
502
  agent: ctx.agentName,
@@ -527,6 +546,9 @@ function buildResultJson(input) {
527
546
  warnings: [
528
547
  ...ctx.beforeSnapshot.warnings,
529
548
  ...(changes.diff_truncated ? ["diff.patch was truncated; changed-files.json retains file evidence."] : []),
549
+ ...(changes.diff_redacted ? [
550
+ `Credential-like diff content was redacted (${(changes.diff_redaction_categories ?? []).join(", ") || "sensitive_content"}).`,
551
+ ] : []),
530
552
  ...evidence.preexistingWarnings,
531
553
  ...(androidWarning ? [androidWarning] : []),
532
554
  ],
@@ -594,13 +616,22 @@ async function runManagedProcess(options) {
594
616
  if (Date.now() >= options.deadlineMs) {
595
617
  return { exitCode: null, stdout: "", stderr: "", spawnError: null, terminationReason: "timeout" };
596
618
  }
619
+ const exactRedactionValues = allowedEnvironmentValues(options.environmentVariableNames);
620
+ const logCapture = new SecureProcessLogCapture([options.stdoutPath, options.stderrPath], options.maxLogBytes);
597
621
  let child;
598
622
  try {
599
- child = spawn(options.command, options.args, {
623
+ const env = buildChildEnvironment({
624
+ cwd: options.cwd,
625
+ allowedNames: options.environmentVariableNames,
626
+ blockedNames: options.blockedEnvironmentVariableNames,
627
+ });
628
+ const command = resolveTrustedExecutable(options.command, options.cwd, { pathValue: env.PATH });
629
+ child = spawn(command, options.args, {
600
630
  cwd: options.cwd,
601
631
  stdio: ["ignore", "pipe", "pipe"],
602
632
  detached: process.platform !== "win32",
603
633
  windowsHide: true,
634
+ env,
604
635
  });
605
636
  }
606
637
  catch (error) {
@@ -612,8 +643,6 @@ async function runManagedProcess(options) {
612
643
  child_pid: child.pid,
613
644
  child_started_at: new Date().toISOString(),
614
645
  });
615
- const stdoutStream = openStream(options.stdoutPath);
616
- const stderrStream = openStream(options.stderrPath);
617
646
  let stdout = "";
618
647
  let stderr = "";
619
648
  let spawnError = null;
@@ -680,12 +709,12 @@ async function runManagedProcess(options) {
680
709
  child.stdout?.on("data", (chunk) => {
681
710
  const text = chunk.toString("utf-8");
682
711
  stdout = appendBounded(stdout, text);
683
- stdoutStream?.write(text);
712
+ logCapture.append(options.stdoutPath, chunk);
684
713
  });
685
714
  child.stderr?.on("data", (chunk) => {
686
715
  const text = chunk.toString("utf-8");
687
716
  stderr = appendBounded(stderr, text);
688
- stderrStream?.write(text);
717
+ logCapture.append(options.stderrPath, chunk);
689
718
  });
690
719
  heartbeat();
691
720
  const heartbeatTimer = setInterval(heartbeat, HEARTBEAT_INTERVAL_MS);
@@ -702,12 +731,6 @@ async function runManagedProcess(options) {
702
731
  };
703
732
  childExitFinish = finish;
704
733
  child.once("close", (code) => finish(code));
705
- child.once("exit", (code) => {
706
- // On Windows, "close" can lag behind "exit" when stdio pipes drain
707
- // after taskkill. Resolve on exit to avoid hanging the runner, but
708
- // still let "close" fire if it arrives first.
709
- finish(code);
710
- });
711
734
  child.once("error", (error) => {
712
735
  spawnError = error.message;
713
736
  finish(null);
@@ -716,8 +739,7 @@ async function runManagedProcess(options) {
716
739
  clearInterval(heartbeatTimer);
717
740
  if (forceTimer)
718
741
  clearTimeout(forceTimer);
719
- stdoutStream?.destroy();
720
- stderrStream?.destroy();
742
+ logCapture.flush(exactRedactionValues);
721
743
  writeTaskRuntime(options.taskDir, {
722
744
  phase: options.phase,
723
745
  last_heartbeat_at: new Date().toISOString(),
@@ -725,7 +747,15 @@ async function runManagedProcess(options) {
725
747
  runner_pid: process.pid,
726
748
  child_pid: undefined,
727
749
  });
728
- return { exitCode, stdout, stderr, spawnError, terminationReason };
750
+ const safeStdout = redactProcessOutput(stdout, exactRedactionValues);
751
+ const safeStderr = redactProcessOutput(stderr, exactRedactionValues);
752
+ return {
753
+ exitCode,
754
+ stdout: safeStdout.length > MAX_CAPTURE_CHARS ? safeStdout.slice(-MAX_CAPTURE_CHARS) : safeStdout,
755
+ stderr: safeStderr.length > MAX_CAPTURE_CHARS ? safeStderr.slice(-MAX_CAPTURE_CHARS) : safeStderr,
756
+ spawnError,
757
+ terminationReason,
758
+ };
729
759
  }
730
760
  async function runTrustedTestCommand(testCommand, repoPath, taskDir, statusFile, deadlineMs) {
731
761
  const config = getConfig();
@@ -736,9 +766,9 @@ async function runTrustedTestCommand(testCommand, repoPath, taskDir, statusFile,
736
766
  let command = parts[0];
737
767
  let args = parts.slice(1);
738
768
  if (process.platform === "win32" && /^(npm|npm\.cmd|pnpm|pnpm\.cmd)$/i.test(command)) {
739
- const shim = /^pnpm/i.test(command) ? "pnpm.cmd" : "npm.cmd";
740
- command = process.env.ComSpec || "cmd.exe";
741
- args = ["/d", "/s", "/c", shim, ...args];
769
+ const packageManager = resolvePackageManagerInvocation(command, repoPath);
770
+ command = packageManager.command;
771
+ args = [...packageManager.argsPrefix, ...args];
742
772
  }
743
773
  const result = await runManagedProcess({
744
774
  command,
@@ -963,10 +993,10 @@ function normalizeTimeout(value, config) {
963
993
  function failBeforeExecution(taskId, taskDir, message, caughtError) {
964
994
  // Phase 9: Preserve PatchWardenError structured info in error.log
965
995
  const structuredError = caughtError ? errorPayload(caughtError) : { error: message };
966
- writeFileSync(join(taskDir, "error.log"), JSON.stringify({
996
+ atomicWriteJsonFileSync(join(taskDir, "error.log"), {
967
997
  summary: message,
968
998
  ...structuredError,
969
- }, null, 2), "utf-8");
999
+ });
970
1000
  const current = readStatus(join(taskDir, "status.json"));
971
1001
  const now = new Date().toISOString();
972
1002
  const verify = {
@@ -974,17 +1004,17 @@ function failBeforeExecution(taskId, taskDir, message, caughtError) {
974
1004
  requested_commands: Array.isArray(current.verify_commands) ? current.verify_commands : [],
975
1005
  commands: [],
976
1006
  };
977
- writeFileSync(join(taskDir, "verify.json"), JSON.stringify(verify, null, 2), "utf-8");
978
- writeFileSync(join(taskDir, "verify.log"), `Verification did not run: ${message}\n`, "utf-8");
979
- writeFileSync(join(taskDir, "test.log"), `(not run)\nExit code: not run\n${message}\n`, "utf-8");
980
- writeFileSync(join(taskDir, "git.diff"), "(task failed before change capture)\n", "utf-8");
981
- writeFileSync(join(taskDir, "diff.patch"), "(task failed before change capture)\n", "utf-8");
982
- writeFileSync(join(taskDir, "file-stats.json"), JSON.stringify({
1007
+ atomicWriteJsonFileSync(join(taskDir, "verify.json"), verify);
1008
+ atomicWriteFileSync(join(taskDir, "verify.log"), `Verification did not run: ${message}\n`);
1009
+ atomicWriteFileSync(join(taskDir, "test.log"), `(not run)\nExit code: not run\n${message}\n`);
1010
+ atomicWriteFileSync(join(taskDir, "git.diff"), "(task failed before change capture)\n");
1011
+ atomicWriteFileSync(join(taskDir, "diff.patch"), "(task failed before change capture)\n");
1012
+ atomicWriteJsonFileSync(join(taskDir, "file-stats.json"), {
983
1013
  task_id: taskId,
984
1014
  additions: 0,
985
1015
  deletions: 0,
986
1016
  files: [],
987
- }, null, 2), "utf-8");
1017
+ });
988
1018
  const result = {
989
1019
  task_id: taskId,
990
1020
  status: "failed",
@@ -1010,8 +1040,8 @@ function failBeforeExecution(taskId, taskDir, message, caughtError) {
1010
1040
  safe_followup_prompt: "Inspect result.json and error.log, correct the task metadata or configuration, and retry without widening repository scope.",
1011
1041
  next_steps: ["Fix task metadata or configuration and retry the task."],
1012
1042
  };
1013
- writeFileSync(join(taskDir, "result.json"), JSON.stringify(result, null, 2), "utf-8");
1014
- writeFileSync(join(taskDir, "result.md"), `# PatchWarden Task Result\n\n## Status\nfailed\n\n## Summary\n${message}\n`, "utf-8");
1043
+ atomicWriteJsonFileSync(join(taskDir, "result.json"), result);
1044
+ atomicWriteFileSync(join(taskDir, "result.md"), `# PatchWarden Task Result\n\n## Status\nfailed\n\n## Summary\n${message}\n`);
1015
1045
  updateStatus(taskDir, {
1016
1046
  status: "failed",
1017
1047
  phase: "failed",
@@ -1037,12 +1067,10 @@ function setTaskPhase(taskDir, phase, currentCommand, note) {
1037
1067
  }
1038
1068
  function updateStatus(taskDir, patch) {
1039
1069
  const statusFile = join(taskDir, "status.json");
1040
- const current = readStatus(statusFile);
1041
- const next = { ...current, ...patch, updated_at: new Date().toISOString() };
1042
- writeFileSync(statusFile, JSON.stringify(next, null, 2), "utf-8");
1070
+ updateTaskStatusFile(statusFile, patch);
1043
1071
  }
1044
1072
  function readStatus(statusFile) {
1045
- return JSON.parse(readFileSync(statusFile, "utf-8"));
1073
+ return readTaskStatusFile(statusFile);
1046
1074
  }
1047
1075
  function gracefulKill(child) {
1048
1076
  try {
@@ -1058,10 +1086,13 @@ function forceKill(child) {
1058
1086
  return;
1059
1087
  try {
1060
1088
  if (process.platform === "win32") {
1061
- const result = spawnSync("taskkill.exe", ["/PID", String(child.pid), "/T", "/F"], {
1089
+ const systemRoot = process.env.SystemRoot || process.env.WINDIR || "C:\\Windows";
1090
+ const taskkill = resolveTrustedExecutable(`${systemRoot}\\System32\\taskkill.exe`, process.cwd());
1091
+ const result = spawnSync(taskkill, ["/PID", String(child.pid), "/T", "/F"], {
1062
1092
  stdio: "ignore",
1063
1093
  timeout: 5000,
1064
1094
  windowsHide: true,
1095
+ env: buildChildEnvironment({ cwd: process.cwd() }),
1065
1096
  });
1066
1097
  if (result.status !== 0)
1067
1098
  child.kill("SIGKILL");
@@ -1077,9 +1108,6 @@ function forceKill(child) {
1077
1108
  catch { } // cleanup failure is safe to ignore
1078
1109
  }
1079
1110
  }
1080
- function openStream(path) {
1081
- return path ? createWriteStream(path, { flags: "a" }) : null;
1082
- }
1083
1111
  function appendBounded(current, next) {
1084
1112
  const combined = current + next;
1085
1113
  return combined.length <= MAX_CAPTURE_CHARS
@@ -7,6 +7,9 @@ export interface SimpleProcessOptions {
7
7
  maxStderrBytes?: number;
8
8
  stdoutPath?: string;
9
9
  stderrPath?: string;
10
+ environmentVariableNames?: string[];
11
+ blockedEnvironmentVariableNames?: string[];
12
+ maxLogBytes?: number;
10
13
  }
11
14
  export interface SimpleProcessResult {
12
15
  exitCode: number | null;