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,9 +1,13 @@
1
- import { existsSync, readFileSync } from "node:fs";
1
+ import { existsSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
- import { getConfig, getTasksDir } from "../config.js";
4
- import { guardReadPath } from "../security/pathGuard.js";
3
+ import { getConfig, getTasksDir } from "../../config.js";
4
+ import { guardReadPath } from "../../security/pathGuard.js";
5
5
  import { getTaskStatus } from "./getTaskStatus.js";
6
- import { redactSensitiveValue } from "../security/contentRedaction.js";
6
+ import { redactSensitiveValue } from "../../security/contentRedaction.js";
7
+ import {
8
+ readTextFilePrefixSync,
9
+ readTextFileTailLinesSync,
10
+ } from "../../utils/boundedFile.js";
7
11
 
8
12
  const TERMINAL_STATUSES = new Set([
9
13
  "done",
@@ -134,7 +138,7 @@ export function getTaskSummary(taskId: string, options: GetTaskSummaryOptions =
134
138
  const taskDir = resolve(getTasksDir(config), taskId);
135
139
  const statusFile = join(taskDir, "status.json");
136
140
  guardReadPath(statusFile, config.workspaceRoot, config.tasksDir);
137
- const status = getTaskStatus(taskId) as any;
141
+ const status = getTaskStatus(taskId);
138
142
  const resultRead = tryReadJson(join(taskDir, "result.json"));
139
143
  const verifyRead = tryReadJson(join(taskDir, "verify.json"));
140
144
  const result = resultRead.data;
@@ -148,7 +152,8 @@ export function getTaskSummary(taskId: string, options: GetTaskSummaryOptions =
148
152
  ?? result.out_of_scope_changes
149
153
  ?? status.out_of_scope_changes
150
154
  );
151
- const verifyStatus = String(verify.status ?? result.verify_status ?? result.verify?.status ?? status.verify_status ?? "not_available");
155
+ const resultVerify = asRecord(result.verify);
156
+ const verifyStatus = String(verify.status ?? result.verify_status ?? resultVerify.status ?? status.verify_status ?? "not_available");
152
157
  const errors = [status.error, ...asArray(result.errors), ...asArray(result.known_issues)]
153
158
  .filter((value): value is string => typeof value === "string" && value.trim() !== "");
154
159
  const warnings = asArray(result.warnings).filter((value): value is string => typeof value === "string");
@@ -179,15 +184,15 @@ export function getTaskSummary(taskId: string, options: GetTaskSummaryOptions =
179
184
  const acceptanceFile = join(taskDir, "acceptance.json");
180
185
  if (existsSync(acceptanceFile)) {
181
186
  try {
182
- const acceptance = JSON.parse(readFileSync(acceptanceFile, "utf-8"));
187
+ const acceptance = asRecord(JSON.parse(readTextFilePrefixSync(acceptanceFile, 1024 * 1024).content));
183
188
  if (acceptance.status === "accepted") {
184
189
  acceptanceStatus = "ready_for_review";
185
- acceptanceReviewedAt = acceptance.reviewed_at || null;
186
- acceptanceReviewer = acceptance.reviewer || null;
190
+ acceptanceReviewedAt = typeof acceptance.reviewed_at === "string" ? acceptance.reviewed_at : null;
191
+ acceptanceReviewer = typeof acceptance.reviewer === "string" ? acceptance.reviewer : null;
187
192
  } else if (acceptance.status === "rejected") {
188
193
  acceptanceStatus = "failed";
189
- acceptanceReviewedAt = acceptance.reviewed_at || null;
190
- acceptanceReviewer = acceptance.reviewer || null;
194
+ acceptanceReviewedAt = typeof acceptance.reviewed_at === "string" ? acceptance.reviewed_at : null;
195
+ acceptanceReviewer = typeof acceptance.reviewer === "string" ? acceptance.reviewer : null;
191
196
  if (acceptance.notes) {
192
197
  warnings.push(`Task was rejected by ${acceptance.reviewer || "human"} at ${acceptance.reviewed_at || "unknown time"}: ${acceptance.notes}`);
193
198
  }
@@ -216,14 +221,14 @@ export function getTaskSummary(taskId: string, options: GetTaskSummaryOptions =
216
221
  const artifactHygiene = asRecord(result.artifact_hygiene ?? changedFilesRead.data.artifact_hygiene ?? {
217
222
  counts: status.artifact_hygiene_counts || {},
218
223
  });
219
- const verifyCommands = asArray(verify.commands ?? result.verify_commands ?? result.verify?.commands);
224
+ const verifyCommands = asArray(verify.commands ?? result.verify_commands ?? resultVerify.commands);
220
225
  const testLogSummary = summarizeTestLog(join(taskDir, "test.log"));
221
226
  const verificationSummary = buildVerificationSummary(verifyStatus, verifyCommands, testLogSummary);
222
227
 
223
228
  // Extract failed command detail from verify records
224
- const failedVerify = (verifyCommands as any[]).find((cmd: any) =>
225
- ["failed", "timed_out", "canceled"].includes(cmd?.status)
226
- );
229
+ const failedVerify = verifyCommands
230
+ .map(asRecord)
231
+ .find((command) => ["failed", "timed_out", "canceled"].includes(String(command.status || "")));
227
232
  const failedCommandDetail = failedVerify ? {
228
233
  command: String(failedVerify.command || ""),
229
234
  exit_code: failedVerify.exit_code ?? null,
@@ -266,7 +271,9 @@ export function getTaskSummary(taskId: string, options: GetTaskSummaryOptions =
266
271
  (status.diff_available ?? (changedFiles.length > 0)) &&
267
272
  (artifacts["diff.patch"] || artifacts["git.diff"])
268
273
  ),
269
- diff_truncated: Boolean(status.diff_truncated || result.warnings?.some?.((warning: string) => warning.includes("diff.patch was truncated"))),
274
+ diff_truncated: Boolean(status.diff_truncated || asArray(result.warnings).some(
275
+ (warning) => typeof warning === "string" && warning.includes("diff.patch was truncated"),
276
+ )),
270
277
  result_available: artifacts["result.md"],
271
278
  result_json_available: artifacts["result.json"],
272
279
  verify_available: artifacts["verify.json"],
@@ -300,7 +307,7 @@ export function getTaskSummary(taskId: string, options: GetTaskSummaryOptions =
300
307
  } as TaskSummaryOutput;
301
308
  }
302
309
 
303
- function buildCompactSummary(output: Record<string, any>, maxItems: number): CompactTaskSummaryOutput {
310
+ function buildCompactSummary(output: Record<string, unknown>, maxItems: number): CompactTaskSummaryOutput {
304
311
  const hygiene = asRecord(output.artifact_hygiene);
305
312
  const groupNames = [
306
313
  "source_changes",
@@ -317,7 +324,9 @@ function buildCompactSummary(output: Record<string, any>, maxItems: number): Com
317
324
  const manifestRead = tryReadJson(join(taskDir, "artifact_manifest.json"));
318
325
  const manifest = asRecord(manifestRead.data);
319
326
  const releaseArtifacts = asArray(manifest.artifacts);
320
- const releaseArtifactPaths = releaseArtifacts.map((a: any) => String(a.path || "")).slice(0, maxItems);
327
+ const releaseArtifactPaths = releaseArtifacts
328
+ .map((artifact) => String(asRecord(artifact).path || ""))
329
+ .slice(0, maxItems);
321
330
 
322
331
  const compact = {
323
332
  view: "compact" as const,
@@ -364,42 +373,45 @@ function normalizeMaxItems(value: number | undefined): number {
364
373
  return value;
365
374
  }
366
375
 
367
- function tryReadJson(path: string): { data: Record<string, any>; error?: string } {
376
+ function tryReadJson(path: string): { data: Record<string, unknown>; error?: string } {
368
377
  if (!existsSync(path)) return { data: {} };
369
378
  try {
370
- return { data: JSON.parse(readFileSync(path, "utf-8")) };
379
+ return { data: asRecord(JSON.parse(readTextFilePrefixSync(path, 2 * 1024 * 1024).content)) };
371
380
  } catch (error) {
372
381
  return { data: {}, error: error instanceof Error ? error.message : String(error) };
373
382
  }
374
383
  }
375
384
 
376
- function asArray(value: unknown): any[] {
385
+ function asArray(value: unknown): unknown[] {
377
386
  return Array.isArray(value) ? value : [];
378
387
  }
379
388
 
380
- function asRecord(value: unknown): Record<string, any> {
381
- return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, any> : {};
389
+ function asRecord(value: unknown): Record<string, unknown> {
390
+ return value && typeof value === "object" && !Array.isArray(value)
391
+ ? value as Record<string, unknown>
392
+ : {};
382
393
  }
383
394
 
384
395
  function summarizeTestLog(path: string): string {
385
396
  if (!existsSync(path)) return "test.log missing";
386
- const text = readFileSync(path, "utf-8");
397
+ const text = readTextFileTailLinesSync(path, 200, 512 * 1024);
387
398
  const exit = text.match(/Exit\s*code:\s*([^\r\n]+)/i)?.[1]?.trim();
388
399
  return exit ? `Exit code: ${exit}` : text.trim().slice(0, 500) || "test.log empty";
389
400
  }
390
401
 
391
402
  function readResultFallback(path: string): string {
392
403
  if (!existsSync(path)) return "";
393
- const text = readFileSync(path, "utf-8");
404
+ const text = readTextFilePrefixSync(path, 1024 * 1024).content;
394
405
  return text.match(/## Summary\s+([\s\S]*?)(?:\n## |\n---|$)/i)?.[1]?.trim().slice(0, 1000) || "";
395
406
  }
396
407
 
397
- function buildVerificationSummary(status: string, commands: any[], testLogSummary: string) {
398
- const passed = commands.filter((command) => command?.status === "passed").length;
399
- const failed = commands.filter((command) => ["failed", "timed_out", "canceled"].includes(command?.status)).length;
400
- const skipped = commands.filter((command) => command?.status === "skipped").length;
408
+ function buildVerificationSummary(status: string, commands: unknown[], testLogSummary: string) {
409
+ const commandRecords = commands.map(asRecord);
410
+ const passed = commandRecords.filter((command) => command.status === "passed").length;
411
+ const failed = commandRecords.filter((command) => ["failed", "timed_out", "canceled"].includes(String(command.status || ""))).length;
412
+ const skipped = commandRecords.filter((command) => command.status === "skipped").length;
401
413
  const evidenceText = [
402
- ...commands.flatMap((command) => [command?.stdout_tail, command?.stderr_tail]),
414
+ ...commandRecords.flatMap((command) => [command.stdout_tail, command.stderr_tail]),
403
415
  testLogSummary,
404
416
  ].filter((value): value is string => typeof value === "string").join("\n");
405
417
  const headline = extractTestHeadline(evidenceText)
@@ -430,9 +442,8 @@ function extractTestHeadline(text: string): string {
430
442
  function readLogTail(path: string, lines: number): string {
431
443
  if (!existsSync(path)) return "(file not found)";
432
444
  try {
433
- const raw = readFileSync(path, "utf-8");
434
- if (raw.length === 0) return "(empty)";
435
- return raw.split("\n").slice(-lines).join("\n");
445
+ const tail = readTextFileTailLinesSync(path, lines);
446
+ return tail || "(empty)";
436
447
  } catch {
437
448
  return "(unreadable)";
438
449
  }
@@ -0,0 +1,18 @@
1
+ export * from "./cancelTask.js";
2
+ export * from "./createTask.js";
3
+ export * from "./diagnoseTask.js";
4
+ export * from "./evidencePack.js";
5
+ export * from "./getTaskFile.js";
6
+ export * from "./getTaskProgress.js";
7
+ export * from "./getTaskStatus.js";
8
+ export * from "./getTaskStdoutTail.js";
9
+ export * from "./getTaskSummary.js";
10
+ export * from "./killTask.js";
11
+ export * from "./listTasks.js";
12
+ export * from "./reconcileTasks.js";
13
+ export * from "./retryTask.js";
14
+ export * from "./runTaskLoop.js";
15
+ export * from "./runVerification.js";
16
+ export * from "./taskLineage.js";
17
+ export * from "./taskOutputs.js";
18
+ export * from "./waitForTask.js";
@@ -1,8 +1,8 @@
1
1
  import { readdirSync, readFileSync, existsSync, statSync } from "node:fs";
2
2
  import { resolve, join } from "node:path";
3
- import { getTasksDir, getPlansDir, getConfig } from "../config.js";
4
- import { guardPath } from "../security/pathGuard.js";
5
- import { readTaskRuntime } from "../taskRuntime.js";
3
+ import { getTasksDir, getPlansDir, getConfig } from "../../config.js";
4
+ import { guardPath } from "../../security/pathGuard.js";
5
+ import { readTaskRuntime } from "../../runner/taskRuntime.js";
6
6
  import type { TaskPhase, TaskStatus, AcceptanceStatus } from "./createTask.js";
7
7
  import {
8
8
  derivePendingReason,
@@ -10,7 +10,7 @@ import {
10
10
  type PendingReason,
11
11
  type WatcherState,
12
12
  type WatcherStatusSnapshot,
13
- } from "../watcherStatus.js";
13
+ } from "../../watcherStatus.js";
14
14
 
15
15
  export interface TaskEntry {
16
16
  task_id: string;
@@ -2,17 +2,17 @@ import {
2
2
  existsSync,
3
3
  readdirSync,
4
4
  readFileSync,
5
- writeFileSync,
6
- renameSync,
7
- unlinkSync,
8
5
  mkdirSync,
9
6
  statSync,
10
7
  type Dirent,
11
8
  } from "node:fs";
12
9
  import { join, resolve, dirname } from "node:path";
13
- import { getTasksDir, getConfig, type PatchWardenConfig } from "../config.js";
10
+ import { getTasksDir, getConfig, type PatchWardenConfig } from "../../config.js";
14
11
  import { diagnoseTask, type DiagnosisType, type DiagnosisConfidence, type SafeAction } from "./diagnoseTask.js";
15
- import { syncSubgoalOnTaskDone, readTaskGoalMeta } from "../goal/subgoalSync.js";
12
+ import { syncSubgoalOnTaskDone, readTaskGoalMeta } from "../../goal/subgoalSync.js";
13
+ import { mutateTaskStatus } from "../../runner/taskStatusStore.js";
14
+ import { atomicWriteFileSync } from "../../utils/atomicFile.js";
15
+ import { appendBoundedTextFileSync } from "../../utils/boundedFile.js";
16
16
 
17
17
  // ── v0.7.0: reconcile_tasks types ──────────────────────────────────
18
18
 
@@ -206,10 +206,9 @@ export function reconcileTasks(
206
206
  report.applied_by = "reconcile_tasks";
207
207
  reconciled += 1;
208
208
  } else {
209
- // Diagnosis type does not map to a safe_fix action — leave unchanged
210
209
  report.reasons = [
211
210
  ...report.reasons,
212
- `safe_fix skipped: diagnosis "${diagnosis.diagnosis}" has no automatic safe_fix action`,
211
+ `safe_fix skipped: ${fixResult.skip_reason || "task state could not be updated safely"}`,
213
212
  ];
214
213
  skippedLowConfidence += 1;
215
214
  }
@@ -254,8 +253,13 @@ interface SafeFixResult {
254
253
  previous_status: string;
255
254
  new_status: string;
256
255
  applied_at: string;
256
+ skip_reason: string | null;
257
257
  }
258
258
 
259
+ type SafeFixMutationOutcome =
260
+ | { applied: true }
261
+ | { applied: false; reason: string };
262
+
259
263
  function applySafeFix(
260
264
  taskDir: string,
261
265
  taskId: string,
@@ -306,6 +310,7 @@ function applySafeFix(
306
310
  previous_status: previousStatus,
307
311
  new_status: previousStatus,
308
312
  applied_at: new Date().toISOString(),
313
+ skip_reason: `diagnosis "${diagnosis}" has no automatic safe_fix action`,
309
314
  };
310
315
  }
311
316
 
@@ -313,72 +318,71 @@ function applySafeFix(
313
318
  const statusFile = join(taskDir, "status.json");
314
319
  const backupFile = join(taskDir, "status.json.bak");
315
320
 
316
- // 1. Write status.json.bak with current contents (overwrite any prior backup)
321
+ // Revalidate and update under the shared task status lock. Any heartbeat or
322
+ // ownership-related status change after diagnosis makes this repair stale.
317
323
  try {
318
- const currentRaw = readFileSync(statusFile, "utf-8");
319
- writeFileSync(backupFile, currentRaw, "utf-8");
320
- } catch {
321
- // If we cannot back up, refuse to apply the fix.
322
- return {
323
- applied: false,
324
- action_taken: "left_unchanged",
325
- previous_status: previousStatus,
326
- new_status: previousStatus,
327
- applied_at: appliedAt,
328
- };
329
- }
324
+ const outcome = mutateTaskStatus<SafeFixMutationOutcome>(statusFile, (current) => {
325
+ if (JSON.stringify(current) !== JSON.stringify(currentStatus)) {
326
+ return { result: { applied: false as const, reason: "task status changed after diagnosis" } };
327
+ }
330
328
 
331
- // 2. Build the new status record with full audit trail
332
- const next: Record<string, unknown> = {
333
- ...currentStatus,
334
- status: newStatus,
335
- phase: newStatus,
336
- updated_at: appliedAt,
337
- last_heartbeat_at: appliedAt,
338
- finished_at: appliedAt,
339
- // v0.7.0 audit fields
340
- previous_status: previousStatus,
341
- diagnosis: {
342
- type: diagnosis,
343
- confidence: "high" as DiagnosisConfidence,
344
- applied_by: "reconcile_tasks",
345
- applied_at: appliedAt,
346
- reasons,
347
- evidence: {
348
- heartbeat_age_seconds: evidence.heartbeat_age_seconds,
349
- stdout_age_seconds: evidence.stdout_age_seconds,
350
- child_pid: evidence.child_pid,
351
- child_pid_alive: evidence.child_pid_alive,
352
- watcher_instance_id: evidence.watcher_instance_id,
353
- current_watcher_instance_id: evidence.current_watcher_instance_id,
354
- },
355
- },
356
- };
329
+ const lockedPreviousStatus = typeof current.status === "string" ? current.status : "unknown";
330
+ const next: Record<string, unknown> = {
331
+ ...current,
332
+ status: newStatus,
333
+ phase: newStatus,
334
+ updated_at: appliedAt,
335
+ last_heartbeat_at: appliedAt,
336
+ finished_at: appliedAt,
337
+ previous_status: lockedPreviousStatus,
338
+ diagnosis: {
339
+ type: diagnosis,
340
+ confidence: "high" as DiagnosisConfidence,
341
+ applied_by: "reconcile_tasks",
342
+ applied_at: appliedAt,
343
+ reasons,
344
+ evidence: {
345
+ heartbeat_age_seconds: evidence.heartbeat_age_seconds,
346
+ stdout_age_seconds: evidence.stdout_age_seconds,
347
+ child_pid: evidence.child_pid,
348
+ child_pid_alive: evidence.child_pid_alive,
349
+ watcher_instance_id: evidence.watcher_instance_id,
350
+ current_watcher_instance_id: evidence.current_watcher_instance_id,
351
+ },
352
+ },
353
+ };
357
354
 
358
- // For done_by_agent, set acceptance_status=pending (per roadmap 3.6)
359
- if (newStatus === "done_by_agent") {
360
- next.acceptance_status = "pending";
361
- next.legacy_status = "done";
362
- }
355
+ if (newStatus === "done_by_agent") {
356
+ next.acceptance_status = "pending";
357
+ next.legacy_status = "done";
358
+ }
363
359
 
364
- // 3. Atomic write: write to temp file then rename
365
- const tmpFile = join(taskDir, "status.json.tmp");
366
- try {
367
- writeFileSync(tmpFile, JSON.stringify(next, null, 2), "utf-8");
368
- renameSync(tmpFile, statusFile);
360
+ // The backup is created while the same lock protects the exact record
361
+ // being replaced, so it cannot capture an unrelated newer state.
362
+ atomicWriteFileSync(backupFile, readFileSync(statusFile, "utf-8"));
363
+ return {
364
+ next,
365
+ result: { applied: true as const },
366
+ };
367
+ });
368
+ if (!outcome.applied) {
369
+ return {
370
+ applied: false,
371
+ action_taken: "left_unchanged",
372
+ previous_status: previousStatus,
373
+ new_status: previousStatus,
374
+ applied_at: appliedAt,
375
+ skip_reason: outcome.reason,
376
+ };
377
+ }
369
378
  } catch {
370
- // Best-effort cleanup of temp file
371
- try {
372
- if (existsSync(tmpFile)) {
373
- unlinkSync(tmpFile);
374
- }
375
- } catch { /* ignore */ }
376
379
  return {
377
380
  applied: false,
378
381
  action_taken: "left_unchanged",
379
382
  previous_status: previousStatus,
380
383
  new_status: previousStatus,
381
384
  applied_at: appliedAt,
385
+ skip_reason: "task status could not be locked, backed up, and updated",
382
386
  };
383
387
  }
384
388
 
@@ -396,6 +400,7 @@ function applySafeFix(
396
400
  previous_status: previousStatus,
397
401
  new_status: newStatus,
398
402
  applied_at: appliedAt,
403
+ skip_reason: null,
399
404
  };
400
405
  }
401
406
 
@@ -428,11 +433,8 @@ function writeReconcileLog(
428
433
  evidence: report.evidence_summary,
429
434
  }));
430
435
  }
431
- // Append — do not overwrite history
432
- const existing = existsSync(logPath) ? readFileSync(logPath, "utf-8") : "";
433
- const nextContent = existing + (existing && !existing.endsWith("\n") ? "\n" : "") + lines.join("\n") + "\n";
434
436
  try {
435
- writeFileSync(logPath, nextContent, "utf-8");
437
+ appendBoundedTextFileSync(logPath, lines.join("\n") + "\n");
436
438
  } catch {
437
439
  // If we cannot write the log, the status change still happened —
438
440
  // the status.json itself contains the diagnosis audit fields.
@@ -0,0 +1,69 @@
1
+ import { readFileSync, existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { getTasksDir, getConfig } from "../../config.js";
4
+ import { guardReadPath } from "../../security/pathGuard.js";
5
+ import { createTask } from "./createTask.js";
6
+ import type { ChangePolicy } from "../taskTemplates.js";
7
+
8
+ export async function retryTask(taskId: string) {
9
+ const config = getConfig();
10
+ const tasksDir = getTasksDir(config);
11
+ const taskDir = join(tasksDir, taskId);
12
+ const statusFile = join(taskDir, "status.json");
13
+
14
+ guardReadPath(statusFile, config.workspaceRoot, config.tasksDir);
15
+
16
+ if (!existsSync(statusFile)) {
17
+ throw new Error(`Task not found: "${taskId}"`);
18
+ }
19
+
20
+ const parsed: unknown = JSON.parse(readFileSync(statusFile, "utf-8"));
21
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
22
+ throw new Error(`Task "${taskId}" has an invalid status record.`);
23
+ }
24
+ const data = parsed as Record<string, unknown>;
25
+
26
+ if (typeof data.plan_id !== "string" || !data.plan_id ||
27
+ typeof data.agent !== "string" || !data.agent) {
28
+ throw new Error(`Task "${taskId}" is missing plan_id or agent. Cannot retry.`);
29
+ }
30
+
31
+ const priorRetryCount = typeof data.retry_count === "number" &&
32
+ Number.isSafeInteger(data.retry_count) && data.retry_count >= 0
33
+ ? data.retry_count
34
+ : 0;
35
+ const planSource = data.plan_source === "inline" || data.plan_source === "template"
36
+ ? data.plan_source
37
+ : "saved";
38
+ const changePolicy: ChangePolicy = data.change_policy === "no_changes"
39
+ ? "no_changes"
40
+ : "repo_scoped_changes";
41
+
42
+ // Create a new task with the same parameters
43
+ const newTask = await createTask({
44
+ plan_id: data.plan_id,
45
+ agent: data.agent,
46
+ repo_path: typeof data.repo_path === "string" ? data.repo_path : undefined,
47
+ test_command: typeof data.test_command === "string" ? data.test_command : undefined,
48
+ verify_commands: Array.isArray(data.verify_commands)
49
+ ? data.verify_commands.filter((value): value is string => typeof value === "string")
50
+ : undefined,
51
+ timeout_seconds: typeof data.timeout_seconds === "number" ? data.timeout_seconds : undefined,
52
+ retry_metadata: {
53
+ retry_of: taskId,
54
+ retry_count: priorRetryCount + 1,
55
+ plan_source: planSource,
56
+ template: typeof data.template === "string" ? data.template : null,
57
+ change_policy: changePolicy,
58
+ },
59
+ });
60
+
61
+ return {
62
+ original_task_id: taskId,
63
+ new_task_id: newTask.task_id,
64
+ plan_id: newTask.plan_id,
65
+ agent: newTask.agent,
66
+ status: newTask.status,
67
+ message: `New task created from retry of ${taskId}. Original task is unchanged.`,
68
+ };
69
+ }