patchwarden 1.5.1 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (573) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +152 -25
  3. package/README.md +120 -23
  4. package/dist/assessments/agentAssessor.js +21 -12
  5. package/dist/assessments/assessmentStore.js +42 -34
  6. package/dist/config.d.ts +15 -0
  7. package/dist/config.js +66 -13
  8. package/dist/control/fileManager.d.ts +2 -0
  9. package/dist/control/fileManager.js +39 -0
  10. package/dist/control/middleware/auth.d.ts +6 -0
  11. package/dist/control/middleware/auth.js +14 -5
  12. package/dist/control/middleware/static.js +16 -3
  13. package/dist/control/routeTable.d.ts +24 -0
  14. package/dist/control/routeTable.js +263 -0
  15. package/dist/control/routes/audit.js +17 -7
  16. package/dist/control/routes/evidence.js +1 -1
  17. package/dist/control/routes/lineage.js +1 -1
  18. package/dist/control/routes/process.d.ts +7 -0
  19. package/dist/control/routes/process.js +329 -42
  20. package/dist/control/routes/sessions.d.ts +9 -1
  21. package/dist/control/routes/sessions.js +84 -21
  22. package/dist/control/routes/status.d.ts +46 -1
  23. package/dist/control/routes/status.js +72 -15
  24. package/dist/control/routes/taskActions.js +24 -60
  25. package/dist/control/routes/tasks.d.ts +4 -0
  26. package/dist/control/routes/tasks.js +61 -62
  27. package/dist/control/routes/workspace.js +50 -23
  28. package/dist/control/runtime.d.ts +3 -2
  29. package/dist/control/runtime.js +63 -30
  30. package/dist/control/server.js +36 -365
  31. package/dist/control/shared.d.ts +25 -1
  32. package/dist/control/shared.js +64 -11
  33. package/dist/direct/directAudit.js +234 -225
  34. package/dist/direct/directGuards.d.ts +2 -1
  35. package/dist/direct/directGuards.js +42 -29
  36. package/dist/direct/directPatch.d.ts +6 -1
  37. package/dist/direct/directPatch.js +55 -8
  38. package/dist/direct/directSessionStore.d.ts +16 -6
  39. package/dist/direct/directSessionStore.js +127 -35
  40. package/dist/direct/directVerification.js +8 -16
  41. package/dist/doctor.js +62 -52
  42. package/dist/goal/acceptanceEngine.d.ts +1 -1
  43. package/dist/goal/goalProgress.js +75 -69
  44. package/dist/goal/goalReport.js +5 -12
  45. package/dist/goal/goalStatus.d.ts +2 -0
  46. package/dist/goal/goalStatus.js +10 -3
  47. package/dist/goal/goalStore.d.ts +9 -2
  48. package/dist/goal/goalStore.js +152 -45
  49. package/dist/goal/handoffExport.js +6 -6
  50. package/dist/goal/specKitImport.js +97 -96
  51. package/dist/goal/subgoalSync.js +11 -9
  52. package/dist/goal/worktreeManager.d.ts +2 -0
  53. package/dist/goal/worktreeManager.js +209 -136
  54. package/dist/httpServer.js +111 -32
  55. package/dist/logging.js +5 -3
  56. package/dist/policy/projectPolicy.js +5 -2
  57. package/dist/release/releaseGate.d.ts +1 -1
  58. package/dist/release/releaseGate.js +78 -25
  59. package/dist/runner/agentInvocation.d.ts +14 -0
  60. package/dist/runner/agentInvocation.js +96 -4
  61. package/dist/runner/changeCapture.d.ts +13 -5
  62. package/dist/runner/changeCapture.js +243 -61
  63. package/dist/runner/postTaskCleanup.js +48 -19
  64. package/dist/runner/processSecurity.d.ts +56 -0
  65. package/dist/runner/processSecurity.js +373 -0
  66. package/dist/runner/runTask.d.ts +1 -1
  67. package/dist/runner/runTask.js +115 -87
  68. package/dist/runner/simpleProcess.d.ts +3 -0
  69. package/dist/runner/simpleProcess.js +50 -36
  70. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  71. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  72. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
  73. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  74. package/dist/runner/taskStatusStore.d.ts +11 -0
  75. package/dist/runner/taskStatusStore.js +24 -0
  76. package/dist/runner/watch.d.ts +14 -1
  77. package/dist/runner/watch.js +256 -48
  78. package/dist/security/contentRedaction.js +2 -2
  79. package/dist/security/discoveryTokenStore.d.ts +3 -2
  80. package/dist/security/discoveryTokenStore.js +17 -2
  81. package/dist/security/loopbackHost.d.ts +2 -0
  82. package/dist/security/loopbackHost.js +16 -0
  83. package/dist/security/pathGuard.js +32 -17
  84. package/dist/security/planGuard.js +2 -1
  85. package/dist/security/runtimeGuard.js +9 -6
  86. package/dist/security/secretComparison.d.ts +3 -0
  87. package/dist/security/secretComparison.js +11 -0
  88. package/dist/security/sensitiveGuard.d.ts +5 -0
  89. package/dist/security/sensitiveGuard.js +27 -8
  90. package/dist/security/toolInvocationGuard.d.ts +2 -2
  91. package/dist/security/toolInvocationGuard.js +1 -1
  92. package/dist/security/workspaceRootGuard.d.ts +8 -0
  93. package/dist/security/workspaceRootGuard.js +47 -0
  94. package/dist/smoke-test.js +24 -23
  95. package/dist/tools/catalog/index.d.ts +4 -0
  96. package/dist/tools/catalog/index.js +4 -0
  97. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  98. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  99. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +6 -15
  100. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  101. package/dist/tools/definitions/toolDefs.js +1177 -0
  102. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  103. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  104. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +1 -1
  105. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +99 -29
  106. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
  107. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +7 -7
  108. package/dist/tools/diagnostics/index.d.ts +6 -0
  109. package/dist/tools/diagnostics/index.js +6 -0
  110. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  111. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  112. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +12 -8
  113. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +23 -13
  114. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  115. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  116. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +6 -6
  117. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +1 -1
  118. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  119. package/dist/tools/direct/index.d.ts +3 -0
  120. package/dist/tools/direct/index.js +3 -0
  121. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  122. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  123. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  124. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  125. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  126. package/dist/tools/discovery/index.d.ts +3 -0
  127. package/dist/tools/discovery/index.js +3 -0
  128. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  129. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  130. package/dist/tools/dispatch/coreDispatch.js +32 -31
  131. package/dist/tools/dispatch/diagnosticDispatch.d.ts +3 -5
  132. package/dist/tools/dispatch/diagnosticDispatch.js +70 -68
  133. package/dist/tools/dispatch/directDispatch.js +11 -10
  134. package/dist/tools/dispatch/goalDispatch.js +5 -4
  135. package/dist/tools/dispatch/releaseDispatch.js +3 -2
  136. package/dist/tools/dispatch/validation.d.ts +7 -0
  137. package/dist/tools/dispatch/validation.js +71 -0
  138. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  139. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +1 -1
  140. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  141. package/dist/tools/goals/index.d.ts +3 -0
  142. package/dist/tools/goals/index.js +3 -0
  143. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  144. package/dist/tools/registry.d.ts +7 -11
  145. package/dist/tools/registry.js +17 -1178
  146. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  147. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  148. package/dist/tools/release/index.d.ts +2 -0
  149. package/dist/tools/release/index.js +2 -0
  150. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  151. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  152. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  153. package/dist/tools/tasks/cancelTask.js +85 -0
  154. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +10 -2
  155. package/dist/tools/{createTask.js → tasks/createTask.js} +42 -27
  156. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  157. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
  158. package/dist/tools/{evidencePack.js → tasks/evidencePack.js} +34 -22
  159. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  160. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  161. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
  162. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  163. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
  164. package/dist/tools/tasks/index.d.ts +18 -0
  165. package/dist/tools/tasks/index.js +18 -0
  166. package/dist/tools/tasks/killTask.d.ts +1 -0
  167. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  168. package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
  169. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
  170. package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
  171. package/dist/tools/tasks/retryTask.js +60 -0
  172. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  173. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +58 -30
  174. package/dist/tools/tasks/runVerification.js +38 -0
  175. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  176. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  177. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  178. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  179. package/dist/tools/workspace/applyPatch.js +53 -0
  180. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  181. package/dist/tools/workspace/index.d.ts +10 -0
  182. package/dist/tools/workspace/index.js +10 -0
  183. package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
  184. package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
  185. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  186. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  187. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  188. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  189. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  190. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  191. package/dist/tools/workspace/syncFile.js +134 -0
  192. package/dist/utils/atomicFile.d.ts +7 -0
  193. package/dist/utils/atomicFile.js +48 -0
  194. package/dist/utils/boundedFile.d.ts +8 -0
  195. package/dist/utils/boundedFile.js +93 -0
  196. package/dist/utils/lockedJsonFile.d.ts +15 -0
  197. package/dist/utils/lockedJsonFile.js +224 -0
  198. package/dist/utils/platform.d.ts +11 -0
  199. package/dist/utils/platform.js +26 -0
  200. package/dist/utils/stableJson.d.ts +2 -0
  201. package/dist/utils/stableJson.js +12 -0
  202. package/dist/version.d.ts +3 -2
  203. package/dist/version.js +3 -2
  204. package/dist/watcherStatus.d.ts +2 -2
  205. package/dist/watcherStatus.js +18 -0
  206. package/docs/CODE_WIKI.md +808 -143
  207. package/docs/architecture.md +15 -55
  208. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  209. package/docs/control-center/README.md +4 -4
  210. package/docs/dashboard-overview.md +4 -4
  211. package/docs/desktop-app.md +175 -0
  212. package/docs/evidence-pack-schema.md +3 -3
  213. package/docs/open-source-application.md +17 -11
  214. package/docs/release-checklist.md +1 -1
  215. package/docs/release-evidence.md +39 -29
  216. package/docs/task-safe-review-workflow.md +5 -5
  217. package/docs/threat-model.md +19 -1
  218. package/examples/config.example.json +4 -2
  219. package/package.json +27 -10
  220. package/scripts/README.md +15 -6
  221. package/scripts/build.js +11 -0
  222. package/scripts/checks/brand-check.js +3 -0
  223. package/scripts/checks/build-output-check.js +37 -0
  224. package/scripts/checks/control-center-smoke.js +91 -6
  225. package/scripts/checks/control-smoke.js +19 -8
  226. package/scripts/checks/http-mcp-smoke.js +82 -4
  227. package/scripts/checks/lifecycle-smoke.js +10 -10
  228. package/scripts/checks/mcp-manifest-check.js +1 -1
  229. package/scripts/checks/mcp-smoke.js +27 -3
  230. package/scripts/checks/package-manifest-check.js +20 -7
  231. package/scripts/checks/unit-tests.js +3 -5
  232. package/scripts/checks/watcher-supervisor-smoke.js +9 -0
  233. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  234. package/scripts/control/manage-patchwarden.ps1 +69 -4
  235. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  236. package/scripts/control/run-background-supervisor.ps1 +41 -0
  237. package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
  238. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  239. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  240. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  241. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  242. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  243. package/scripts/lib/clean-generated-output.js +11 -0
  244. package/scripts/lib/file-discovery.js +12 -0
  245. package/scripts/release/desktop-preflight.js +211 -0
  246. package/scripts/release/pack-clean.js +59 -14
  247. package/src/assessments/agentAssessor.ts +26 -12
  248. package/src/assessments/assessmentStore.ts +52 -40
  249. package/src/config.ts +70 -13
  250. package/src/control/fileManager.ts +40 -0
  251. package/src/control/middleware/auth.ts +22 -4
  252. package/src/control/middleware/static.ts +16 -3
  253. package/src/control/routeTable.ts +301 -0
  254. package/src/control/routes/audit.ts +14 -6
  255. package/src/control/routes/evidence.ts +1 -1
  256. package/src/control/routes/lineage.ts +1 -1
  257. package/src/control/routes/process.ts +341 -40
  258. package/src/control/routes/sessions.ts +88 -25
  259. package/src/control/routes/status.ts +97 -17
  260. package/src/control/routes/taskActions.ts +24 -58
  261. package/src/control/routes/tasks.ts +66 -58
  262. package/src/control/routes/workspace.ts +56 -25
  263. package/src/control/runtime.ts +69 -33
  264. package/src/control/server.ts +36 -342
  265. package/src/control/shared.ts +77 -11
  266. package/src/direct/directAudit.ts +17 -9
  267. package/src/direct/directGuards.ts +72 -44
  268. package/src/direct/directPatch.ts +95 -8
  269. package/src/direct/directSessionStore.ts +211 -55
  270. package/src/direct/directVerification.ts +9 -23
  271. package/src/doctor.ts +73 -59
  272. package/src/goal/acceptanceEngine.ts +1 -1
  273. package/src/goal/goalProgress.ts +17 -13
  274. package/src/goal/goalReport.ts +5 -13
  275. package/src/goal/goalStatus.ts +19 -3
  276. package/src/goal/goalStore.ts +241 -54
  277. package/src/goal/handoffExport.ts +6 -6
  278. package/src/goal/specKitImport.ts +12 -12
  279. package/src/goal/subgoalSync.ts +11 -8
  280. package/src/goal/worktreeManager.ts +139 -22
  281. package/src/httpServer.ts +113 -30
  282. package/src/logging.ts +10 -3
  283. package/src/policy/projectPolicy.ts +5 -2
  284. package/src/release/releaseGate.ts +74 -27
  285. package/src/runner/agentInvocation.ts +126 -4
  286. package/src/runner/changeCapture.ts +291 -57
  287. package/src/runner/postTaskCleanup.ts +41 -17
  288. package/src/runner/processSecurity.ts +433 -0
  289. package/src/runner/runTask.ts +138 -91
  290. package/src/runner/simpleProcess.ts +66 -31
  291. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  292. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
  293. package/src/runner/taskStatusStore.ts +54 -0
  294. package/src/runner/watch.ts +281 -51
  295. package/src/security/contentRedaction.ts +2 -2
  296. package/src/security/discoveryTokenStore.ts +18 -4
  297. package/src/security/loopbackHost.ts +19 -0
  298. package/src/security/pathGuard.ts +35 -16
  299. package/src/security/planGuard.ts +2 -1
  300. package/src/security/runtimeGuard.ts +9 -6
  301. package/src/security/secretComparison.ts +13 -0
  302. package/src/security/sensitiveGuard.ts +27 -8
  303. package/src/security/toolInvocationGuard.ts +2 -2
  304. package/src/security/workspaceRootGuard.ts +53 -0
  305. package/src/smoke-test.ts +25 -24
  306. package/src/tools/catalog/index.ts +4 -0
  307. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  308. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +7 -16
  309. package/src/tools/definitions/toolDefs.ts +1264 -0
  310. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  311. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +99 -32
  312. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +8 -8
  313. package/src/tools/diagnostics/index.ts +6 -0
  314. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  315. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +27 -17
  316. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  317. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +6 -6
  318. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +7 -5
  319. package/src/tools/direct/index.ts +3 -0
  320. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  321. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  322. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  323. package/src/tools/discovery/index.ts +3 -0
  324. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  325. package/src/tools/dispatch/coreDispatch.ts +36 -31
  326. package/src/tools/dispatch/diagnosticDispatch.ts +26 -21
  327. package/src/tools/dispatch/directDispatch.ts +11 -10
  328. package/src/tools/dispatch/goalDispatch.ts +5 -4
  329. package/src/tools/dispatch/releaseDispatch.ts +3 -2
  330. package/src/tools/dispatch/validation.ts +78 -0
  331. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  332. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +41 -26
  333. package/src/tools/goals/index.ts +3 -0
  334. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  335. package/src/tools/registry.ts +17 -1261
  336. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  337. package/src/tools/release/index.ts +2 -0
  338. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  339. package/src/tools/tasks/cancelTask.ts +109 -0
  340. package/src/tools/{createTask.ts → tasks/createTask.ts} +49 -26
  341. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
  342. package/src/tools/{evidencePack.ts → tasks/evidencePack.ts} +36 -22
  343. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  344. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
  345. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  346. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
  347. package/src/tools/tasks/index.ts +18 -0
  348. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
  349. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
  350. package/src/tools/tasks/retryTask.ts +69 -0
  351. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +69 -34
  352. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  353. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  354. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  355. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  356. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  357. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  358. package/src/tools/workspace/index.ts +10 -0
  359. package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
  360. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  361. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  362. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  363. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  364. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  365. package/src/tools/workspace/syncFile.ts +292 -0
  366. package/src/utils/atomicFile.ts +58 -0
  367. package/src/utils/boundedFile.ts +116 -0
  368. package/src/utils/lockedJsonFile.ts +251 -0
  369. package/src/utils/platform.ts +31 -0
  370. package/src/utils/stableJson.ts +12 -0
  371. package/src/version.ts +3 -2
  372. package/src/watcherStatus.ts +19 -1
  373. package/ui/desktop-bootstrap.js +11 -0
  374. package/ui/desktop-bridge.js +36 -0
  375. package/ui/desktop.css +160 -0
  376. package/ui/getting-started.js +93 -0
  377. package/ui/i18n.js +484 -0
  378. package/ui/log-parser.js +30 -0
  379. package/ui/pages/audit.html +12 -0
  380. package/ui/pages/dashboard.html +90 -147
  381. package/ui/pages/direct-sessions.html +123 -26
  382. package/ui/pages/getting-started.html +43 -0
  383. package/ui/pages/logs.html +105 -22
  384. package/ui/pages/settings.html +101 -0
  385. package/ui/pages/task-detail.html +12 -0
  386. package/ui/pages/tasks.html +54 -8
  387. package/ui/pages/workspace.html +38 -1
  388. package/ui/settings.js +245 -0
  389. package/PatchWarden-Control.cmd +0 -6
  390. package/Restart-PatchWarden-Control.cmd +0 -6
  391. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  392. package/dist/test/unit/acceptance-engine.test.js +0 -228
  393. package/dist/test/unit/agent-router.test.d.ts +0 -1
  394. package/dist/test/unit/agent-router.test.js +0 -287
  395. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  396. package/dist/test/unit/android-doctor.test.js +0 -118
  397. package/dist/test/unit/apply-patch.test.d.ts +0 -1
  398. package/dist/test/unit/apply-patch.test.js +0 -225
  399. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  400. package/dist/test/unit/audit-checks.test.js +0 -350
  401. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  402. package/dist/test/unit/chinese-path.test.js +0 -91
  403. package/dist/test/unit/command-guard.test.d.ts +0 -1
  404. package/dist/test/unit/command-guard.test.js +0 -160
  405. package/dist/test/unit/create-task.test.d.ts +0 -1
  406. package/dist/test/unit/create-task.test.js +0 -197
  407. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  408. package/dist/test/unit/diagnose-task.test.js +0 -457
  409. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  410. package/dist/test/unit/direct-guards.test.js +0 -328
  411. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  412. package/dist/test/unit/discovery-token-store.test.js +0 -139
  413. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  414. package/dist/test/unit/evidence-pack.test.js +0 -224
  415. package/dist/test/unit/get-task-status.test.d.ts +0 -1
  416. package/dist/test/unit/get-task-status.test.js +0 -174
  417. package/dist/test/unit/get-task-summary.test.d.ts +0 -1
  418. package/dist/test/unit/get-task-summary.test.js +0 -146
  419. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  420. package/dist/test/unit/goal-graph.test.js +0 -298
  421. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  422. package/dist/test/unit/goal-progress.test.js +0 -381
  423. package/dist/test/unit/goal-report.test.d.ts +0 -1
  424. package/dist/test/unit/goal-report.test.js +0 -159
  425. package/dist/test/unit/goal-status.test.d.ts +0 -1
  426. package/dist/test/unit/goal-status.test.js +0 -215
  427. package/dist/test/unit/goal-store.test.d.ts +0 -1
  428. package/dist/test/unit/goal-store.test.js +0 -253
  429. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  430. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  431. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  432. package/dist/test/unit/goal-tools-registry.test.js +0 -192
  433. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  434. package/dist/test/unit/handoff-export.test.js +0 -263
  435. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  436. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  437. package/dist/test/unit/logging.test.d.ts +0 -1
  438. package/dist/test/unit/logging.test.js +0 -397
  439. package/dist/test/unit/path-guard.test.d.ts +0 -1
  440. package/dist/test/unit/path-guard.test.js +0 -133
  441. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  442. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  443. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  444. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  445. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  446. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  447. package/dist/test/unit/release-gate.test.d.ts +0 -1
  448. package/dist/test/unit/release-gate.test.js +0 -242
  449. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  450. package/dist/test/unit/run-task-loop.test.js +0 -380
  451. package/dist/test/unit/safe-status.test.d.ts +0 -1
  452. package/dist/test/unit/safe-status.test.js +0 -165
  453. package/dist/test/unit/safe-views.test.d.ts +0 -1
  454. package/dist/test/unit/safe-views.test.js +0 -171
  455. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  456. package/dist/test/unit/schema-drift-check.test.js +0 -176
  457. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  458. package/dist/test/unit/sensitive-guard.test.js +0 -104
  459. package/dist/test/unit/spec-kit-import.test.d.ts +0 -1
  460. package/dist/test/unit/spec-kit-import.test.js +0 -341
  461. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  462. package/dist/test/unit/subgoal-sync.test.js +0 -183
  463. package/dist/test/unit/sync-file.test.d.ts +0 -1
  464. package/dist/test/unit/sync-file.test.js +0 -154
  465. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  466. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  467. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  468. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  469. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  470. package/dist/test/unit/toolSearch.test.js +0 -571
  471. package/dist/test/unit/wait-for-task.test.d.ts +0 -1
  472. package/dist/test/unit/wait-for-task.test.js +0 -144
  473. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  474. package/dist/test/unit/watcher-status.test.js +0 -169
  475. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  476. package/dist/test/unit/worktree-manager.test.js +0 -176
  477. package/dist/tools/applyPatch.js +0 -41
  478. package/dist/tools/cancelTask.d.ts +0 -30
  479. package/dist/tools/cancelTask.js +0 -64
  480. package/dist/tools/finalizeDirectSession.js +0 -84
  481. package/dist/tools/goalSubgoalTask.js +0 -110
  482. package/dist/tools/killTask.d.ts +0 -15
  483. package/dist/tools/retryTask.js +0 -45
  484. package/dist/tools/runVerification.js +0 -36
  485. package/dist/tools/syncFile.js +0 -65
  486. package/docs/control-center/control-center-daily-driver.md +0 -211
  487. package/docs/control-center/control-center-mvp.md +0 -205
  488. package/docs/control-center/control-center-phase2.md +0 -159
  489. package/docs/execution-plan-2026-07-09.md +0 -315
  490. package/docs/release-v0.6.0.md +0 -71
  491. package/docs/release-v0.6.1.md +0 -75
  492. package/docs/release-v0.6.4.md +0 -45
  493. package/docs/roadmap-execution-and-acceptance.md +0 -365
  494. package/src/test/unit/acceptance-engine.test.ts +0 -261
  495. package/src/test/unit/agent-router.test.ts +0 -342
  496. package/src/test/unit/android-doctor.test.ts +0 -158
  497. package/src/test/unit/apply-patch.test.ts +0 -293
  498. package/src/test/unit/audit-checks.test.ts +0 -567
  499. package/src/test/unit/chinese-path.test.ts +0 -106
  500. package/src/test/unit/command-guard.test.ts +0 -221
  501. package/src/test/unit/create-task.test.ts +0 -255
  502. package/src/test/unit/diagnose-task.test.ts +0 -544
  503. package/src/test/unit/direct-guards.test.ts +0 -467
  504. package/src/test/unit/discovery-token-store.test.ts +0 -181
  505. package/src/test/unit/evidence-pack.test.ts +0 -251
  506. package/src/test/unit/get-task-status.test.ts +0 -203
  507. package/src/test/unit/get-task-summary.test.ts +0 -173
  508. package/src/test/unit/goal-graph.test.ts +0 -347
  509. package/src/test/unit/goal-progress.test.ts +0 -538
  510. package/src/test/unit/goal-report.test.ts +0 -189
  511. package/src/test/unit/goal-status.test.ts +0 -270
  512. package/src/test/unit/goal-store.test.ts +0 -318
  513. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  514. package/src/test/unit/goal-tools-registry.test.ts +0 -245
  515. package/src/test/unit/handoff-export.test.ts +0 -295
  516. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  517. package/src/test/unit/logging.test.ts +0 -497
  518. package/src/test/unit/path-guard.test.ts +0 -180
  519. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  520. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  521. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  522. package/src/test/unit/release-gate.test.ts +0 -314
  523. package/src/test/unit/run-task-loop.test.ts +0 -425
  524. package/src/test/unit/safe-status.test.ts +0 -187
  525. package/src/test/unit/safe-views.test.ts +0 -184
  526. package/src/test/unit/schema-drift-check.test.ts +0 -260
  527. package/src/test/unit/sensitive-guard.test.ts +0 -124
  528. package/src/test/unit/spec-kit-import.test.ts +0 -429
  529. package/src/test/unit/subgoal-sync.test.ts +0 -236
  530. package/src/test/unit/sync-file.test.ts +0 -231
  531. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  532. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  533. package/src/test/unit/toolSearch.test.ts +0 -756
  534. package/src/test/unit/wait-for-task.test.ts +0 -176
  535. package/src/test/unit/watcher-status.test.ts +0 -190
  536. package/src/test/unit/worktree-manager.test.ts +0 -247
  537. package/src/tools/cancelTask.ts +0 -70
  538. package/src/tools/retryTask.ts +0 -53
  539. package/src/tools/syncFile.ts +0 -122
  540. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  541. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  542. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  543. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  544. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  545. /package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +0 -0
  546. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  547. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  548. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  549. /package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +0 -0
  550. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  551. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  552. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  553. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  554. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  555. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  556. /package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +0 -0
  557. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  558. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  559. /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
  560. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  561. /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
  562. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  563. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  564. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  565. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  566. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  567. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  568. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  569. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  570. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  571. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  572. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
  573. /package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +0 -0
