patchwarden 1.5.1 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (591) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +158 -26
  3. package/README.md +124 -24
  4. package/dist/assessments/agentAssessor.js +21 -12
  5. package/dist/assessments/assessmentDiagnostics.d.ts +29 -0
  6. package/dist/assessments/assessmentDiagnostics.js +132 -0
  7. package/dist/assessments/assessmentStore.d.ts +39 -0
  8. package/dist/assessments/assessmentStore.js +207 -45
  9. package/dist/assessments/securitySnapshot.d.ts +50 -0
  10. package/dist/assessments/securitySnapshot.js +158 -0
  11. package/dist/config.d.ts +15 -0
  12. package/dist/config.js +66 -13
  13. package/dist/control/fileManager.d.ts +2 -0
  14. package/dist/control/fileManager.js +39 -0
  15. package/dist/control/middleware/auth.d.ts +6 -0
  16. package/dist/control/middleware/auth.js +14 -5
  17. package/dist/control/middleware/static.js +16 -3
  18. package/dist/control/routeTable.d.ts +24 -0
  19. package/dist/control/routeTable.js +263 -0
  20. package/dist/control/routes/audit.js +17 -7
  21. package/dist/control/routes/evidence.js +1 -1
  22. package/dist/control/routes/lineage.js +1 -1
  23. package/dist/control/routes/process.d.ts +7 -0
  24. package/dist/control/routes/process.js +329 -42
  25. package/dist/control/routes/sessions.d.ts +9 -1
  26. package/dist/control/routes/sessions.js +84 -21
  27. package/dist/control/routes/status.d.ts +60 -1
  28. package/dist/control/routes/status.js +94 -15
  29. package/dist/control/routes/taskActions.js +24 -60
  30. package/dist/control/routes/tasks.d.ts +4 -0
  31. package/dist/control/routes/tasks.js +61 -62
  32. package/dist/control/routes/workspace.js +50 -23
  33. package/dist/control/runtime.d.ts +3 -2
  34. package/dist/control/runtime.js +65 -40
  35. package/dist/control/server.js +37 -365
  36. package/dist/control/shared.d.ts +25 -1
  37. package/dist/control/shared.js +64 -11
  38. package/dist/diagnostics/allowedTestCommandSafety.d.ts +1 -0
  39. package/dist/diagnostics/allowedTestCommandSafety.js +11 -0
  40. package/dist/direct/directAudit.js +234 -225
  41. package/dist/direct/directGuards.d.ts +2 -1
  42. package/dist/direct/directGuards.js +42 -29
  43. package/dist/direct/directPatch.d.ts +6 -1
  44. package/dist/direct/directPatch.js +55 -8
  45. package/dist/direct/directSessionStore.d.ts +16 -6
  46. package/dist/direct/directSessionStore.js +128 -35
  47. package/dist/direct/directVerification.js +8 -16
  48. package/dist/doctor.js +65 -57
  49. package/dist/goal/acceptanceEngine.d.ts +1 -1
  50. package/dist/goal/goalProgress.js +75 -69
  51. package/dist/goal/goalReport.js +5 -12
  52. package/dist/goal/goalStatus.d.ts +2 -0
  53. package/dist/goal/goalStatus.js +10 -3
  54. package/dist/goal/goalStore.d.ts +9 -2
  55. package/dist/goal/goalStore.js +152 -45
  56. package/dist/goal/handoffExport.js +6 -6
  57. package/dist/goal/specKitImport.js +97 -96
  58. package/dist/goal/subgoalSync.js +11 -9
  59. package/dist/goal/worktreeManager.d.ts +2 -0
  60. package/dist/goal/worktreeManager.js +209 -136
  61. package/dist/httpServer.js +111 -32
  62. package/dist/logging.js +5 -3
  63. package/dist/policy/projectPolicy.js +5 -2
  64. package/dist/release/releaseGate.d.ts +1 -1
  65. package/dist/release/releaseGate.js +78 -25
  66. package/dist/runner/agentInvocation.d.ts +14 -0
  67. package/dist/runner/agentInvocation.js +96 -4
  68. package/dist/runner/changeCapture.d.ts +13 -5
  69. package/dist/runner/changeCapture.js +243 -61
  70. package/dist/runner/postTaskCleanup.js +48 -19
  71. package/dist/runner/processSecurity.d.ts +56 -0
  72. package/dist/runner/processSecurity.js +373 -0
  73. package/dist/runner/runTask.d.ts +2 -1
  74. package/dist/runner/runTask.js +162 -93
  75. package/dist/runner/simpleProcess.d.ts +3 -0
  76. package/dist/runner/simpleProcess.js +50 -36
  77. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  78. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  79. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +3 -1
  80. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  81. package/dist/runner/taskStatusStore.d.ts +11 -0
  82. package/dist/runner/taskStatusStore.js +35 -0
  83. package/dist/runner/watch.d.ts +14 -1
  84. package/dist/runner/watch.js +320 -49
  85. package/dist/security/contentRedaction.js +2 -2
  86. package/dist/security/discoveryTokenStore.d.ts +3 -2
  87. package/dist/security/discoveryTokenStore.js +17 -2
  88. package/dist/security/loopbackHost.d.ts +2 -0
  89. package/dist/security/loopbackHost.js +16 -0
  90. package/dist/security/pathGuard.js +32 -17
  91. package/dist/security/planGuard.js +2 -1
  92. package/dist/security/runtimeGuard.js +9 -6
  93. package/dist/security/secretComparison.d.ts +3 -0
  94. package/dist/security/secretComparison.js +11 -0
  95. package/dist/security/sensitiveGuard.d.ts +5 -0
  96. package/dist/security/sensitiveGuard.js +27 -8
  97. package/dist/security/toolInvocationGuard.d.ts +2 -2
  98. package/dist/security/toolInvocationGuard.js +1 -1
  99. package/dist/security/workspaceRootGuard.d.ts +8 -0
  100. package/dist/security/workspaceRootGuard.js +47 -0
  101. package/dist/smoke-test.js +27 -26
  102. package/dist/tools/catalog/index.d.ts +4 -0
  103. package/dist/tools/catalog/index.js +4 -0
  104. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  105. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  106. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +6 -15
  107. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  108. package/dist/tools/definitions/toolDefs.js +1177 -0
  109. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  110. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  111. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +2 -1
  112. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +134 -36
  113. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +18 -6
  114. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +36 -8
  115. package/dist/tools/diagnostics/index.d.ts +6 -0
  116. package/dist/tools/diagnostics/index.js +6 -0
  117. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  118. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  119. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +12 -8
  120. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +23 -13
  121. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  122. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  123. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +6 -6
  124. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +1 -1
  125. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  126. package/dist/tools/direct/index.d.ts +3 -0
  127. package/dist/tools/direct/index.js +3 -0
  128. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  129. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  130. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  131. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  132. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  133. package/dist/tools/discovery/index.d.ts +3 -0
  134. package/dist/tools/discovery/index.js +3 -0
  135. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  136. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  137. package/dist/tools/dispatch/coreDispatch.js +32 -31
  138. package/dist/tools/dispatch/diagnosticDispatch.d.ts +3 -5
  139. package/dist/tools/dispatch/diagnosticDispatch.js +70 -68
  140. package/dist/tools/dispatch/directDispatch.js +11 -10
  141. package/dist/tools/dispatch/goalDispatch.js +5 -4
  142. package/dist/tools/dispatch/releaseDispatch.js +3 -2
  143. package/dist/tools/dispatch/validation.d.ts +7 -0
  144. package/dist/tools/dispatch/validation.js +71 -0
  145. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  146. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +1 -1
  147. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  148. package/dist/tools/goals/index.d.ts +3 -0
  149. package/dist/tools/goals/index.js +3 -0
  150. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  151. package/dist/tools/registry.d.ts +7 -11
  152. package/dist/tools/registry.js +17 -1178
  153. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  154. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  155. package/dist/tools/release/index.d.ts +2 -0
  156. package/dist/tools/release/index.js +2 -0
  157. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  158. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  159. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  160. package/dist/tools/tasks/cancelTask.js +86 -0
  161. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
  162. package/dist/tools/{createTask.js → tasks/createTask.js} +110 -34
  163. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  164. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +12 -16
  165. package/dist/tools/{evidencePack.js → tasks/evidencePack.js} +34 -22
  166. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  167. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  168. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +11 -6
  169. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  170. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +35 -36
  171. package/dist/tools/tasks/index.d.ts +18 -0
  172. package/dist/tools/tasks/index.js +18 -0
  173. package/dist/tools/tasks/killTask.d.ts +1 -0
  174. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  175. package/dist/tools/{listTasks.js → tasks/listTasks.js} +5 -4
  176. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +3 -2
  177. package/dist/tools/tasks/reconcileTasks.js +496 -0
  178. package/dist/tools/tasks/retryTask.js +60 -0
  179. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  180. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +63 -41
  181. package/dist/tools/tasks/runVerification.js +38 -0
  182. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  183. package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +2 -2
  184. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  185. package/dist/tools/tasks/taskStates.d.ts +6 -0
  186. package/dist/tools/tasks/taskStates.js +52 -0
  187. package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +3 -11
  188. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  189. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  190. package/dist/tools/workspace/applyPatch.js +53 -0
  191. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  192. package/dist/tools/workspace/index.d.ts +10 -0
  193. package/dist/tools/workspace/index.js +10 -0
  194. package/dist/tools/workspace/listAgents.d.ts +23 -0
  195. package/dist/tools/workspace/listAgents.js +56 -0
  196. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  197. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  198. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  199. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  200. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  201. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  202. package/dist/tools/workspace/syncFile.js +134 -0
  203. package/dist/utils/atomicFile.d.ts +7 -0
  204. package/dist/utils/atomicFile.js +67 -0
  205. package/dist/utils/boundedFile.d.ts +8 -0
  206. package/dist/utils/boundedFile.js +93 -0
  207. package/dist/utils/lockedJsonFile.d.ts +15 -0
  208. package/dist/utils/lockedJsonFile.js +224 -0
  209. package/dist/utils/platform.d.ts +11 -0
  210. package/dist/utils/platform.js +26 -0
  211. package/dist/utils/stableJson.d.ts +2 -0
  212. package/dist/utils/stableJson.js +12 -0
  213. package/dist/version.d.ts +3 -2
  214. package/dist/version.js +3 -2
  215. package/dist/watcherStatus.d.ts +2 -2
  216. package/dist/watcherStatus.js +18 -0
  217. package/docs/CODE_WIKI.md +808 -143
  218. package/docs/architecture.md +15 -55
  219. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  220. package/docs/control-center/README.md +4 -4
  221. package/docs/dashboard-overview.md +4 -4
  222. package/docs/desktop-app.md +175 -0
  223. package/docs/evidence-pack-schema.md +3 -3
  224. package/docs/open-source-application.md +16 -11
  225. package/docs/release-checklist.md +1 -1
  226. package/docs/release-evidence.md +35 -39
  227. package/docs/task-safe-review-workflow.md +5 -5
  228. package/docs/threat-model.md +19 -1
  229. package/examples/config.example.json +4 -2
  230. package/package.json +27 -10
  231. package/scripts/README.md +15 -6
  232. package/scripts/build.js +11 -0
  233. package/scripts/checks/brand-check.js +3 -0
  234. package/scripts/checks/build-output-check.js +37 -0
  235. package/scripts/checks/control-center-smoke.js +102 -10
  236. package/scripts/checks/control-smoke.js +19 -8
  237. package/scripts/checks/http-mcp-smoke.js +82 -4
  238. package/scripts/checks/lifecycle-smoke.js +15 -12
  239. package/scripts/checks/mcp-manifest-check.js +1 -1
  240. package/scripts/checks/mcp-smoke.js +58 -4
  241. package/scripts/checks/package-manifest-check.js +20 -7
  242. package/scripts/checks/unit-tests.js +3 -5
  243. package/scripts/checks/watcher-supervisor-smoke.js +38 -1
  244. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  245. package/scripts/control/manage-patchwarden.ps1 +69 -4
  246. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  247. package/scripts/control/run-background-supervisor.ps1 +41 -0
  248. package/scripts/control/start-patchwarden-tunnel.ps1 +191 -33
  249. package/scripts/e2e/demo-runtime.mjs +153 -0
  250. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  251. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  252. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  253. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  254. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  255. package/scripts/lib/clean-generated-output.js +11 -0
  256. package/scripts/lib/file-discovery.js +12 -0
  257. package/scripts/release/desktop-preflight.js +211 -0
  258. package/scripts/release/pack-clean.js +59 -14
  259. package/src/assessments/agentAssessor.ts +26 -12
  260. package/src/assessments/assessmentDiagnostics.ts +172 -0
  261. package/src/assessments/assessmentStore.ts +288 -51
  262. package/src/assessments/securitySnapshot.ts +231 -0
  263. package/src/config.ts +70 -13
  264. package/src/control/fileManager.ts +40 -0
  265. package/src/control/middleware/auth.ts +22 -4
  266. package/src/control/middleware/static.ts +16 -3
  267. package/src/control/routeTable.ts +301 -0
  268. package/src/control/routes/audit.ts +14 -6
  269. package/src/control/routes/evidence.ts +1 -1
  270. package/src/control/routes/lineage.ts +1 -1
  271. package/src/control/routes/process.ts +341 -40
  272. package/src/control/routes/sessions.ts +88 -25
  273. package/src/control/routes/status.ts +134 -17
  274. package/src/control/routes/taskActions.ts +24 -58
  275. package/src/control/routes/tasks.ts +66 -58
  276. package/src/control/routes/workspace.ts +56 -25
  277. package/src/control/runtime.ts +71 -43
  278. package/src/control/server.ts +37 -342
  279. package/src/control/shared.ts +77 -11
  280. package/src/diagnostics/allowedTestCommandSafety.ts +12 -0
  281. package/src/direct/directAudit.ts +17 -9
  282. package/src/direct/directGuards.ts +72 -44
  283. package/src/direct/directPatch.ts +95 -8
  284. package/src/direct/directSessionStore.ts +213 -55
  285. package/src/direct/directVerification.ts +9 -23
  286. package/src/doctor.ts +76 -64
  287. package/src/goal/acceptanceEngine.ts +1 -1
  288. package/src/goal/goalProgress.ts +17 -13
  289. package/src/goal/goalReport.ts +5 -13
  290. package/src/goal/goalStatus.ts +19 -3
  291. package/src/goal/goalStore.ts +241 -54
  292. package/src/goal/handoffExport.ts +6 -6
  293. package/src/goal/specKitImport.ts +12 -12
  294. package/src/goal/subgoalSync.ts +11 -8
  295. package/src/goal/worktreeManager.ts +139 -22
  296. package/src/httpServer.ts +113 -30
  297. package/src/logging.ts +10 -3
  298. package/src/policy/projectPolicy.ts +5 -2
  299. package/src/release/releaseGate.ts +74 -27
  300. package/src/runner/agentInvocation.ts +126 -4
  301. package/src/runner/changeCapture.ts +291 -57
  302. package/src/runner/postTaskCleanup.ts +41 -17
  303. package/src/runner/processSecurity.ts +433 -0
  304. package/src/runner/runTask.ts +197 -97
  305. package/src/runner/simpleProcess.ts +66 -31
  306. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  307. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +6 -3
  308. package/src/runner/taskStatusStore.ts +69 -0
  309. package/src/runner/watch.ts +348 -52
  310. package/src/security/contentRedaction.ts +2 -2
  311. package/src/security/discoveryTokenStore.ts +18 -4
  312. package/src/security/loopbackHost.ts +19 -0
  313. package/src/security/pathGuard.ts +35 -16
  314. package/src/security/planGuard.ts +2 -1
  315. package/src/security/runtimeGuard.ts +9 -6
  316. package/src/security/secretComparison.ts +13 -0
  317. package/src/security/sensitiveGuard.ts +27 -8
  318. package/src/security/toolInvocationGuard.ts +2 -2
  319. package/src/security/workspaceRootGuard.ts +53 -0
  320. package/src/smoke-test.ts +27 -26
  321. package/src/tools/catalog/index.ts +4 -0
  322. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  323. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +7 -16
  324. package/src/tools/definitions/toolDefs.ts +1264 -0
  325. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  326. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +136 -39
  327. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +37 -9
  328. package/src/tools/diagnostics/index.ts +6 -0
  329. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  330. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +27 -17
  331. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  332. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +6 -6
  333. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +7 -5
  334. package/src/tools/direct/index.ts +3 -0
  335. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  336. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  337. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  338. package/src/tools/discovery/index.ts +3 -0
  339. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  340. package/src/tools/dispatch/coreDispatch.ts +36 -31
  341. package/src/tools/dispatch/diagnosticDispatch.ts +26 -21
  342. package/src/tools/dispatch/directDispatch.ts +11 -10
  343. package/src/tools/dispatch/goalDispatch.ts +5 -4
  344. package/src/tools/dispatch/releaseDispatch.ts +3 -2
  345. package/src/tools/dispatch/validation.ts +78 -0
  346. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  347. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +41 -26
  348. package/src/tools/goals/index.ts +3 -0
  349. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  350. package/src/tools/registry.ts +17 -1261
  351. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  352. package/src/tools/release/index.ts +2 -0
  353. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  354. package/src/tools/tasks/cancelTask.ts +110 -0
  355. package/src/tools/{createTask.ts → tasks/createTask.ts} +161 -33
  356. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +12 -16
  357. package/src/tools/{evidencePack.ts → tasks/evidencePack.ts} +36 -22
  358. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  359. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +13 -6
  360. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  361. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +46 -44
  362. package/src/tools/tasks/index.ts +18 -0
  363. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +6 -5
  364. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +228 -77
  365. package/src/tools/tasks/retryTask.ts +69 -0
  366. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +73 -46
  367. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  368. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  369. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  370. package/src/tools/tasks/taskStates.ts +54 -0
  371. package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +3 -12
  372. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  373. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  374. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  375. package/src/tools/workspace/index.ts +10 -0
  376. package/src/tools/workspace/listAgents.ts +75 -0
  377. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  378. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  379. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  380. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  381. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  382. package/src/tools/workspace/syncFile.ts +292 -0
  383. package/src/utils/atomicFile.ts +74 -0
  384. package/src/utils/boundedFile.ts +116 -0
  385. package/src/utils/lockedJsonFile.ts +251 -0
  386. package/src/utils/platform.ts +31 -0
  387. package/src/utils/stableJson.ts +12 -0
  388. package/src/version.ts +3 -2
  389. package/src/watcherStatus.ts +19 -1
  390. package/ui/desktop-bootstrap.js +11 -0
  391. package/ui/desktop-bridge.js +36 -0
  392. package/ui/desktop.css +160 -0
  393. package/ui/getting-started.js +93 -0
  394. package/ui/i18n.js +484 -0
  395. package/ui/log-parser.js +30 -0
  396. package/ui/pages/audit.html +12 -0
  397. package/ui/pages/dashboard.html +94 -147
  398. package/ui/pages/direct-sessions.html +123 -26
  399. package/ui/pages/getting-started.html +43 -0
  400. package/ui/pages/logs.html +134 -22
  401. package/ui/pages/settings.html +101 -0
  402. package/ui/pages/task-detail.html +12 -0
  403. package/ui/pages/tasks.html +54 -8
  404. package/ui/pages/workspace.html +38 -1
  405. package/ui/settings.js +254 -0
  406. package/PatchWarden-Control.cmd +0 -6
  407. package/Restart-PatchWarden-Control.cmd +0 -6
  408. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  409. package/dist/test/unit/acceptance-engine.test.js +0 -228
  410. package/dist/test/unit/agent-router.test.d.ts +0 -1
  411. package/dist/test/unit/agent-router.test.js +0 -287
  412. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  413. package/dist/test/unit/android-doctor.test.js +0 -118
  414. package/dist/test/unit/apply-patch.test.d.ts +0 -1
  415. package/dist/test/unit/apply-patch.test.js +0 -225
  416. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  417. package/dist/test/unit/audit-checks.test.js +0 -350
  418. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  419. package/dist/test/unit/chinese-path.test.js +0 -91
  420. package/dist/test/unit/command-guard.test.d.ts +0 -1
  421. package/dist/test/unit/command-guard.test.js +0 -160
  422. package/dist/test/unit/create-task.test.d.ts +0 -1
  423. package/dist/test/unit/create-task.test.js +0 -197
  424. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  425. package/dist/test/unit/diagnose-task.test.js +0 -457
  426. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  427. package/dist/test/unit/direct-guards.test.js +0 -328
  428. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  429. package/dist/test/unit/discovery-token-store.test.js +0 -139
  430. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  431. package/dist/test/unit/evidence-pack.test.js +0 -224
  432. package/dist/test/unit/get-task-status.test.d.ts +0 -1
  433. package/dist/test/unit/get-task-status.test.js +0 -174
  434. package/dist/test/unit/get-task-summary.test.d.ts +0 -1
  435. package/dist/test/unit/get-task-summary.test.js +0 -146
  436. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  437. package/dist/test/unit/goal-graph.test.js +0 -298
  438. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  439. package/dist/test/unit/goal-progress.test.js +0 -381
  440. package/dist/test/unit/goal-report.test.d.ts +0 -1
  441. package/dist/test/unit/goal-report.test.js +0 -159
  442. package/dist/test/unit/goal-status.test.d.ts +0 -1
  443. package/dist/test/unit/goal-status.test.js +0 -215
  444. package/dist/test/unit/goal-store.test.d.ts +0 -1
  445. package/dist/test/unit/goal-store.test.js +0 -253
  446. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  447. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  448. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  449. package/dist/test/unit/goal-tools-registry.test.js +0 -192
  450. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  451. package/dist/test/unit/handoff-export.test.js +0 -263
  452. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  453. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  454. package/dist/test/unit/logging.test.d.ts +0 -1
  455. package/dist/test/unit/logging.test.js +0 -397
  456. package/dist/test/unit/path-guard.test.d.ts +0 -1
  457. package/dist/test/unit/path-guard.test.js +0 -133
  458. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  459. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  460. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  461. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  462. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  463. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  464. package/dist/test/unit/release-gate.test.d.ts +0 -1
  465. package/dist/test/unit/release-gate.test.js +0 -242
  466. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  467. package/dist/test/unit/run-task-loop.test.js +0 -380
  468. package/dist/test/unit/safe-status.test.d.ts +0 -1
  469. package/dist/test/unit/safe-status.test.js +0 -165
  470. package/dist/test/unit/safe-views.test.d.ts +0 -1
  471. package/dist/test/unit/safe-views.test.js +0 -171
  472. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  473. package/dist/test/unit/schema-drift-check.test.js +0 -176
  474. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  475. package/dist/test/unit/sensitive-guard.test.js +0 -104
  476. package/dist/test/unit/spec-kit-import.test.d.ts +0 -1
  477. package/dist/test/unit/spec-kit-import.test.js +0 -341
  478. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  479. package/dist/test/unit/subgoal-sync.test.js +0 -183
  480. package/dist/test/unit/sync-file.test.d.ts +0 -1
  481. package/dist/test/unit/sync-file.test.js +0 -154
  482. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  483. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  484. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  485. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  486. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  487. package/dist/test/unit/toolSearch.test.js +0 -571
  488. package/dist/test/unit/wait-for-task.test.d.ts +0 -1
  489. package/dist/test/unit/wait-for-task.test.js +0 -144
  490. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  491. package/dist/test/unit/watcher-status.test.js +0 -169
  492. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  493. package/dist/test/unit/worktree-manager.test.js +0 -176
  494. package/dist/tools/applyPatch.js +0 -41
  495. package/dist/tools/cancelTask.d.ts +0 -30
  496. package/dist/tools/cancelTask.js +0 -64
  497. package/dist/tools/finalizeDirectSession.js +0 -84
  498. package/dist/tools/goalSubgoalTask.js +0 -110
  499. package/dist/tools/killTask.d.ts +0 -15
  500. package/dist/tools/listAgents.d.ts +0 -12
  501. package/dist/tools/listAgents.js +0 -42
  502. package/dist/tools/reconcileTasks.js +0 -352
  503. package/dist/tools/retryTask.js +0 -45
  504. package/dist/tools/runVerification.js +0 -36
  505. package/dist/tools/syncFile.js +0 -65
  506. package/docs/control-center/control-center-daily-driver.md +0 -211
  507. package/docs/control-center/control-center-mvp.md +0 -205
  508. package/docs/control-center/control-center-phase2.md +0 -159
  509. package/docs/execution-plan-2026-07-09.md +0 -315
  510. package/docs/release-v0.6.0.md +0 -71
  511. package/docs/release-v0.6.1.md +0 -75
  512. package/docs/release-v0.6.4.md +0 -45
  513. package/docs/roadmap-execution-and-acceptance.md +0 -365
  514. package/src/test/unit/acceptance-engine.test.ts +0 -261
  515. package/src/test/unit/agent-router.test.ts +0 -342
  516. package/src/test/unit/android-doctor.test.ts +0 -158
  517. package/src/test/unit/apply-patch.test.ts +0 -293
  518. package/src/test/unit/audit-checks.test.ts +0 -567
  519. package/src/test/unit/chinese-path.test.ts +0 -106
  520. package/src/test/unit/command-guard.test.ts +0 -221
  521. package/src/test/unit/create-task.test.ts +0 -255
  522. package/src/test/unit/diagnose-task.test.ts +0 -544
  523. package/src/test/unit/direct-guards.test.ts +0 -467
  524. package/src/test/unit/discovery-token-store.test.ts +0 -181
  525. package/src/test/unit/evidence-pack.test.ts +0 -251
  526. package/src/test/unit/get-task-status.test.ts +0 -203
  527. package/src/test/unit/get-task-summary.test.ts +0 -173
  528. package/src/test/unit/goal-graph.test.ts +0 -347
  529. package/src/test/unit/goal-progress.test.ts +0 -538
  530. package/src/test/unit/goal-report.test.ts +0 -189
  531. package/src/test/unit/goal-status.test.ts +0 -270
  532. package/src/test/unit/goal-store.test.ts +0 -318
  533. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  534. package/src/test/unit/goal-tools-registry.test.ts +0 -245
  535. package/src/test/unit/handoff-export.test.ts +0 -295
  536. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  537. package/src/test/unit/logging.test.ts +0 -497
  538. package/src/test/unit/path-guard.test.ts +0 -180
  539. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  540. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  541. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  542. package/src/test/unit/release-gate.test.ts +0 -314
  543. package/src/test/unit/run-task-loop.test.ts +0 -425
  544. package/src/test/unit/safe-status.test.ts +0 -187
  545. package/src/test/unit/safe-views.test.ts +0 -184
  546. package/src/test/unit/schema-drift-check.test.ts +0 -260
  547. package/src/test/unit/sensitive-guard.test.ts +0 -124
  548. package/src/test/unit/spec-kit-import.test.ts +0 -429
  549. package/src/test/unit/subgoal-sync.test.ts +0 -236
  550. package/src/test/unit/sync-file.test.ts +0 -231
  551. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  552. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  553. package/src/test/unit/toolSearch.test.ts +0 -756
  554. package/src/test/unit/wait-for-task.test.ts +0 -176
  555. package/src/test/unit/watcher-status.test.ts +0 -190
  556. package/src/test/unit/worktree-manager.test.ts +0 -247
  557. package/src/tools/cancelTask.ts +0 -70
  558. package/src/tools/listAgents.ts +0 -54
  559. package/src/tools/retryTask.ts +0 -53
  560. package/src/tools/syncFile.ts +0 -122
  561. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  562. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  563. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  564. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  565. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  566. /package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +0 -0
  567. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  568. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  569. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  570. /package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +0 -0
  571. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  572. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  573. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  574. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  575. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  576. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  577. /package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +0 -0
  578. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  579. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  580. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  581. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  582. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  583. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  584. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  585. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  586. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  587. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  588. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  589. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  590. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  591. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
