patchwarden 1.5.0 → 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 (597) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +194 -26
  3. package/README.md +160 -22
  4. package/dist/assessments/agentAssessor.d.ts +1 -1
  5. package/dist/assessments/agentAssessor.js +24 -15
  6. package/dist/assessments/assessmentStore.d.ts +1 -1
  7. package/dist/assessments/assessmentStore.js +44 -36
  8. package/dist/assessments/confirmCli.js +5 -4
  9. package/dist/config.d.ts +22 -0
  10. package/dist/config.js +98 -11
  11. package/dist/control/fileManager.d.ts +2 -0
  12. package/dist/control/fileManager.js +39 -0
  13. package/dist/control/middleware/auth.d.ts +16 -0
  14. package/dist/control/middleware/auth.js +17 -0
  15. package/dist/control/middleware/static.d.ts +3 -0
  16. package/dist/control/middleware/static.js +78 -0
  17. package/dist/control/routeTable.d.ts +24 -0
  18. package/dist/control/routeTable.js +263 -0
  19. package/dist/control/routes/audit.d.ts +15 -0
  20. package/dist/control/routes/audit.js +287 -0
  21. package/dist/control/routes/evidence.d.ts +4 -0
  22. package/dist/control/routes/evidence.js +96 -0
  23. package/dist/control/routes/lineage.d.ts +3 -0
  24. package/dist/control/routes/lineage.js +71 -0
  25. package/dist/control/routes/policy.d.ts +3 -0
  26. package/dist/control/routes/policy.js +81 -0
  27. package/dist/control/routes/process.d.ts +12 -0
  28. package/dist/control/routes/process.js +487 -0
  29. package/dist/control/routes/sessions.d.ts +30 -0
  30. package/dist/control/routes/sessions.js +287 -0
  31. package/dist/control/routes/status.d.ts +51 -0
  32. package/dist/control/routes/status.js +307 -0
  33. package/dist/control/routes/taskActions.d.ts +21 -0
  34. package/dist/control/routes/taskActions.js +197 -0
  35. package/dist/control/routes/tasks.d.ts +24 -0
  36. package/dist/control/routes/tasks.js +309 -0
  37. package/dist/control/routes/workspace.d.ts +15 -0
  38. package/dist/control/routes/workspace.js +220 -0
  39. package/dist/control/runtime.d.ts +92 -0
  40. package/dist/control/runtime.js +425 -0
  41. package/dist/control/server.d.ts +13 -0
  42. package/dist/control/server.js +150 -0
  43. package/dist/control/shared.d.ts +59 -0
  44. package/dist/control/shared.js +341 -0
  45. package/dist/controlCenter.d.ts +6 -0
  46. package/dist/controlCenter.js +7 -2197
  47. package/dist/direct/directAudit.js +234 -225
  48. package/dist/direct/directGuards.d.ts +2 -1
  49. package/dist/direct/directGuards.js +72 -37
  50. package/dist/direct/directPatch.d.ts +6 -1
  51. package/dist/direct/directPatch.js +55 -8
  52. package/dist/direct/directSessionStore.d.ts +16 -6
  53. package/dist/direct/directSessionStore.js +127 -35
  54. package/dist/direct/directVerification.js +8 -16
  55. package/dist/doctor.d.ts +18 -1
  56. package/dist/doctor.js +614 -373
  57. package/dist/goal/acceptanceEngine.d.ts +1 -1
  58. package/dist/goal/goalProgress.js +75 -69
  59. package/dist/goal/goalReport.d.ts +54 -0
  60. package/dist/goal/goalReport.js +197 -0
  61. package/dist/goal/goalStatus.d.ts +8 -0
  62. package/dist/goal/goalStatus.js +10 -3
  63. package/dist/goal/goalStore.d.ts +9 -2
  64. package/dist/goal/goalStore.js +152 -45
  65. package/dist/goal/handoffExport.js +6 -6
  66. package/dist/goal/specKitImport.d.ts +63 -0
  67. package/dist/goal/specKitImport.js +221 -0
  68. package/dist/goal/subgoalSync.js +13 -10
  69. package/dist/goal/worktreeManager.d.ts +2 -0
  70. package/dist/goal/worktreeManager.js +209 -136
  71. package/dist/httpServer.js +126 -44
  72. package/dist/index.js +7 -4
  73. package/dist/logging.d.ts +7 -1
  74. package/dist/logging.js +13 -3
  75. package/dist/policy/projectPolicy.js +5 -2
  76. package/dist/release/releaseGate.d.ts +1 -1
  77. package/dist/release/releaseGate.js +78 -25
  78. package/dist/runner/agentInvocation.d.ts +14 -0
  79. package/dist/runner/agentInvocation.js +96 -4
  80. package/dist/runner/changeCapture.d.ts +14 -6
  81. package/dist/runner/changeCapture.js +287 -81
  82. package/dist/runner/cli.js +7 -6
  83. package/dist/runner/postTaskCleanup.js +60 -7
  84. package/dist/runner/processSecurity.d.ts +56 -0
  85. package/dist/runner/processSecurity.js +373 -0
  86. package/dist/runner/runTask.d.ts +1 -1
  87. package/dist/runner/runTask.js +339 -287
  88. package/dist/runner/simpleProcess.d.ts +3 -0
  89. package/dist/runner/simpleProcess.js +52 -38
  90. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  91. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  92. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
  93. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  94. package/dist/runner/taskStatusStore.d.ts +11 -0
  95. package/dist/runner/taskStatusStore.js +24 -0
  96. package/dist/runner/watch.d.ts +14 -1
  97. package/dist/runner/watch.js +265 -54
  98. package/dist/security/contentRedaction.d.ts +6 -0
  99. package/dist/security/contentRedaction.js +24 -2
  100. package/dist/security/discoveryTokenStore.d.ts +3 -2
  101. package/dist/security/discoveryTokenStore.js +17 -2
  102. package/dist/security/loopbackHost.d.ts +2 -0
  103. package/dist/security/loopbackHost.js +16 -0
  104. package/dist/security/pathGuard.js +32 -17
  105. package/dist/security/planGuard.js +2 -1
  106. package/dist/security/runtimeGuard.js +9 -6
  107. package/dist/security/secretComparison.d.ts +3 -0
  108. package/dist/security/secretComparison.js +11 -0
  109. package/dist/security/sensitiveGuard.d.ts +5 -0
  110. package/dist/security/sensitiveGuard.js +27 -8
  111. package/dist/security/toolInvocationGuard.d.ts +2 -2
  112. package/dist/security/toolInvocationGuard.js +1 -1
  113. package/dist/security/workspaceRootGuard.d.ts +8 -0
  114. package/dist/security/workspaceRootGuard.js +47 -0
  115. package/dist/smoke-test.js +280 -273
  116. package/dist/tools/catalog/index.d.ts +4 -0
  117. package/dist/tools/catalog/index.js +4 -0
  118. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  119. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  120. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +28 -15
  121. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  122. package/dist/tools/definitions/toolDefs.js +1177 -0
  123. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  124. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  125. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +8 -63
  126. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +111 -37
  127. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
  128. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +10 -10
  129. package/dist/tools/diagnostics/index.d.ts +6 -0
  130. package/dist/tools/diagnostics/index.js +6 -0
  131. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  132. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  133. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +14 -10
  134. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +25 -15
  135. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  136. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  137. package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +1 -1
  138. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +8 -8
  139. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +2 -2
  140. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  141. package/dist/tools/direct/index.d.ts +3 -0
  142. package/dist/tools/direct/index.js +3 -0
  143. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  144. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  145. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  146. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  147. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  148. package/dist/tools/discovery/index.d.ts +3 -0
  149. package/dist/tools/discovery/index.js +3 -0
  150. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  151. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  152. package/dist/tools/dispatch/coreDispatch.d.ts +9 -0
  153. package/dist/tools/dispatch/coreDispatch.js +283 -0
  154. package/dist/tools/dispatch/diagnosticDispatch.d.ts +12 -0
  155. package/dist/tools/dispatch/diagnosticDispatch.js +80 -0
  156. package/dist/tools/dispatch/directDispatch.d.ts +8 -0
  157. package/dist/tools/dispatch/directDispatch.js +116 -0
  158. package/dist/tools/dispatch/goalDispatch.d.ts +8 -0
  159. package/dist/tools/dispatch/goalDispatch.js +92 -0
  160. package/dist/tools/dispatch/releaseDispatch.d.ts +8 -0
  161. package/dist/tools/dispatch/releaseDispatch.js +46 -0
  162. package/dist/tools/dispatch/types.d.ts +23 -0
  163. package/dist/tools/dispatch/types.js +15 -0
  164. package/dist/tools/dispatch/validation.d.ts +7 -0
  165. package/dist/tools/dispatch/validation.js +71 -0
  166. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  167. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +2 -2
  168. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  169. package/dist/tools/goals/index.d.ts +3 -0
  170. package/dist/tools/goals/index.js +3 -0
  171. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  172. package/dist/tools/registry.d.ts +10 -14
  173. package/dist/tools/registry.js +47 -1651
  174. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  175. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  176. package/dist/tools/release/index.d.ts +2 -0
  177. package/dist/tools/release/index.js +2 -0
  178. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  179. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  180. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  181. package/dist/tools/tasks/cancelTask.js +85 -0
  182. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
  183. package/dist/tools/{createTask.js → tasks/createTask.js} +46 -31
  184. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  185. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
  186. package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +6 -0
  187. package/dist/tools/tasks/evidencePack.js +387 -0
  188. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  189. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  190. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
  191. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  192. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
  193. package/dist/tools/tasks/index.d.ts +18 -0
  194. package/dist/tools/tasks/index.js +18 -0
  195. package/dist/tools/tasks/killTask.d.ts +1 -0
  196. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  197. package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
  198. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
  199. package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
  200. package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +2 -2
  201. package/dist/tools/tasks/retryTask.js +60 -0
  202. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  203. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +59 -31
  204. package/dist/tools/tasks/runVerification.js +38 -0
  205. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  206. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  207. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  208. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  209. package/dist/tools/workspace/applyPatch.js +53 -0
  210. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  211. package/dist/tools/workspace/index.d.ts +10 -0
  212. package/dist/tools/workspace/index.js +10 -0
  213. package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
  214. package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
  215. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  216. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  217. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  218. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  219. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  220. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  221. package/dist/tools/workspace/syncFile.js +134 -0
  222. package/dist/utils/atomicFile.d.ts +7 -0
  223. package/dist/utils/atomicFile.js +48 -0
  224. package/dist/utils/boundedFile.d.ts +8 -0
  225. package/dist/utils/boundedFile.js +93 -0
  226. package/dist/utils/lockedJsonFile.d.ts +15 -0
  227. package/dist/utils/lockedJsonFile.js +224 -0
  228. package/dist/utils/platform.d.ts +11 -0
  229. package/dist/utils/platform.js +26 -0
  230. package/dist/utils/stableJson.d.ts +2 -0
  231. package/dist/utils/stableJson.js +12 -0
  232. package/dist/version.d.ts +3 -2
  233. package/dist/version.js +3 -2
  234. package/dist/watcherStatus.d.ts +2 -2
  235. package/dist/watcherStatus.js +18 -0
  236. package/docs/CODE_WIKI.md +1648 -0
  237. package/docs/agentseal-integration.md +150 -0
  238. package/docs/architecture.md +23 -0
  239. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  240. package/docs/assets/patchwarden-oss-demo.gif +0 -0
  241. package/docs/control-center/README.md +4 -4
  242. package/docs/dashboard-overview.md +86 -0
  243. package/docs/demo.md +8 -0
  244. package/docs/desktop-app.md +175 -0
  245. package/docs/direct-session-workflow.md +98 -0
  246. package/docs/evidence-pack-schema.md +215 -0
  247. package/docs/lineage-evidence-pack-workflow.md +127 -0
  248. package/docs/mcp-inspector-testing.md +200 -0
  249. package/docs/open-source-application.md +168 -0
  250. package/docs/opencode-worker.md +151 -0
  251. package/docs/openhands-worker.md +181 -0
  252. package/docs/release-checklist.md +1 -1
  253. package/docs/release-evidence.md +82 -0
  254. package/docs/spec-kit-integration.md +131 -0
  255. package/docs/task-safe-review-workflow.md +98 -0
  256. package/docs/threat-model.md +97 -0
  257. package/docs/user-feedback.md +40 -0
  258. package/docs/why-patchwarden.md +110 -0
  259. package/examples/config.example.json +4 -2
  260. package/package.json +28 -11
  261. package/scripts/README.md +15 -6
  262. package/scripts/build.js +11 -0
  263. package/scripts/checks/brand-check.js +3 -0
  264. package/scripts/checks/build-output-check.js +37 -0
  265. package/scripts/checks/control-center-smoke.js +447 -6
  266. package/scripts/checks/control-smoke.js +19 -8
  267. package/scripts/checks/http-mcp-smoke.js +82 -4
  268. package/scripts/checks/lifecycle-smoke.js +39 -33
  269. package/scripts/checks/mcp-manifest-check.js +1 -1
  270. package/scripts/checks/mcp-smoke.js +30 -4
  271. package/scripts/checks/package-manifest-check.js +20 -7
  272. package/scripts/checks/unit-tests.js +3 -5
  273. package/scripts/checks/watcher-supervisor-smoke.js +9 -0
  274. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  275. package/scripts/control/manage-patchwarden.ps1 +69 -4
  276. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  277. package/scripts/control/run-background-supervisor.ps1 +41 -0
  278. package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
  279. package/scripts/generate-demo-gif.py +320 -0
  280. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  281. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  282. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  283. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  284. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  285. package/scripts/lib/clean-generated-output.js +11 -0
  286. package/scripts/lib/file-discovery.js +12 -0
  287. package/scripts/release/desktop-preflight.js +211 -0
  288. package/scripts/release/pack-clean.js +59 -14
  289. package/src/assessments/agentAssessor.ts +29 -15
  290. package/src/assessments/assessmentStore.ts +55 -43
  291. package/src/assessments/confirmCli.ts +5 -4
  292. package/src/config.ts +105 -11
  293. package/src/control/fileManager.ts +40 -0
  294. package/src/control/middleware/auth.ts +35 -0
  295. package/src/control/middleware/static.ts +84 -0
  296. package/src/control/routeTable.ts +301 -0
  297. package/src/control/routes/audit.ts +329 -0
  298. package/src/control/routes/evidence.ts +107 -0
  299. package/src/control/routes/lineage.ts +92 -0
  300. package/src/control/routes/policy.ts +81 -0
  301. package/src/control/routes/process.ts +505 -0
  302. package/src/control/routes/sessions.ts +314 -0
  303. package/src/control/routes/status.ts +405 -0
  304. package/src/control/routes/taskActions.ts +214 -0
  305. package/src/control/routes/tasks.ts +331 -0
  306. package/src/control/routes/workspace.ts +234 -0
  307. package/src/control/runtime.ts +508 -0
  308. package/src/control/server.ts +165 -0
  309. package/src/control/shared.ts +360 -0
  310. package/src/controlCenter.ts +7 -2347
  311. package/src/direct/directAudit.ts +17 -9
  312. package/src/direct/directGuards.ts +100 -51
  313. package/src/direct/directPatch.ts +95 -8
  314. package/src/direct/directSessionStore.ts +211 -55
  315. package/src/direct/directVerification.ts +9 -23
  316. package/src/doctor.ts +766 -492
  317. package/src/goal/acceptanceEngine.ts +1 -1
  318. package/src/goal/goalProgress.ts +17 -13
  319. package/src/goal/goalReport.ts +263 -0
  320. package/src/goal/goalStatus.ts +25 -3
  321. package/src/goal/goalStore.ts +241 -54
  322. package/src/goal/handoffExport.ts +6 -6
  323. package/src/goal/specKitImport.ts +355 -0
  324. package/src/goal/subgoalSync.ts +15 -10
  325. package/src/goal/worktreeManager.ts +139 -22
  326. package/src/httpServer.ts +130 -44
  327. package/src/index.ts +7 -4
  328. package/src/logging.ts +20 -4
  329. package/src/policy/projectPolicy.ts +5 -2
  330. package/src/release/releaseGate.ts +74 -27
  331. package/src/runner/agentInvocation.ts +126 -4
  332. package/src/runner/changeCapture.ts +342 -80
  333. package/src/runner/cli.ts +7 -6
  334. package/src/runner/postTaskCleanup.ts +55 -7
  335. package/src/runner/processSecurity.ts +433 -0
  336. package/src/runner/runTask.ts +441 -292
  337. package/src/runner/simpleProcess.ts +68 -33
  338. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  339. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
  340. package/src/runner/taskStatusStore.ts +54 -0
  341. package/src/runner/watch.ts +290 -57
  342. package/src/security/contentRedaction.ts +31 -2
  343. package/src/security/discoveryTokenStore.ts +18 -4
  344. package/src/security/loopbackHost.ts +19 -0
  345. package/src/security/pathGuard.ts +35 -16
  346. package/src/security/planGuard.ts +2 -1
  347. package/src/security/runtimeGuard.ts +9 -6
  348. package/src/security/secretComparison.ts +13 -0
  349. package/src/security/sensitiveGuard.ts +27 -8
  350. package/src/security/toolInvocationGuard.ts +2 -2
  351. package/src/security/workspaceRootGuard.ts +53 -0
  352. package/src/smoke-test.ts +276 -273
  353. package/src/tools/catalog/index.ts +4 -0
  354. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  355. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +29 -16
  356. package/src/tools/definitions/toolDefs.ts +1264 -0
  357. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  358. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +196 -89
  359. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +11 -11
  360. package/src/tools/diagnostics/index.ts +6 -0
  361. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  362. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +29 -19
  363. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  364. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +9 -9
  365. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +11 -9
  366. package/src/tools/direct/index.ts +3 -0
  367. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  368. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  369. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  370. package/src/tools/discovery/index.ts +3 -0
  371. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  372. package/src/tools/dispatch/coreDispatch.ts +379 -0
  373. package/src/tools/dispatch/diagnosticDispatch.ts +106 -0
  374. package/src/tools/dispatch/directDispatch.ts +168 -0
  375. package/src/tools/dispatch/goalDispatch.ts +128 -0
  376. package/src/tools/dispatch/releaseDispatch.ts +66 -0
  377. package/src/tools/dispatch/types.ts +24 -0
  378. package/src/tools/dispatch/validation.ts +78 -0
  379. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  380. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +43 -28
  381. package/src/tools/goals/index.ts +3 -0
  382. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  383. package/src/tools/registry.ts +54 -1858
  384. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  385. package/src/tools/release/index.ts +2 -0
  386. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  387. package/src/tools/tasks/cancelTask.ts +109 -0
  388. package/src/tools/{createTask.ts → tasks/createTask.ts} +56 -33
  389. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
  390. package/src/tools/tasks/evidencePack.ts +504 -0
  391. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  392. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
  393. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  394. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
  395. package/src/tools/tasks/index.ts +18 -0
  396. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
  397. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
  398. package/src/tools/tasks/retryTask.ts +69 -0
  399. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +70 -35
  400. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  401. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  402. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  403. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  404. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  405. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  406. package/src/tools/workspace/index.ts +10 -0
  407. package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
  408. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  409. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  410. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  411. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  412. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  413. package/src/tools/workspace/syncFile.ts +292 -0
  414. package/src/utils/atomicFile.ts +58 -0
  415. package/src/utils/boundedFile.ts +116 -0
  416. package/src/utils/lockedJsonFile.ts +251 -0
  417. package/src/utils/platform.ts +31 -0
  418. package/src/utils/stableJson.ts +12 -0
  419. package/src/version.ts +3 -2
  420. package/src/watcherStatus.ts +19 -1
  421. package/ui/desktop-bootstrap.js +11 -0
  422. package/ui/desktop-bridge.js +36 -0
  423. package/ui/desktop.css +160 -0
  424. package/ui/getting-started.js +93 -0
  425. package/ui/i18n.js +484 -0
  426. package/ui/log-parser.js +30 -0
  427. package/ui/pages/audit.html +204 -3
  428. package/ui/pages/dashboard.html +937 -79
  429. package/ui/pages/direct-sessions.html +609 -60
  430. package/ui/pages/getting-started.html +43 -0
  431. package/ui/pages/logs.html +105 -22
  432. package/ui/pages/settings.html +101 -0
  433. package/ui/pages/task-detail.html +468 -438
  434. package/ui/pages/tasks.html +648 -65
  435. package/ui/pages/workspace.html +38 -1
  436. package/ui/settings.js +245 -0
  437. package/PatchWarden-Control.cmd +0 -6
  438. package/Restart-PatchWarden-Control.cmd +0 -6
  439. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  440. package/dist/test/unit/acceptance-engine.test.js +0 -228
  441. package/dist/test/unit/agent-router.test.d.ts +0 -1
  442. package/dist/test/unit/agent-router.test.js +0 -287
  443. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  444. package/dist/test/unit/android-doctor.test.js +0 -118
  445. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  446. package/dist/test/unit/audit-checks.test.js +0 -350
  447. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  448. package/dist/test/unit/chinese-path.test.js +0 -91
  449. package/dist/test/unit/command-guard.test.d.ts +0 -1
  450. package/dist/test/unit/command-guard.test.js +0 -160
  451. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  452. package/dist/test/unit/diagnose-task.test.js +0 -457
  453. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  454. package/dist/test/unit/direct-guards.test.js +0 -213
  455. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  456. package/dist/test/unit/discovery-token-store.test.js +0 -139
  457. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  458. package/dist/test/unit/evidence-pack.test.js +0 -130
  459. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  460. package/dist/test/unit/goal-graph.test.js +0 -298
  461. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  462. package/dist/test/unit/goal-progress.test.js +0 -381
  463. package/dist/test/unit/goal-status.test.d.ts +0 -1
  464. package/dist/test/unit/goal-status.test.js +0 -215
  465. package/dist/test/unit/goal-store.test.d.ts +0 -1
  466. package/dist/test/unit/goal-store.test.js +0 -253
  467. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  468. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  469. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  470. package/dist/test/unit/goal-tools-registry.test.js +0 -190
  471. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  472. package/dist/test/unit/handoff-export.test.js +0 -263
  473. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  474. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  475. package/dist/test/unit/logging.test.d.ts +0 -1
  476. package/dist/test/unit/logging.test.js +0 -397
  477. package/dist/test/unit/path-guard.test.d.ts +0 -1
  478. package/dist/test/unit/path-guard.test.js +0 -109
  479. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  480. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  481. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  482. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  483. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  484. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  485. package/dist/test/unit/release-gate.test.d.ts +0 -1
  486. package/dist/test/unit/release-gate.test.js +0 -242
  487. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  488. package/dist/test/unit/run-task-loop.test.js +0 -380
  489. package/dist/test/unit/safe-status.test.d.ts +0 -1
  490. package/dist/test/unit/safe-status.test.js +0 -165
  491. package/dist/test/unit/safe-views.test.d.ts +0 -1
  492. package/dist/test/unit/safe-views.test.js +0 -171
  493. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  494. package/dist/test/unit/schema-drift-check.test.js +0 -176
  495. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  496. package/dist/test/unit/sensitive-guard.test.js +0 -104
  497. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  498. package/dist/test/unit/subgoal-sync.test.js +0 -183
  499. package/dist/test/unit/sync-file.test.d.ts +0 -1
  500. package/dist/test/unit/sync-file.test.js +0 -154
  501. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  502. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  503. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  504. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  505. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  506. package/dist/test/unit/toolSearch.test.js +0 -571
  507. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  508. package/dist/test/unit/watcher-status.test.js +0 -169
  509. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  510. package/dist/test/unit/worktree-manager.test.js +0 -176
  511. package/dist/tools/applyPatch.js +0 -41
  512. package/dist/tools/cancelTask.d.ts +0 -30
  513. package/dist/tools/cancelTask.js +0 -64
  514. package/dist/tools/evidencePack.js +0 -168
  515. package/dist/tools/finalizeDirectSession.js +0 -84
  516. package/dist/tools/goalSubgoalTask.js +0 -110
  517. package/dist/tools/killTask.d.ts +0 -15
  518. package/dist/tools/retryTask.js +0 -45
  519. package/dist/tools/runVerification.js +0 -36
  520. package/dist/tools/syncFile.js +0 -65
  521. package/docs/control-center/control-center-daily-driver.md +0 -211
  522. package/docs/control-center/control-center-mvp.md +0 -205
  523. package/docs/control-center/control-center-phase2.md +0 -159
  524. package/docs/release-v0.6.0.md +0 -71
  525. package/docs/release-v0.6.1.md +0 -75
  526. package/docs/release-v0.6.4.md +0 -45
  527. package/src/test/unit/acceptance-engine.test.ts +0 -261
  528. package/src/test/unit/agent-router.test.ts +0 -342
  529. package/src/test/unit/android-doctor.test.ts +0 -158
  530. package/src/test/unit/audit-checks.test.ts +0 -567
  531. package/src/test/unit/chinese-path.test.ts +0 -106
  532. package/src/test/unit/command-guard.test.ts +0 -221
  533. package/src/test/unit/diagnose-task.test.ts +0 -544
  534. package/src/test/unit/direct-guards.test.ts +0 -297
  535. package/src/test/unit/discovery-token-store.test.ts +0 -181
  536. package/src/test/unit/evidence-pack.test.ts +0 -142
  537. package/src/test/unit/goal-graph.test.ts +0 -347
  538. package/src/test/unit/goal-progress.test.ts +0 -538
  539. package/src/test/unit/goal-status.test.ts +0 -270
  540. package/src/test/unit/goal-store.test.ts +0 -318
  541. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  542. package/src/test/unit/goal-tools-registry.test.ts +0 -243
  543. package/src/test/unit/handoff-export.test.ts +0 -295
  544. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  545. package/src/test/unit/logging.test.ts +0 -497
  546. package/src/test/unit/path-guard.test.ts +0 -150
  547. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  548. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  549. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  550. package/src/test/unit/release-gate.test.ts +0 -314
  551. package/src/test/unit/run-task-loop.test.ts +0 -425
  552. package/src/test/unit/safe-status.test.ts +0 -187
  553. package/src/test/unit/safe-views.test.ts +0 -184
  554. package/src/test/unit/schema-drift-check.test.ts +0 -260
  555. package/src/test/unit/sensitive-guard.test.ts +0 -124
  556. package/src/test/unit/subgoal-sync.test.ts +0 -236
  557. package/src/test/unit/sync-file.test.ts +0 -231
  558. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  559. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  560. package/src/test/unit/toolSearch.test.ts +0 -756
  561. package/src/test/unit/watcher-status.test.ts +0 -190
  562. package/src/test/unit/worktree-manager.test.ts +0 -247
  563. package/src/tools/cancelTask.ts +0 -70
  564. package/src/tools/evidencePack.ts +0 -205
  565. package/src/tools/retryTask.ts +0 -53
  566. package/src/tools/syncFile.ts +0 -122
  567. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  568. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  569. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  570. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  571. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  572. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  573. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  574. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  575. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  576. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  577. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  578. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  579. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  580. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  581. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  582. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  583. /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
  584. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  585. /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
  586. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  587. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  588. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  589. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  590. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  591. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  592. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  593. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  594. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  595. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  596. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
  597. /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
+ }