@@ -1,197 +0,0 @@
1
- import { describe, it, beforeEach, afterEach } from "node:test";
2
- import { strict as assert } from "node:assert";
3
- import { mkdtempSync, rmSync, mkdirSync, writeFileSync, existsSync, readFileSync, readdirSync, } from "node:fs";
4
- import { join, dirname } from "node:path";
5
- import { tmpdir } from "node:os";
6
- import { reloadConfig } from "../../config.js";
7
- import { createTask } from "../../tools/createTask.js";
8
- import { savePlan } from "../../tools/savePlan.js";
9
- import { PatchWardenError } from "../../errors.js";
10
- // ── Helpers ───────────────────────────────────────────────────────
11
- let tempDir;
12
- let prevConfigEnv;
13
- function writeConfig(workspaceRoot) {
14
- const configPath = join(tempDir, "patchwarden.config.json");
15
- writeFileSync(configPath, JSON.stringify({
16
- workspaceRoot,
17
- tasksDir: ".patchwarden/tasks",
18
- plansDir: ".patchwarden/plans",
19
- assessmentsDir: ".patchwarden/assessments",
20
- directSessionsDir: ".patchwarden/direct-sessions",
21
- agents: {
22
- codex: { command: "codex", args: ["exec", "{prompt}"] },
23
- },
24
- allowedTestCommands: ["npm test", "npm run build"],
25
- defaultTaskTimeoutSeconds: 60,
26
- maxTaskTimeoutSeconds: 300,
27
- watcherStaleSeconds: 30,
28
- }), "utf-8");
29
- prevConfigEnv = process.env.PATCHWARDEN_CONFIG;
30
- process.env.PATCHWARDEN_CONFIG = configPath;
31
- reloadConfig();
32
- }
33
- /** Write a fresh watcher heartbeat so readWatcherStatus reports healthy. */
34
- function writeFreshHeartbeat(workspaceRoot) {
35
- const heartbeatPath = join(workspaceRoot, ".patchwarden", "watcher-heartbeat.json");
36
- mkdirSync(dirname(heartbeatPath), { recursive: true });
37
- writeFileSync(heartbeatPath, JSON.stringify({
38
- status: "running",
39
- pid: process.pid,
40
- last_heartbeat_at: new Date().toISOString(),
41
- instance_id: "test-instance",
42
- }), "utf-8");
43
- }
44
- describe("createTask", () => {
45
- beforeEach(() => {
46
- tempDir = mkdtempSync(join(tmpdir(), "pw-createtask-"));
47
- });
48
- afterEach(() => {
49
- if (prevConfigEnv === undefined)
50
- delete process.env.PATCHWARDEN_CONFIG;
51
- else
52
- process.env.PATCHWARDEN_CONFIG = prevConfigEnv;
53
- reloadConfig();
54
- rmSync(tempDir, { recursive: true, force: true });
55
- });
56
- // ── 1. inline_plan source ──────────────────────────────────────
57
- it("creates a task from inline_plan, writes status.json, and allocates a task directory", async () => {
58
- writeConfig(tempDir);
59
- mkdirSync(join(tempDir, "my-repo"), { recursive: true });
60
- writeFreshHeartbeat(tempDir);
61
- const result = await createTask({
62
- inline_plan: "## Goal\nAdd a hello-world print statement to main.ts.",
63
- agent: "codex",
64
- repo_path: "my-repo",
65
- });
66
- const out = result;
67
- assert.ok(out.task_id.startsWith("task_"), `task_id should start with "task_", got: ${out.task_id}`);
68
- assert.equal(out.status, "pending");
69
- assert.equal(out.plan_source, "inline");
70
- assert.equal(out.agent, "codex");
71
- assert.equal(out.execution_blocked, false);
72
- assert.ok(existsSync(out.path), "task directory should exist");
73
- assert.ok(existsSync(join(out.path, "status.json")), "status.json should exist");
74
- const status = JSON.parse(readFileSync(join(out.path, "status.json"), "utf-8"));
75
- assert.equal(status.task_id, out.task_id);
76
- assert.equal(status.status, "pending");
77
- assert.equal(status.phase, "queued");
78
- assert.equal(status.agent, "codex");
79
- assert.equal(status.plan_source, "inline");
80
- assert.equal(status.repo_path, "my-repo");
81
- });
82
- // ── 2. template source ─────────────────────────────────────────
83
- it("creates a task from a built-in template (feature_small)", async () => {
84
- writeConfig(tempDir);
85
- mkdirSync(join(tempDir, "my-repo"), { recursive: true });
86
- writeFreshHeartbeat(tempDir);
87
- const result = await createTask({
88
- template: "feature_small",
89
- goal: "Add a utility function for string truncation.",
90
- agent: "codex",
91
- repo_path: "my-repo",
92
- });
93
- const out = result;
94
- assert.ok(out.task_id.startsWith("task_"));
95
- assert.equal(out.plan_source, "template");
96
- assert.equal(out.template, "feature_small");
97
- assert.ok(existsSync(join(out.path, "status.json")));
98
- const status = JSON.parse(readFileSync(join(out.path, "status.json"), "utf-8"));
99
- assert.equal(status.template, "feature_small");
100
- assert.equal(status.plan_source, "template");
101
- });
102
- // ── 3. plan_id source ──────────────────────────────────────────
103
- it("creates a task from a previously saved plan_id", async () => {
104
- writeConfig(tempDir);
105
- mkdirSync(join(tempDir, "my-repo"), { recursive: true });
106
- writeFreshHeartbeat(tempDir);
107
- const saved = savePlan({
108
- title: "Pre-saved plan",
109
- content: "## Goal\nDo something safe and repo-local.",
110
- });
111
- const result = await createTask({
112
- plan_id: saved.plan_id,
113
- agent: "codex",
114
- repo_path: "my-repo",
115
- });
116
- const out = result;
117
- assert.equal(out.plan_id, saved.plan_id);
118
- assert.equal(out.plan_source, "saved");
119
- assert.ok(existsSync(join(out.path, "status.json")));
120
- const status = JSON.parse(readFileSync(join(out.path, "status.json"), "utf-8"));
121
- assert.equal(status.plan_id, saved.plan_id);
122
- assert.equal(status.plan_source, "saved");
123
- });
124
- // ── 4. assess_only mode ────────────────────────────────────────
125
- it("returns a risk assessment instead of creating a task in assess_only mode", async () => {
126
- writeConfig(tempDir);
127
- mkdirSync(join(tempDir, "my-repo"), { recursive: true });
128
- const result = await createTask({
129
- inline_plan: "## Goal\nAdd a print statement to main.ts.",
130
- agent: "codex",
131
- repo_path: "my-repo",
132
- execution_mode: "assess_only",
133
- });
134
- const out = result;
135
- assert.ok(out.assessment_id, "assessment_id should be present");
136
- assert.ok(out.assessment_short_id, "assessment_short_id should be present");
137
- assert.equal(out.decision, "allow");
138
- assert.equal(out.risk_level, "low");
139
- assert.ok(out.next_tool_call, "next_tool_call should be present for allow decision");
140
- assert.equal(out.next_tool_call.name, "create_task");
141
- assert.equal(out.next_tool_call.arguments.execution_mode, "execute");
142
- assert.equal(out.next_tool_call.arguments.assessment_id, out.assessment_id);
143
- // No task directory should be created in assess_only mode
144
- assert.equal("task_id" in out, false, "assess_only output should not contain task_id");
145
- const tasksDir = join(tempDir, ".patchwarden", "tasks");
146
- if (existsSync(tasksDir)) {
147
- const taskEntries = readdirSync(tasksDir).filter((name) => name.startsWith("task_"));
148
- assert.equal(taskEntries.length, 0, "no task directory should be created in assess_only mode");
149
- }
150
- });
151
- // ── 5. agent not configured ────────────────────────────────────
152
- it("throws PatchWardenError (agent_not_configured) when the agent is not registered", async () => {
153
- writeConfig(tempDir);
154
- mkdirSync(join(tempDir, "my-repo"), { recursive: true });
155
- await assert.rejects(() => createTask({
156
- inline_plan: "## Goal\nDo something.",
157
- agent: "ghost-agent",
158
- repo_path: "my-repo",
159
- }), (err) => {
160
- assert.ok(err instanceof PatchWardenError, "should throw PatchWardenError");
161
- assert.equal(err.reason, "agent_not_configured");
162
- return true;
163
- });
164
- });
165
- // ── 6. repo_path escapes workspace ─────────────────────────────
166
- it("throws workspace_path_escape when repo_path is outside workspaceRoot", async () => {
167
- writeConfig(tempDir);
168
- mkdirSync(join(tempDir, "my-repo"), { recursive: true });
169
- await assert.rejects(() => createTask({
170
- inline_plan: "## Goal\nDo something.",
171
- agent: "codex",
172
- repo_path: "../outside-workspace",
173
- }), (err) => {
174
- assert.ok(err instanceof PatchWardenError, "should throw PatchWardenError");
175
- assert.equal(err.reason, "workspace_path_escape");
176
- return true;
177
- });
178
- });
179
- // ── 7. watcher not running ─────────────────────────────────────
180
- it("returns execution_blocked: true when watcher is not running", async () => {
181
- writeConfig(tempDir);
182
- mkdirSync(join(tempDir, "my-repo"), { recursive: true });
183
- // No watcher heartbeat file → watcher status is "missing"
184
- const result = await createTask({
185
- inline_plan: "## Goal\nDo something.",
186
- agent: "codex",
187
- repo_path: "my-repo",
188
- });
189
- const out = result;
190
- assert.equal(out.execution_blocked, true);
191
- assert.equal(out.watcher.available, false);
192
- assert.equal(out.watcher.status, "missing");
193
- assert.equal(out.next_tool_call.name, "health_check");
194
- // Task is still created (queued) despite being blocked
195
- assert.ok(existsSync(join(out.path, "status.json")));
196
- });
197
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,457 +0,0 @@
1
- import { describe, it, beforeEach, afterEach } from "node:test";
2
- import { strict as assert } from "node:assert";
3
- import { mkdtempSync, rmSync, mkdirSync, writeFileSync, utimesSync, existsSync, } from "node:fs";
4
- import { join, dirname } from "node:path";
5
- import { tmpdir } from "node:os";
6
- import { diagnoseTask } from "../../tools/diagnoseTask.js";
7
- // ── Test fixtures ──────────────────────────────────────────────────
8
- function makeConfig(workspaceRoot) {
9
- return {
10
- workspaceRoot,
11
- plansDir: ".patchwarden/plans",
12
- tasksDir: ".patchwarden/tasks",
13
- assessmentsDir: ".patchwarden/assessments",
14
- assessmentTtlSeconds: 3600,
15
- agents: { codex: { command: "codex", args: ["exec", "{prompt}"] } },
16
- allowedTestCommands: ["npm test"],
17
- repoAllowedTestCommands: {},
18
- maxReadFileBytes: 200_000,
19
- defaultTaskTimeoutSeconds: 900,
20
- maxTaskTimeoutSeconds: 3600,
21
- watcherStaleSeconds: 30,
22
- directSessionsDir: ".patchwarden/direct-sessions",
23
- directSessionTtlSeconds: 3600,
24
- directMaxPatchBytes: 200_000,
25
- directMaxFileBytes: 500_000,
26
- };
27
- }
28
- /** A PID that is guaranteed to be alive (the test process itself). */
29
- const ALIVE_PID = process.pid;
30
- /** A PID that is guaranteed to not exist (well above typical PID max). */
31
- const DEAD_PID = 999999;
32
- function isoSecondsAgo(seconds) {
33
- return new Date(Date.now() - seconds * 1000).toISOString();
34
- }
35
- /** Set a file's mtime to N seconds ago so diagnose_task sees it as stale or fresh. */
36
- function ageFile(filePath, secondsAgo) {
37
- if (!existsSync(filePath))
38
- return;
39
- const targetMs = Date.now() - secondsAgo * 1000;
40
- const targetSec = targetMs / 1000;
41
- utimesSync(filePath, targetSec, targetSec);
42
- }
43
- describe("diagnoseTask", () => {
44
- let tempDir;
45
- let config;
46
- let tasksDir;
47
- let watcherHeartbeatPath;
48
- beforeEach(() => {
49
- tempDir = mkdtempSync(join(tmpdir(), "pw-diagnose-"));
50
- config = makeConfig(tempDir);
51
- tasksDir = join(tempDir, ".patchwarden", "tasks");
52
- mkdirSync(tasksDir, { recursive: true });
53
- watcherHeartbeatPath = join(dirname(tasksDir), "watcher-heartbeat.json");
54
- });
55
- afterEach(() => {
56
- rmSync(tempDir, { recursive: true, force: true });
57
- });
58
- /**
59
- * Build a task fixture with status.json, runtime.json, and optional log
60
- * files with controlled mtimes.
61
- */
62
- function buildTask(opts) {
63
- const taskId = opts.taskId;
64
- const taskDir = join(tasksDir, taskId);
65
- mkdirSync(taskDir, { recursive: true });
66
- const statusSecondsAgo = opts.createdSecondsAgo ?? 600;
67
- const status = {
68
- task_id: taskId,
69
- status: opts.status ?? "running",
70
- phase: opts.phase ?? "executing_agent",
71
- created_at: isoSecondsAgo(statusSecondsAgo),
72
- updated_at: isoSecondsAgo(statusSecondsAgo),
73
- };
74
- if (opts.heartbeatSecondsAgo !== undefined) {
75
- status.last_heartbeat_at = isoSecondsAgo(opts.heartbeatSecondsAgo);
76
- }
77
- writeFileSync(join(taskDir, "status.json"), JSON.stringify(status, null, 2), "utf-8");
78
- const runtime = {
79
- phase: opts.phase ?? "executing_agent",
80
- };
81
- if (opts.heartbeatSecondsAgo !== undefined) {
82
- runtime.last_heartbeat_at = isoSecondsAgo(opts.heartbeatSecondsAgo);
83
- }
84
- if (opts.childPid !== undefined) {
85
- runtime.child_pid = opts.childPid;
86
- }
87
- if (opts.childStartedSecondsAgo !== undefined) {
88
- runtime.child_started_at = isoSecondsAgo(opts.childStartedSecondsAgo);
89
- }
90
- if (opts.watcherInstanceId !== undefined) {
91
- runtime.watcher_instance_id = opts.watcherInstanceId;
92
- }
93
- if (opts.taskStartedSecondsAgo !== undefined) {
94
- runtime.task_started_at = isoSecondsAgo(opts.taskStartedSecondsAgo);
95
- }
96
- runtime.current_command = null;
97
- writeFileSync(join(taskDir, "runtime.json"), JSON.stringify(runtime, null, 2), "utf-8");
98
- if (opts.stdoutContent !== undefined) {
99
- const stdoutPath = join(taskDir, "stdout.log");
100
- writeFileSync(stdoutPath, opts.stdoutContent, "utf-8");
101
- if (opts.stdoutSecondsAgo !== undefined)
102
- ageFile(stdoutPath, opts.stdoutSecondsAgo);
103
- }
104
- if (opts.stderrContent !== undefined) {
105
- const stderrPath = join(taskDir, "stderr.log");
106
- writeFileSync(stderrPath, opts.stderrContent, "utf-8");
107
- if (opts.stderrSecondsAgo !== undefined)
108
- ageFile(stderrPath, opts.stderrSecondsAgo);
109
- }
110
- if (opts.resultMd) {
111
- writeFileSync(join(taskDir, "result.md"), "# Result\nTask completed.", "utf-8");
112
- }
113
- if (opts.testLog) {
114
- writeFileSync(join(taskDir, "test.log"), "TAP version 13\n1..1\nok 1 test", "utf-8");
115
- }
116
- if (opts.gitDiff) {
117
- writeFileSync(join(taskDir, "git.diff"), "diff --git a/foo b/foo\n", "utf-8");
118
- }
119
- if (opts.artifactManifest) {
120
- writeFileSync(join(taskDir, "artifact_manifest.json"), "{}", "utf-8");
121
- }
122
- return taskDir;
123
- }
124
- /** Write a watcher heartbeat with the given instance_id and freshness. */
125
- function writeWatcherHeartbeat(instanceId, secondsAgo = 5) {
126
- const heartbeat = {
127
- last_heartbeat_at: isoSecondsAgo(secondsAgo),
128
- pid: process.pid,
129
- instance_id: instanceId,
130
- launcher_pid: process.ppid ?? null,
131
- };
132
- writeFileSync(watcherHeartbeatPath, JSON.stringify(heartbeat, null, 2), "utf-8");
133
- }
134
- // ── 1. active_running: fresh heartbeat, watcher owns, logs growing ──
135
- it("diagnoses active_running when heartbeat fresh, watcher owns, logs growing", () => {
136
- buildTask({
137
- taskId: "task-active-001",
138
- status: "running",
139
- phase: "executing_agent",
140
- heartbeatSecondsAgo: 10, // fresh (< 120s)
141
- childPid: ALIVE_PID, // alive
142
- watcherInstanceId: "watcher-A", // matches watcher
143
- taskStartedSecondsAgo: 60,
144
- stdoutContent: "Running...\n",
145
- stdoutSecondsAgo: 5, // logs growing (< 120s)
146
- stderrContent: "stderr\n",
147
- stderrSecondsAgo: 5,
148
- });
149
- writeWatcherHeartbeat("watcher-A", 3);
150
- const result = diagnoseTask({ task_id: "task-active-001" }, config);
151
- assert.equal(result.diagnosis, "active_running");
152
- assert.equal(result.confidence, "high");
153
- assert.ok(result.safe_actions.includes("leave_unchanged"));
154
- assert.equal(result.evidence.watcher_owns_task, true);
155
- assert.equal(result.evidence.child_pid_alive, true);
156
- assert.equal(result.evidence.pid_reuse_suspected, false);
157
- });
158
- // ── 2. stale_running: heartbeat stale, PID dead, no watcher ownership, logs stale ──
159
- //
160
- // This task was started directly (e.g., via run_task MCP tool) and has no
161
- // watcher_instance_id in runtime, so isWatcherOwningTask returns
162
- // "no_runtime_record" — which does NOT trigger orphaned_running. With
163
- // heartbeat/PID/logs all stale, it falls through to stale_running.
164
- it("diagnoses stale_running when heartbeat stale, PID dead, no watcher record, logs stale", () => {
165
- buildTask({
166
- taskId: "task-stale-001",
167
- status: "running",
168
- phase: "executing_agent",
169
- heartbeatSecondsAgo: 600, // stale (> 300s)
170
- childPid: DEAD_PID, // dead
171
- // No watcherInstanceId — simulates task started directly via run_task
172
- taskStartedSecondsAgo: 700,
173
- stdoutContent: "old output\n",
174
- stdoutSecondsAgo: 600, // stale (> 300s)
175
- stderrContent: "old stderr\n",
176
- stderrSecondsAgo: 600,
177
- });
178
- const result = diagnoseTask({ task_id: "task-stale-001" }, config);
179
- assert.equal(result.diagnosis, "stale_running");
180
- assert.equal(result.confidence, "high");
181
- assert.ok(result.safe_actions.includes("mark_failed_stale"));
182
- assert.equal(result.evidence.child_pid_alive, false);
183
- assert.equal(result.evidence.watcher_owns_task, false);
184
- assert.equal(result.evidence.watcher_ownership_reason, "no_runtime_record");
185
- });
186
- // ── 3. possibly_stale_running: heartbeat possibly stale + logs stale, PID alive ──
187
- //
188
- // heartbeat is between 120-300s (possibly stale), logs are > 300s (stale),
189
- // and PID is alive but pidReuseSuspected is false because heartbeat is not
190
- // stale enough (> 300s required). This falls into possibly_stale_running.
191
- it("diagnoses possibly_stale_running when heartbeat possibly stale and logs stale", () => {
192
- buildTask({
193
- taskId: "task-posstale-001",
194
- status: "running",
195
- phase: "executing_agent",
196
- heartbeatSecondsAgo: 200, // possibly stale (> 120, <= 300)
197
- childPid: ALIVE_PID, // alive
198
- watcherInstanceId: "watcher-A",
199
- taskStartedSecondsAgo: 300,
200
- stdoutContent: "output\n",
201
- stdoutSecondsAgo: 400, // stale (> 300s)
202
- stderrContent: "stderr\n",
203
- stderrSecondsAgo: 400,
204
- });
205
- writeWatcherHeartbeat("watcher-A", 5);
206
- const result = diagnoseTask({ task_id: "task-posstale-001" }, config);
207
- // heartbeatPossiblyStale=true && logsStale=true → possibly_stale_running
208
- assert.equal(result.diagnosis, "possibly_stale_running");
209
- assert.equal(result.confidence, "medium");
210
- assert.ok(result.safe_actions.includes("leave_unchanged"));
211
- assert.equal(result.evidence.pid_reuse_suspected, false);
212
- });
213
- // ── 4. orphaned_running: watcher does not own task (instance mismatch) ──
214
- it("diagnoses orphaned_running when watcher instance_id mismatches", () => {
215
- buildTask({
216
- taskId: "task-orphan-001",
217
- status: "running",
218
- phase: "executing_agent",
219
- heartbeatSecondsAgo: 30, // fresh
220
- childPid: ALIVE_PID,
221
- watcherInstanceId: "watcher-old", // does NOT match current watcher
222
- taskStartedSecondsAgo: 60,
223
- stdoutContent: "output\n",
224
- stdoutSecondsAgo: 10,
225
- stderrContent: "stderr\n",
226
- stderrSecondsAgo: 10,
227
- });
228
- writeWatcherHeartbeat("watcher-new", 5);
229
- const result = diagnoseTask({ task_id: "task-orphan-001" }, config);
230
- assert.equal(result.diagnosis, "orphaned_running");
231
- assert.equal(result.confidence, "high");
232
- assert.ok(result.safe_actions.includes("mark_orphaned"));
233
- assert.equal(result.evidence.watcher_owns_task, false);
234
- assert.equal(result.evidence.watcher_ownership_reason, "instance_mismatch");
235
- });
236
- // ── 5. artifact_collection_stuck: in collecting_artifacts and stuck ──
237
- it("diagnoses artifact_collection_stuck when in collecting phase and signals stale", () => {
238
- buildTask({
239
- taskId: "task-stuckart-001",
240
- status: "collecting_artifacts",
241
- phase: "collecting_artifacts",
242
- heartbeatSecondsAgo: 400, // stale
243
- childPid: DEAD_PID, // dead
244
- watcherInstanceId: "watcher-A",
245
- taskStartedSecondsAgo: 500,
246
- stdoutContent: "agent done\n",
247
- stdoutSecondsAgo: 400,
248
- stderrContent: "stderr\n",
249
- stderrSecondsAgo: 400,
250
- });
251
- writeWatcherHeartbeat("watcher-A", 5);
252
- const result = diagnoseTask({ task_id: "task-stuckart-001" }, config);
253
- assert.equal(result.diagnosis, "artifact_collection_stuck");
254
- assert.equal(result.confidence, "high");
255
- assert.ok(result.safe_actions.includes("recollect_artifacts"));
256
- assert.ok(result.safe_actions.includes("mark_failed_stale"));
257
- assert.equal(result.evidence.collecting_artifacts_phase, true);
258
- });
259
- // ── 6. done_candidate: artifacts exist while status still running ──
260
- it("diagnoses done_candidate with high confidence when result.md + test.log exist", () => {
261
- buildTask({
262
- taskId: "task-donecand-001",
263
- status: "running",
264
- phase: "executing_agent",
265
- heartbeatSecondsAgo: 400,
266
- childPid: DEAD_PID,
267
- watcherInstanceId: "watcher-old",
268
- taskStartedSecondsAgo: 500,
269
- stdoutContent: "done\n",
270
- stdoutSecondsAgo: 400,
271
- stderrContent: "stderr\n",
272
- stderrSecondsAgo: 400,
273
- resultMd: true,
274
- testLog: true,
275
- gitDiff: true,
276
- });
277
- // No watcher heartbeat — orphaned
278
- const result = diagnoseTask({ task_id: "task-donecand-001" }, config);
279
- // done_candidate takes priority over orphaned_running because artifacts exist
280
- assert.equal(result.diagnosis, "done_candidate");
281
- assert.equal(result.confidence, "high");
282
- assert.ok(result.safe_actions.includes("mark_done_by_agent"));
283
- assert.equal(result.evidence.has_result_md, true);
284
- assert.equal(result.evidence.has_test_log, true);
285
- });
286
- it("diagnoses done_candidate with medium confidence when only result.md + collecting phase", () => {
287
- buildTask({
288
- taskId: "task-donecand-002",
289
- status: "collecting_artifacts",
290
- phase: "collecting_artifacts",
291
- heartbeatSecondsAgo: 30, // fresh — not stuck
292
- childPid: ALIVE_PID,
293
- watcherInstanceId: "watcher-A",
294
- taskStartedSecondsAgo: 100,
295
- stdoutContent: "done\n",
296
- stdoutSecondsAgo: 30,
297
- stderrContent: "stderr\n",
298
- stderrSecondsAgo: 30,
299
- resultMd: true,
300
- // No test.log/git.diff → medium signal
301
- });
302
- writeWatcherHeartbeat("watcher-A", 5);
303
- const result = diagnoseTask({ task_id: "task-donecand-002" }, config);
304
- assert.equal(result.diagnosis, "done_candidate");
305
- assert.equal(result.confidence, "medium");
306
- assert.ok(result.safe_actions.includes("mark_done_by_agent"));
307
- assert.ok(result.safe_actions.includes("collect_artifacts"));
308
- });
309
- // ── 7. unknown: insufficient evidence ──
310
- //
311
- // heartbeat is fresh but PID is dead and logs are stale — no single
312
- // branch matches with enough confidence, so it falls through to unknown.
313
- it("diagnoses unknown when signals conflict (fresh heartbeat but dead PID + stale logs)", () => {
314
- buildTask({
315
- taskId: "task-unknown-001",
316
- status: "running",
317
- phase: "executing_agent",
318
- heartbeatSecondsAgo: 10, // fresh — doesn't hit stale paths
319
- childPid: DEAD_PID, // dead — but heartbeat says fresh
320
- watcherInstanceId: "watcher-A",
321
- taskStartedSecondsAgo: 60,
322
- stdoutContent: "running\n",
323
- stdoutSecondsAgo: 400, // stale — but heartbeat says fresh
324
- stderrContent: "stderr\n",
325
- stderrSecondsAgo: 400,
326
- });
327
- writeWatcherHeartbeat("watcher-A", 5);
328
- const result = diagnoseTask({ task_id: "task-unknown-001" }, config);
329
- // Fresh heartbeat means not stale_running / possibly_stale_running.
330
- // Dead PID + stale logs means not active_running.
331
- // Conflicting signals → unknown.
332
- assert.equal(result.diagnosis, "unknown");
333
- assert.equal(result.confidence, "low");
334
- assert.ok(result.safe_actions.includes("leave_unchanged"));
335
- });
336
- // ── 8. PID reuse case: PID alive but everything else stale ──
337
- //
338
- // This is the conservative safety check: even though process.kill(pid, 0)
339
- // returns true, we refuse to call the task "active" because heartbeat and
340
- // logs are all stale. The PID may have been reused by the OS for an
341
- // unrelated process.
342
- it("refuses active_running on PID reuse: PID alive but heartbeat + logs stale", () => {
343
- buildTask({
344
- taskId: "task-pidreuse-001",
345
- status: "running",
346
- phase: "executing_agent",
347
- heartbeatSecondsAgo: 600, // very stale
348
- childPid: ALIVE_PID, // alive — but probably reused!
349
- watcherInstanceId: "watcher-A",
350
- taskStartedSecondsAgo: 700,
351
- stdoutContent: "old output\n",
352
- stdoutSecondsAgo: 600, // very stale
353
- stderrContent: "old stderr\n",
354
- stderrSecondsAgo: 600,
355
- });
356
- writeWatcherHeartbeat("watcher-A", 5);
357
- const result = diagnoseTask({ task_id: "task-pidreuse-001" }, config);
358
- // Must NOT be active_running — that would be the unsafe conclusion.
359
- assert.notEqual(result.diagnosis, "active_running");
360
- assert.equal(result.diagnosis, "possibly_stale_running");
361
- assert.equal(result.confidence, "medium");
362
- assert.equal(result.evidence.pid_reuse_suspected, true);
363
- assert.equal(result.evidence.child_pid_alive, true);
364
- assert.ok(result.reasons.some((r) => r.includes("PID reuse")));
365
- });
366
- // ── 9. terminal: already-terminal statuses return terminal diagnosis ──
367
- it("returns terminal diagnosis for done status", () => {
368
- buildTask({
369
- taskId: "pw-terminal-done",
370
- status: "done",
371
- phase: "completed",
372
- createdSecondsAgo: 1000,
373
- });
374
- const result = diagnoseTask({ task_id: "pw-terminal-done" }, config);
375
- assert.equal(result.diagnosis, "terminal");
376
- assert.equal(result.confidence, "high");
377
- assert.ok(result.safe_actions.includes("leave_unchanged"));
378
- });
379
- it("returns terminal diagnosis for failed_stale status (v0.7.0 status)", () => {
380
- buildTask({
381
- taskId: "pw-terminal-failed-stale",
382
- status: "failed_stale",
383
- phase: "failed_stale",
384
- createdSecondsAgo: 1000,
385
- });
386
- const result = diagnoseTask({ task_id: "pw-terminal-failed-stale" }, config);
387
- assert.equal(result.diagnosis, "terminal");
388
- });
389
- it("returns terminal diagnosis for orphaned status (v0.7.0 status)", () => {
390
- buildTask({
391
- taskId: "pw-terminal-orphaned",
392
- status: "orphaned",
393
- phase: "orphaned",
394
- createdSecondsAgo: 1000,
395
- });
396
- const result = diagnoseTask({ task_id: "pw-terminal-orphaned" }, config);
397
- assert.equal(result.diagnosis, "terminal");
398
- });
399
- it("returns terminal diagnosis for done_by_agent status (v0.7.0 status)", () => {
400
- buildTask({
401
- taskId: "pw-terminal-done-by-agent",
402
- status: "done_by_agent",
403
- phase: "done_by_agent",
404
- createdSecondsAgo: 1000,
405
- });
406
- const result = diagnoseTask({ task_id: "pw-terminal-done-by-agent" }, config);
407
- assert.equal(result.diagnosis, "terminal");
408
- });
409
- // ── include_logs flag ──
410
- it("includes redacted log tails when include_logs=true", () => {
411
- buildTask({
412
- taskId: "task-logs-001",
413
- status: "running",
414
- phase: "executing_agent",
415
- heartbeatSecondsAgo: 10,
416
- childPid: ALIVE_PID,
417
- watcherInstanceId: "watcher-A",
418
- taskStartedSecondsAgo: 60,
419
- stdoutContent: "line1\nline2 with Bearer abcdefghijklmnop123456\n",
420
- stdoutSecondsAgo: 5,
421
- stderrContent: "stderr line\n",
422
- stderrSecondsAgo: 5,
423
- });
424
- writeWatcherHeartbeat("watcher-A", 3);
425
- const result = diagnoseTask({ task_id: "task-logs-001", include_logs: true }, config);
426
- assert.ok(result.logs !== null);
427
- assert.ok(result.logs.stdout_tail !== null);
428
- assert.ok(result.logs.stderr_tail !== null);
429
- // The bearer token must be redacted
430
- assert.ok(!result.logs.stdout_tail.includes("abcdefghijklmnop123456"));
431
- assert.ok(result.logs.stdout_tail.includes("[REDACTED]"));
432
- });
433
- it("omits log tails when include_logs is false (default)", () => {
434
- buildTask({
435
- taskId: "task-nologs-001",
436
- status: "running",
437
- phase: "executing_agent",
438
- heartbeatSecondsAgo: 10,
439
- childPid: ALIVE_PID,
440
- watcherInstanceId: "watcher-A",
441
- taskStartedSecondsAgo: 60,
442
- stdoutContent: "line1\n",
443
- stdoutSecondsAgo: 5,
444
- stderrContent: "stderr\n",
445
- stderrSecondsAgo: 5,
446
- });
447
- writeWatcherHeartbeat("watcher-A", 3);
448
- const result = diagnoseTask({ task_id: "task-nologs-001" }, config);
449
- assert.equal(result.logs, null);
450
- });
451
- // ── Error case: task not found ──
452
- it("throws when task does not exist", () => {
453
- // guardReadPath throws "File not found" before the existsSync check
454
- // produces "Task not found" — both indicate the task doesn't exist.
455
- assert.throws(() => diagnoseTask({ task_id: "non-existent-task" }, config), /(Task not found|File not found)/);
456
- });
457
- });
@@ -1 +0,0 @@
1
- export {};