@@ -0,0 +1,496 @@
1
+ import { existsSync, readdirSync, readFileSync, mkdirSync, statSync, } from "node:fs";
2
+ import { join, resolve, dirname } from "node:path";
3
+ import { getTasksDir, getConfig } from "../../config.js";
4
+ import { diagnoseTask } from "./diagnoseTask.js";
5
+ import { syncSubgoalOnTaskDone, readTaskGoalMeta } from "../../goal/subgoalSync.js";
6
+ import { mutateTaskStatus } from "../../runner/taskStatusStore.js";
7
+ import { writeTaskRuntime } from "../../runner/taskRuntime.js";
8
+ import { atomicWriteFileSync, atomicWriteJsonFileSync } from "../../utils/atomicFile.js";
9
+ import { appendBoundedTextFileSync } from "../../utils/boundedFile.js";
10
+ import { isActiveTaskStatus } from "./taskStates.js";
11
+ // ── Defaults ────────────────────────────────────────────────────────
12
+ const DEFAULT_MAX_AGE_MINUTES = 30;
13
+ const RECONCILE_LOG_NAME = "reconcile.log";
14
+ // ── Main entry point ──────────────────────────────────────────────
15
+ export function reconcileTasks(input = {}, config = getConfig()) {
16
+ const mode = input.mode === "safe_fix" ? "safe_fix" : "report_only";
17
+ const maxAgeMinutes = typeof input.max_age_minutes === "number" && input.max_age_minutes > 0
18
+ ? Math.min(input.max_age_minutes, 24 * 60)
19
+ : DEFAULT_MAX_AGE_MINUTES;
20
+ const includeDoneCandidates = input.include_done_candidates !== false; // default true
21
+ const requestedTaskIds = input.task_ids?.length
22
+ ? new Set(input.task_ids.filter((taskId) => /^task[-_][a-zA-Z0-9_-]+$/.test(taskId)))
23
+ : null;
24
+ const maxAgeSeconds = maxAgeMinutes * 60;
25
+ const tasksDir = getTasksDir(config);
26
+ const reports = [];
27
+ let scanned = 0;
28
+ let candidates = 0;
29
+ let reconciled = 0;
30
+ let skippedLowConfidence = 0;
31
+ let skippedActiveWatcher = 0;
32
+ const nowMs = Date.now();
33
+ if (!existsSync(tasksDir)) {
34
+ return {
35
+ mode,
36
+ scanned: 0,
37
+ candidates: 0,
38
+ reconciled: 0,
39
+ skipped_low_confidence: 0,
40
+ skipped_active_watcher: 0,
41
+ reports: [],
42
+ reconcile_log_path: null,
43
+ };
44
+ }
45
+ let entries = [];
46
+ try {
47
+ entries = readdirSync(tasksDir, { withFileTypes: true });
48
+ }
49
+ catch {
50
+ return {
51
+ mode,
52
+ scanned: 0,
53
+ candidates: 0,
54
+ reconciled: 0,
55
+ skipped_low_confidence: 0,
56
+ skipped_active_watcher: 0,
57
+ reports: [],
58
+ reconcile_log_path: null,
59
+ };
60
+ }
61
+ const taskDirs = entries.filter((e) => e.isDirectory());
62
+ for (const entry of taskDirs) {
63
+ const taskId = entry.name;
64
+ if (requestedTaskIds && !requestedTaskIds.has(taskId))
65
+ continue;
66
+ scanned += 1;
67
+ const taskDir = resolve(tasksDir, taskId);
68
+ const statusFile = join(taskDir, "status.json");
69
+ if (!existsSync(statusFile))
70
+ continue;
71
+ let statusData;
72
+ try {
73
+ statusData = JSON.parse(readFileSync(statusFile, "utf-8"));
74
+ }
75
+ catch {
76
+ continue; // corrupted status, skip
77
+ }
78
+ const statusStr = typeof statusData.status === "string" ? statusData.status : "unknown";
79
+ // Scan every persisted non-terminal lifecycle spelling, including legacy
80
+ // phase-as-status records left by older Watchers.
81
+ const isCandidate = isActiveTaskStatus(statusStr) ||
82
+ (includeDoneCandidates && statusStr === "done_by_agent");
83
+ if (!isCandidate)
84
+ continue;
85
+ // Use the oldest reliable timestamp for ordinary stale detection. Explicit
86
+ // cancel, persisted deadline, and Watcher-instance mismatch bypass this
87
+ // age gate so a restart cannot extend the task's total budget.
88
+ const ageSeconds = taskAgeSeconds(taskDir, statusData, nowMs);
89
+ // Diagnose the task
90
+ let diagnosis;
91
+ try {
92
+ diagnosis = diagnoseTask({ task_id: taskId }, config);
93
+ }
94
+ catch {
95
+ continue; // diagnosis failed — skip
96
+ }
97
+ const cancellationRequested = statusData.cancel_requested === true || statusStr === "cancel_requested";
98
+ const deadlineExceeded = taskDeadlineExceeded(statusData, nowMs);
99
+ const urgentRecovery = cancellationRequested
100
+ || deadlineExceeded
101
+ || diagnosis.diagnosis === "orphaned_running";
102
+ if (ageSeconds !== null && ageSeconds < maxAgeSeconds && !urgentRecovery)
103
+ continue;
104
+ candidates += 1;
105
+ const report = {
106
+ task_id: taskId,
107
+ status: diagnosis.status,
108
+ phase: diagnosis.phase,
109
+ diagnosis: diagnosis.diagnosis,
110
+ confidence: diagnosis.confidence,
111
+ reasons: diagnosis.reasons,
112
+ safe_actions: diagnosis.safe_actions,
113
+ age_seconds: ageSeconds,
114
+ action_taken: "left_unchanged",
115
+ previous_status: null,
116
+ new_status: null,
117
+ applied_at: null,
118
+ applied_by: null,
119
+ evidence_summary: {
120
+ heartbeat_age_seconds: diagnosis.evidence.heartbeat_age_seconds,
121
+ stdout_age_seconds: diagnosis.evidence.stdout_age_seconds,
122
+ child_pid: diagnosis.evidence.child_pid,
123
+ child_pid_alive: diagnosis.evidence.child_pid_alive,
124
+ watcher_owns_task: diagnosis.evidence.watcher_owns_task,
125
+ },
126
+ };
127
+ // ── safe_fix rules ──
128
+ //
129
+ // safe_fix is ONLY applied when:
130
+ // 1. mode === "safe_fix"
131
+ // 2. diagnosis.confidence === "high"
132
+ // 3. The task is NOT still owned by an active watcher (we must not
133
+ // touch tasks the live watcher is executing).
134
+ // 4. The diagnosis type maps to a reconcilable action.
135
+ //
136
+ // Anything else is left_unchanged and recorded for audit.
137
+ const deterministicRecovery = !diagnosis.evidence.watcher_owns_task
138
+ && (cancellationRequested || deadlineExceeded);
139
+ if (mode === "safe_fix" && (diagnosis.confidence === "high" || deterministicRecovery)) {
140
+ if (diagnosis.evidence.watcher_owns_task) {
141
+ // Hard rule: do not touch tasks the live watcher still owns.
142
+ skippedActiveWatcher += 1;
143
+ report.reasons = [
144
+ ...report.reasons,
145
+ "safe_fix skipped: task is still owned by an active watcher instance",
146
+ ];
147
+ }
148
+ else {
149
+ const fixResult = applySafeFix(taskDir, taskId, statusData, diagnosis.diagnosis, diagnosis.reasons, diagnosis.evidence, config);
150
+ if (fixResult.applied) {
151
+ report.action_taken = fixResult.action_taken;
152
+ report.previous_status = fixResult.previous_status;
153
+ report.new_status = fixResult.new_status;
154
+ report.applied_at = fixResult.applied_at;
155
+ report.applied_by = "reconcile_tasks";
156
+ reconciled += 1;
157
+ }
158
+ else {
159
+ report.reasons = [
160
+ ...report.reasons,
161
+ `safe_fix skipped: ${fixResult.skip_reason || "task state could not be updated safely"}`,
162
+ ];
163
+ skippedLowConfidence += 1;
164
+ }
165
+ }
166
+ }
167
+ else if (mode === "safe_fix" && diagnosis.confidence !== "high") {
168
+ skippedLowConfidence += 1;
169
+ report.reasons = [
170
+ ...report.reasons,
171
+ `safe_fix skipped: confidence is "${diagnosis.confidence}", only "high" is eligible`,
172
+ ];
173
+ }
174
+ if (reports.length < 200) {
175
+ reports.push(report);
176
+ }
177
+ if (reports.length >= 200)
178
+ break;
179
+ }
180
+ // ── Write reconcile.log when safe_fix applied any change ──
181
+ let reconcileLogPath = null;
182
+ if (mode === "safe_fix" && reconciled > 0) {
183
+ reconcileLogPath = writeReconcileLog(tasksDir, config, reports.filter((r) => r.action_taken !== "left_unchanged"));
184
+ }
185
+ return {
186
+ mode,
187
+ scanned,
188
+ candidates,
189
+ reconciled,
190
+ skipped_low_confidence: skippedLowConfidence,
191
+ skipped_active_watcher: skippedActiveWatcher,
192
+ reports,
193
+ reconcile_log_path: reconcileLogPath,
194
+ };
195
+ }
196
+ function applySafeFix(taskDir, taskId, currentStatus, diagnosis, reasons, evidence, config) {
197
+ // Map diagnosis type to new status. Only high-confidence, well-understood
198
+ // diagnoses are eligible. "possibly_stale_running" and "unknown" are NOT
199
+ // eligible even if confidence were high (which they never are).
200
+ const previousStatus = typeof currentStatus.status === "string" ? currentStatus.status : "unknown";
201
+ let newStatus = null;
202
+ let actionTaken = "left_unchanged";
203
+ const cancellationRequested = currentStatus.cancel_requested === true || previousStatus === "cancel_requested";
204
+ if (!evidence.watcher_owns_task && cancellationRequested) {
205
+ newStatus = "canceled";
206
+ actionTaken = "marked_canceled";
207
+ }
208
+ else if (!evidence.watcher_owns_task && taskDeadlineExceeded(currentStatus)) {
209
+ newStatus = "timeout";
210
+ actionTaken = "marked_timed_out";
211
+ }
212
+ else
213
+ switch (diagnosis) {
214
+ case "stale_running":
215
+ newStatus = "failed_stale";
216
+ actionTaken = "marked_failed_stale";
217
+ break;
218
+ case "orphaned_running":
219
+ newStatus = "orphaned";
220
+ actionTaken = "marked_orphaned";
221
+ break;
222
+ case "done_candidate":
223
+ newStatus = "done_by_agent";
224
+ actionTaken = "marked_done_by_agent";
225
+ break;
226
+ case "artifact_collection_stuck":
227
+ // Treat as failed_stale — artifact collection should not hang.
228
+ newStatus = "failed_stale";
229
+ actionTaken = "marked_failed_stale";
230
+ break;
231
+ default:
232
+ // active_running, possibly_stale_running, unknown, terminal — not eligible
233
+ return {
234
+ applied: false,
235
+ action_taken: "left_unchanged",
236
+ previous_status: previousStatus,
237
+ new_status: previousStatus,
238
+ applied_at: new Date().toISOString(),
239
+ skip_reason: `diagnosis "${diagnosis}" has no automatic safe_fix action`,
240
+ };
241
+ }
242
+ const appliedAt = new Date().toISOString();
243
+ const statusFile = join(taskDir, "status.json");
244
+ const backupFile = join(taskDir, "status.json.bak");
245
+ // Revalidate and update under the shared task status lock. Any heartbeat or
246
+ // ownership-related status change after diagnosis makes this repair stale.
247
+ try {
248
+ const outcome = mutateTaskStatus(statusFile, (current) => {
249
+ if (JSON.stringify(current) !== JSON.stringify(currentStatus)) {
250
+ return { result: { applied: false, reason: "task status changed after diagnosis" } };
251
+ }
252
+ const lockedPreviousStatus = typeof current.status === "string" ? current.status : "unknown";
253
+ const next = {
254
+ ...current,
255
+ status: newStatus,
256
+ phase: newStatus,
257
+ updated_at: appliedAt,
258
+ last_heartbeat_at: appliedAt,
259
+ finished_at: appliedAt,
260
+ previous_status: lockedPreviousStatus,
261
+ diagnosis: {
262
+ type: diagnosis,
263
+ confidence: "high",
264
+ applied_by: "reconcile_tasks",
265
+ applied_at: appliedAt,
266
+ reasons,
267
+ evidence: {
268
+ heartbeat_age_seconds: evidence.heartbeat_age_seconds,
269
+ stdout_age_seconds: evidence.stdout_age_seconds,
270
+ child_pid: evidence.child_pid,
271
+ child_pid_alive: evidence.child_pid_alive,
272
+ watcher_instance_id: evidence.watcher_instance_id,
273
+ current_watcher_instance_id: evidence.current_watcher_instance_id,
274
+ },
275
+ },
276
+ process_cleanup_required: !evidence.watcher_owns_task,
277
+ process_cleanup_reason: !evidence.watcher_owns_task
278
+ ? "The previous runner is no longer owned by this Watcher; child-process termination could not be confirmed and no untrusted PID was killed."
279
+ : null,
280
+ };
281
+ if (newStatus === "canceled") {
282
+ next.canceled_at = appliedAt;
283
+ next.cancel_reason = "Cancellation converged after the original task runner stopped responding.";
284
+ next.termination_reason = "canceled";
285
+ next.error_code = "runner_lost_during_cancel";
286
+ }
287
+ else if (actionTaken === "marked_timed_out") {
288
+ next.error = `Task exceeded its configured timeout of ${Number(current.timeout_seconds)} seconds after the original runner stopped responding.`;
289
+ next.termination_reason = "timeout";
290
+ next.error_code = "task_timeout";
291
+ }
292
+ else if (diagnosis === "artifact_collection_stuck") {
293
+ next.error = "Artifact collection was interrupted and could not be safely resumed after the original runner stopped responding.";
294
+ next.error_code = "artifact_collection_interrupted";
295
+ }
296
+ else if (diagnosis === "orphaned_running") {
297
+ next.error = "The original task runner was lost and its child process ownership could not be re-established.";
298
+ next.error_code = "agent_lost";
299
+ }
300
+ else if (diagnosis === "stale_running") {
301
+ next.error = "The task runner heartbeat expired and no current Runner ownership could be proven.";
302
+ next.error_code = "agent_lost";
303
+ }
304
+ if (newStatus === "done_by_agent") {
305
+ next.acceptance_status = "pending";
306
+ next.legacy_status = "done";
307
+ }
308
+ // The backup is created while the same lock protects the exact record
309
+ // being replaced, so it cannot capture an unrelated newer state.
310
+ if (!existsSync(backupFile))
311
+ atomicWriteFileSync(backupFile, readFileSync(statusFile, "utf-8"));
312
+ return {
313
+ next,
314
+ result: { applied: true },
315
+ };
316
+ });
317
+ if (!outcome.applied) {
318
+ return {
319
+ applied: false,
320
+ action_taken: "left_unchanged",
321
+ previous_status: previousStatus,
322
+ new_status: previousStatus,
323
+ applied_at: appliedAt,
324
+ skip_reason: outcome.reason,
325
+ };
326
+ }
327
+ }
328
+ catch {
329
+ return {
330
+ applied: false,
331
+ action_taken: "left_unchanged",
332
+ previous_status: previousStatus,
333
+ new_status: previousStatus,
334
+ applied_at: appliedAt,
335
+ skip_reason: "task status could not be locked, backed up, and updated",
336
+ };
337
+ }
338
+ if (newStatus !== "done_by_agent") {
339
+ writeRecoveryArtifacts(taskDir, taskId, currentStatus, newStatus, actionTaken, appliedAt);
340
+ }
341
+ // Keep the persisted runtime view terminal as well. get_task_status merges
342
+ // runtime.json over status.json, so leaving an old executing phase here
343
+ // would make a successfully reconciled task appear active again.
344
+ writeTaskRuntime(taskDir, {
345
+ phase: newStatus,
346
+ current_command: null,
347
+ last_heartbeat_at: appliedAt,
348
+ child_pid: undefined,
349
+ child_started_at: undefined,
350
+ child_owned_by_runner_instance_id: undefined,
351
+ });
352
+ // v0.8.0: 当状态变为 done_by_agent 时,同步关联 subgoal 状态(running → done_by_agent)
353
+ if (newStatus === "done_by_agent") {
354
+ const goalMeta = readTaskGoalMeta(taskDir);
355
+ if (goalMeta.subgoal_id) {
356
+ syncSubgoalOnTaskDone(taskId, goalMeta, config.workspaceRoot);
357
+ }
358
+ }
359
+ return {
360
+ applied: true,
361
+ action_taken: actionTaken,
362
+ previous_status: previousStatus,
363
+ new_status: newStatus,
364
+ applied_at: appliedAt,
365
+ skip_reason: null,
366
+ };
367
+ }
368
+ function writeRecoveryArtifacts(taskDir, taskId, status, newStatus, actionTaken, recoveredAt) {
369
+ const timeoutSeconds = Number(status.timeout_seconds);
370
+ const summary = actionTaken === "marked_timed_out"
371
+ ? `Task exceeded its configured timeout of ${timeoutSeconds} seconds after the original runner stopped responding.`
372
+ : actionTaken === "marked_canceled"
373
+ ? "Cancellation converged after the original task runner stopped responding."
374
+ : `Task was reconciled to ${newStatus} after the original runner stopped responding.`;
375
+ const errorCode = actionTaken === "marked_timed_out"
376
+ ? "task_timeout"
377
+ : actionTaken === "marked_canceled"
378
+ ? "runner_lost_during_cancel"
379
+ : newStatus === "orphaned"
380
+ ? "agent_lost"
381
+ : newStatus === "failed_stale" && String(status.phase || status.status) === "collecting_artifacts"
382
+ ? "artifact_collection_interrupted"
383
+ : "agent_lost";
384
+ const requestedCommands = Array.isArray(status.verify_commands)
385
+ ? status.verify_commands.map(String)
386
+ : [];
387
+ const artifacts = {
388
+ "verify.json": {
389
+ status: "failed",
390
+ requested_commands: requestedCommands,
391
+ commands: [],
392
+ failure_reason: "runner_recovery",
393
+ error_code: errorCode,
394
+ },
395
+ "result.json": {
396
+ task_id: taskId,
397
+ status: newStatus,
398
+ agent: status.agent || "",
399
+ repo_path: status.repo_path || "",
400
+ resolved_repo_path: status.resolved_repo_path || "",
401
+ summary,
402
+ artifact_status: "partial",
403
+ changed_files: [],
404
+ diff_available: false,
405
+ verify_status: "failed",
406
+ failure_reason: "runner_recovery",
407
+ error_code: errorCode,
408
+ termination_reason: actionTaken === "marked_timed_out"
409
+ ? "timeout"
410
+ : actionTaken === "marked_canceled"
411
+ ? "canceled"
412
+ : "runner_stale",
413
+ recovered_at: recoveredAt,
414
+ warnings: ["The original runner stopped before complete change and verification evidence could be collected."],
415
+ },
416
+ };
417
+ for (const [name, value] of Object.entries(artifacts)) {
418
+ const path = join(taskDir, name);
419
+ if (!existsSync(path))
420
+ atomicWriteJsonFileSync(path, value);
421
+ }
422
+ const textArtifacts = {
423
+ "result.md": `# PatchWarden Task Result\n\n## Status\n${newStatus}\n\n## Summary\n${summary}\n\n## Artifact Status\npartial\n`,
424
+ "test.log": `(not run)\nExit code: not run\n${summary}\n`,
425
+ "git.diff": `(unavailable: ${summary})\n`,
426
+ "diff.patch": `(unavailable: ${summary})\n`,
427
+ };
428
+ for (const [name, content] of Object.entries(textArtifacts)) {
429
+ const path = join(taskDir, name);
430
+ if (!existsSync(path))
431
+ atomicWriteFileSync(path, content);
432
+ }
433
+ }
434
+ function taskDeadlineExceeded(status, nowMs = Date.now()) {
435
+ const timeoutSeconds = Number(status.timeout_seconds);
436
+ const startedAt = typeof status.started_at === "string"
437
+ ? Date.parse(status.started_at)
438
+ : typeof status.created_at === "string"
439
+ ? Date.parse(status.created_at)
440
+ : NaN;
441
+ return Number.isFinite(startedAt)
442
+ && Number.isFinite(timeoutSeconds)
443
+ && timeoutSeconds > 0
444
+ && nowMs >= startedAt + timeoutSeconds * 1000;
445
+ }
446
+ // ── reconcile.log writer ───────────────────────────────────────────
447
+ function writeReconcileLog(tasksDir, config, appliedReports) {
448
+ // The reconcile.log lives at the .patchwarden/ root (parent of tasksDir),
449
+ // so it captures every reconcile run across all tasks.
450
+ const logDir = dirname(tasksDir);
451
+ const logPath = join(logDir, RECONCILE_LOG_NAME);
452
+ try {
453
+ mkdirSync(logDir, { recursive: true });
454
+ }
455
+ catch { /* ignore */ }
456
+ const lines = [];
457
+ for (const report of appliedReports) {
458
+ lines.push(JSON.stringify({
459
+ timestamp: report.applied_at,
460
+ task_id: report.task_id,
461
+ previous_status: report.previous_status,
462
+ new_status: report.new_status,
463
+ diagnosis: report.diagnosis,
464
+ confidence: report.confidence,
465
+ applied_by: report.applied_by,
466
+ reasons: report.reasons,
467
+ evidence: report.evidence_summary,
468
+ }));
469
+ }
470
+ try {
471
+ appendBoundedTextFileSync(logPath, lines.join("\n") + "\n");
472
+ }
473
+ catch {
474
+ // If we cannot write the log, the status change still happened —
475
+ // the status.json itself contains the diagnosis audit fields.
476
+ }
477
+ return logPath;
478
+ }
479
+ // ── Task age helper ────────────────────────────────────────────────
480
+ function taskAgeSeconds(taskDir, status, nowMs) {
481
+ // Prefer created_at; fall back to status.json mtime.
482
+ const createdStr = typeof status.created_at === "string" ? status.created_at : null;
483
+ if (createdStr) {
484
+ const ms = Date.parse(createdStr);
485
+ if (Number.isFinite(ms)) {
486
+ return Math.max(0, Math.round((nowMs - ms) / 1000));
487
+ }
488
+ }
489
+ try {
490
+ const stat = statSync(join(taskDir, "status.json"));
491
+ return Math.max(0, Math.round((nowMs - stat.mtimeMs) / 1000));
492
+ }
493
+ catch {
494
+ return null;
495
+ }
496
+ }
@@ -0,0 +1,60 @@
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
+ export async function retryTask(taskId) {
7
+ const config = getConfig();
8
+ const tasksDir = getTasksDir(config);
9
+ const taskDir = join(tasksDir, taskId);
10
+ const statusFile = join(taskDir, "status.json");
11
+ guardReadPath(statusFile, config.workspaceRoot, config.tasksDir);
12
+ if (!existsSync(statusFile)) {
13
+ throw new Error(`Task not found: "${taskId}"`);
14
+ }
15
+ const parsed = JSON.parse(readFileSync(statusFile, "utf-8"));
16
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
17
+ throw new Error(`Task "${taskId}" has an invalid status record.`);
18
+ }
19
+ const data = parsed;
20
+ if (typeof data.plan_id !== "string" || !data.plan_id ||
21
+ typeof data.agent !== "string" || !data.agent) {
22
+ throw new Error(`Task "${taskId}" is missing plan_id or agent. Cannot retry.`);
23
+ }
24
+ const priorRetryCount = typeof data.retry_count === "number" &&
25
+ Number.isSafeInteger(data.retry_count) && data.retry_count >= 0
26
+ ? data.retry_count
27
+ : 0;
28
+ const planSource = data.plan_source === "inline" || data.plan_source === "template"
29
+ ? data.plan_source
30
+ : "saved";
31
+ const changePolicy = data.change_policy === "no_changes"
32
+ ? "no_changes"
33
+ : "repo_scoped_changes";
34
+ // Create a new task with the same parameters
35
+ const newTask = await createTask({
36
+ plan_id: data.plan_id,
37
+ agent: data.agent,
38
+ repo_path: typeof data.repo_path === "string" ? data.repo_path : undefined,
39
+ test_command: typeof data.test_command === "string" ? data.test_command : undefined,
40
+ verify_commands: Array.isArray(data.verify_commands)
41
+ ? data.verify_commands.filter((value) => typeof value === "string")
42
+ : undefined,
43
+ timeout_seconds: typeof data.timeout_seconds === "number" ? data.timeout_seconds : undefined,
44
+ retry_metadata: {
45
+ retry_of: taskId,
46
+ retry_count: priorRetryCount + 1,
47
+ plan_source: planSource,
48
+ template: typeof data.template === "string" ? data.template : null,
49
+ change_policy: changePolicy,
50
+ },
51
+ });
52
+ return {
53
+ original_task_id: taskId,
54
+ new_task_id: newTask.task_id,
55
+ plan_id: newTask.plan_id,
56
+ agent: newTask.agent,
57
+ status: newTask.status,
58
+ message: `New task created from retry of ${taskId}. Original task is unchanged.`,
59
+ };
60
+ }
@@ -1,11 +1,11 @@
1
- import { createWorktree } from "../goal/worktreeManager.js";
2
- import { createDirectSession } from "./createDirectSession.js";
1
+ import { createWorktree } from "../../goal/worktreeManager.js";
2
+ import { createDirectSession } from "../direct/createDirectSession.js";
3
3
  import { createTask } from "./createTask.js";
4
- import { recommendAgentForTask } from "./recommendAgentForTask.js";
5
- import { runDirectVerificationBundle } from "./runDirectVerificationBundle.js";
4
+ import { recommendAgentForTask } from "../workspace/recommendAgentForTask.js";
5
+ import { runDirectVerificationBundle } from "../direct/runDirectVerificationBundle.js";
6
6
  import { waitForTask } from "./waitForTask.js";
7
- import { safeAudit, safeAuditDirectSession, safeFinalizeDirectSession, safeResult, safeTestSummary } from "./safeViews.js";
8
- import type { TaskTemplateName } from "./taskTemplates.js";
7
+ import { safeAudit, safeAuditDirectSession, safeFinalizeDirectSession, safeResult, safeTestSummary } from "../diagnostics/safeViews.js";
8
+ import type { TaskTemplateName } from "../taskTemplates.js";
9
9
  import { createLineageId, writeTaskLineage, type SafeTaskLineage, type TaskLineageWorktree } from "./taskLineage.js";
10
10
  export interface RunTaskLoopInput {
11
11
  repo_path: string;