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
@@ -1,176 +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 } from "node:fs";
4
- import { join } from "node:path";
5
- import { tmpdir } from "node:os";
6
- import { reloadConfig } from "../../config.js";
7
- import { waitForTask } from "../../tools/waitForTask.js";
8
-
9
- let tempDir: string;
10
- let tasksDir: string;
11
- let prevConfigEnv: string | undefined;
12
-
13
- function writeConfig(): void {
14
- const configPath = join(tempDir, "patchwarden.config.json");
15
- writeFileSync(
16
- configPath,
17
- JSON.stringify({
18
- workspaceRoot: tempDir,
19
- tasksDir: ".patchwarden/tasks",
20
- plansDir: ".patchwarden/plans",
21
- assessmentsDir: ".patchwarden/assessments",
22
- directSessionsDir: ".patchwarden/direct-sessions",
23
- agents: { codex: { command: "codex", args: ["exec", "{prompt}"] } },
24
- allowedTestCommands: ["npm test"],
25
- }),
26
- "utf-8"
27
- );
28
- prevConfigEnv = process.env.PATCHWARDEN_CONFIG;
29
- process.env.PATCHWARDEN_CONFIG = configPath;
30
- reloadConfig();
31
- }
32
-
33
- function writeTaskStatus(taskId: string, status: Record<string, unknown>): string {
34
- const taskDir = join(tasksDir, taskId);
35
- mkdirSync(taskDir, { recursive: true });
36
- writeFileSync(join(taskDir, "status.json"), JSON.stringify(status, null, 2), "utf-8");
37
- return taskDir;
38
- }
39
-
40
- function baseStatus(taskId: string, overrides: Record<string, unknown> = {}): Record<string, unknown> {
41
- return {
42
- task_id: taskId,
43
- plan_id: "plan-001",
44
- agent: "codex",
45
- workspace_root: tempDir,
46
- repo_path: "repo",
47
- resolved_repo_path: join(tempDir, "repo"),
48
- created_at: "2026-07-12T10:00:00Z",
49
- started_at: "2026-07-12T10:00:05Z",
50
- finished_at: "2026-07-12T10:05:00Z",
51
- updated_at: "2026-07-12T10:05:00Z",
52
- timeout_seconds: 900,
53
- error: null,
54
- ...overrides,
55
- };
56
- }
57
-
58
- describe("waitForTask", () => {
59
- beforeEach(() => {
60
- tempDir = mkdtempSync(join(tmpdir(), "pw-waitfortask-"));
61
- tasksDir = join(tempDir, ".patchwarden", "tasks");
62
- mkdirSync(tasksDir, { recursive: true });
63
- writeConfig();
64
- });
65
-
66
- afterEach(() => {
67
- if (prevConfigEnv === undefined) delete process.env.PATCHWARDEN_CONFIG;
68
- else process.env.PATCHWARDEN_CONFIG = prevConfigEnv;
69
- reloadConfig();
70
- rmSync(tempDir, { recursive: true, force: true });
71
- });
72
-
73
- // ── already terminal: done ──
74
- it("returns immediately when task is already done", async () => {
75
- writeTaskStatus("task-done-001", baseStatus("task-done-001", {
76
- status: "done",
77
- phase: "completed",
78
- verify_status: "passed",
79
- }));
80
-
81
- const result = await waitForTask("task-done-001", 5);
82
-
83
- assert.equal(result.task_id, "task-done-001");
84
- assert.equal(result.status, "done");
85
- assert.equal(result.terminal, true);
86
- assert.equal(result.timed_out, false);
87
- assert.equal(result.continuation_required, false);
88
- // terminal → includes compact summary, next tool is audit_task
89
- assert.ok(result.summary);
90
- assert.equal(result.next_tool_call.name, "audit_task");
91
- assert.equal(result.progress_summary, undefined);
92
- });
93
-
94
- // ── already terminal: failed ──
95
- it("returns immediately when task is already failed", async () => {
96
- writeTaskStatus("task-failed-001", baseStatus("task-failed-001", {
97
- status: "failed",
98
- phase: "failed",
99
- error: "Agent exited with code 1",
100
- }));
101
-
102
- const result = await waitForTask("task-failed-001", 5);
103
-
104
- assert.equal(result.terminal, true);
105
- assert.equal(result.status, "failed");
106
- assert.equal(result.timed_out, false);
107
- assert.equal(result.continuation_required, false);
108
- assert.ok(result.summary);
109
- assert.equal(result.next_tool_call.name, "audit_task");
110
- });
111
-
112
- // ── running + timeout → continuation_required ──
113
- it("returns continuation_required when running task times out", async () => {
114
- writeTaskStatus("task-running-001", baseStatus("task-running-001", {
115
- status: "running",
116
- phase: "executing_agent",
117
- finished_at: undefined,
118
- }));
119
-
120
- const result = await waitForTask("task-running-001", 1);
121
-
122
- assert.equal(result.task_id, "task-running-001");
123
- assert.equal(result.status, "running");
124
- assert.equal(result.terminal, false);
125
- assert.equal(result.timed_out, true);
126
- assert.equal(result.continuation_required, true);
127
- // non-terminal → includes progress_summary, next tool is wait_for_task
128
- assert.ok(result.progress_summary);
129
- assert.equal(result.next_tool_call.name, "wait_for_task");
130
- assert.equal(result.summary, undefined);
131
- // waited approximately 1 second
132
- assert.ok(result.waited_ms >= 900 && result.waited_ms <= 3000);
133
- });
134
-
135
- // ── canceled (terminal) ──
136
- it("returns terminal result for canceled task", async () => {
137
- writeTaskStatus("task-canceled-001", baseStatus("task-canceled-001", {
138
- status: "canceled",
139
- phase: "canceled",
140
- }));
141
-
142
- const result = await waitForTask("task-canceled-001", 5);
143
-
144
- assert.equal(result.terminal, true);
145
- assert.equal(result.status, "canceled");
146
- assert.equal(result.timed_out, false);
147
- assert.equal(result.continuation_required, false);
148
- assert.ok(result.summary);
149
- assert.equal(result.next_tool_call.name, "audit_task");
150
- });
151
-
152
- // ── task not found ──
153
- it("throws when task does not exist", async () => {
154
- await assert.rejects(
155
- () => waitForTask("non-existent-task", 1),
156
- /(File not found|Task not found)/
157
- );
158
- });
159
-
160
- // ── invalid wait_seconds ──
161
- it("throws when wait_seconds is out of range", async () => {
162
- writeTaskStatus("task-validate-001", baseStatus("task-validate-001", {
163
- status: "done",
164
- phase: "completed",
165
- }));
166
-
167
- await assert.rejects(
168
- () => waitForTask("task-validate-001", 0),
169
- /wait_seconds must be an integer from 1 to 30/
170
- );
171
- await assert.rejects(
172
- () => waitForTask("task-validate-001", 31),
173
- /wait_seconds must be an integer from 1 to 30/
174
- );
175
- });
176
- });
@@ -1,190 +0,0 @@
1
- import { describe, it, beforeEach, afterEach } from "node:test";
2
- import { strict as assert } from "node:assert";
3
- import { mkdirSync, mkdtempSync, rmSync, writeFileSync, existsSync, mkdir } from "node:fs";
4
- import { join, dirname } from "node:path";
5
- import { tmpdir } from "node:os";
6
- import type { PatchWardenConfig } from "../../config.js";
7
- import { readWatcherStatus, getWatcherHeartbeatPath } from "../../watcherStatus.js";
8
-
9
- function makeConfig(workspaceRoot: string): PatchWardenConfig {
10
- return {
11
- workspaceRoot,
12
- plansDir: ".patchwarden/plans",
13
- tasksDir: ".patchwarden/tasks",
14
- assessmentsDir: ".patchwarden/assessments",
15
- assessmentTtlSeconds: 3600,
16
- agents: { codex: { command: "codex", args: ["exec", "{prompt}"] } },
17
- allowedTestCommands: ["npm test"],
18
- repoAllowedTestCommands: {},
19
- maxReadFileBytes: 200_000,
20
- defaultTaskTimeoutSeconds: 900,
21
- maxTaskTimeoutSeconds: 3600,
22
- watcherStaleSeconds: 30,
23
- directSessionsDir: ".patchwarden/direct-sessions",
24
- directSessionTtlSeconds: 3600,
25
- directMaxPatchBytes: 200_000,
26
- directMaxFileBytes: 500_000,
27
- };
28
- }
29
-
30
- describe("readWatcherStatus — watcher stale fallback", () => {
31
- let tempDir: string;
32
- let config: PatchWardenConfig;
33
-
34
- beforeEach(() => {
35
- tempDir = mkdtempSync(join(tmpdir(), "pw-watcher-test-"));
36
- config = makeConfig(tempDir);
37
- mkdirSync(join(tempDir, ".patchwarden", "tasks"), { recursive: true });
38
- });
39
-
40
- afterEach(() => {
41
- rmSync(tempDir, { recursive: true, force: true });
42
- });
43
-
44
- it("returns missing when no heartbeat and no running task", () => {
45
- const result = readWatcherStatus(config, Date.now());
46
- assert.equal(result.status, "missing");
47
- assert.equal(result.available, false);
48
- assert.equal(result.activity, null);
49
- });
50
-
51
- it("returns healthy when watcher heartbeat is fresh", () => {
52
- const heartbeatPath = getWatcherHeartbeatPath(config);
53
- mkdirSync(dirname(heartbeatPath), { recursive: true });
54
- writeFileSync(heartbeatPath, JSON.stringify({
55
- status: "running",
56
- pid: process.pid,
57
- last_heartbeat_at: new Date().toISOString(),
58
- }), "utf-8");
59
-
60
- const result = readWatcherStatus(config, Date.now());
61
- assert.equal(result.status, "healthy");
62
- assert.equal(result.available, true);
63
- assert.equal(result.activity, null);
64
- });
65
-
66
- it("returns stale when watcher heartbeat is old and no running task", () => {
67
- const heartbeatPath = getWatcherHeartbeatPath(config);
68
- mkdirSync(dirname(heartbeatPath), { recursive: true });
69
- const oldTime = new Date(Date.now() - 120_000).toISOString();
70
- writeFileSync(heartbeatPath, JSON.stringify({
71
- status: "running",
72
- pid: process.pid,
73
- last_heartbeat_at: oldTime,
74
- }), "utf-8");
75
-
76
- const result = readWatcherStatus(config, Date.now());
77
- assert.equal(result.status, "stale");
78
- assert.equal(result.available, false);
79
- assert.equal(result.activity, null);
80
- });
81
-
82
- it("returns healthy when watcher heartbeat is stale but a running task has fresh heartbeat", () => {
83
- // Write stale watcher heartbeat
84
- const heartbeatPath = getWatcherHeartbeatPath(config);
85
- mkdirSync(dirname(heartbeatPath), { recursive: true });
86
- const oldTime = new Date(Date.now() - 120_000).toISOString();
87
- writeFileSync(heartbeatPath, JSON.stringify({
88
- status: "running",
89
- pid: process.pid,
90
- last_heartbeat_at: oldTime,
91
- }), "utf-8");
92
-
93
- // Create a running task with fresh heartbeat
94
- const taskDir = join(tempDir, ".patchwarden", "tasks", "test-task-001");
95
- mkdirSync(taskDir, { recursive: true });
96
- writeFileSync(join(taskDir, "status.json"), JSON.stringify({
97
- status: "running",
98
- task_id: "test-task-001",
99
- }), "utf-8");
100
- writeFileSync(join(taskDir, "runtime.json"), JSON.stringify({
101
- phase: "executing_agent",
102
- last_heartbeat_at: new Date().toISOString(),
103
- current_command: "codex exec",
104
- }), "utf-8");
105
-
106
- const result = readWatcherStatus(config, Date.now());
107
- assert.equal(result.status, "healthy");
108
- assert.equal(result.available, true);
109
- assert.ok(result.activity);
110
- assert.match(result.activity!, /test-task-001/);
111
- });
112
-
113
- it("returns healthy when watcher heartbeat is missing but a running task has fresh heartbeat", () => {
114
- // No watcher heartbeat file at all
115
- // Create a running task with fresh heartbeat
116
- const taskDir = join(tempDir, ".patchwarden", "tasks", "test-task-002");
117
- mkdirSync(taskDir, { recursive: true });
118
- writeFileSync(join(taskDir, "status.json"), JSON.stringify({
119
- status: "running",
120
- task_id: "test-task-002",
121
- }), "utf-8");
122
- writeFileSync(join(taskDir, "runtime.json"), JSON.stringify({
123
- phase: "running_tests",
124
- last_heartbeat_at: new Date().toISOString(),
125
- current_command: "npm test",
126
- }), "utf-8");
127
-
128
- const result = readWatcherStatus(config, Date.now());
129
- assert.equal(result.status, "healthy");
130
- assert.equal(result.available, true);
131
- assert.ok(result.activity);
132
- assert.match(result.activity!, /test-task-002/);
133
- });
134
-
135
- it("returns stale when both watcher heartbeat and task heartbeat are old", () => {
136
- const heartbeatPath = getWatcherHeartbeatPath(config);
137
- mkdirSync(dirname(heartbeatPath), { recursive: true });
138
- const oldTime = new Date(Date.now() - 120_000).toISOString();
139
- writeFileSync(heartbeatPath, JSON.stringify({
140
- status: "running",
141
- pid: process.pid,
142
- last_heartbeat_at: oldTime,
143
- }), "utf-8");
144
-
145
- const taskDir = join(tempDir, ".patchwarden", "tasks", "test-task-003");
146
- mkdirSync(taskDir, { recursive: true });
147
- writeFileSync(join(taskDir, "status.json"), JSON.stringify({
148
- status: "running",
149
- task_id: "test-task-003",
150
- }), "utf-8");
151
- writeFileSync(join(taskDir, "runtime.json"), JSON.stringify({
152
- phase: "executing_agent",
153
- last_heartbeat_at: oldTime,
154
- current_command: "codex exec",
155
- }), "utf-8");
156
-
157
- const result = readWatcherStatus(config, Date.now());
158
- assert.equal(result.status, "stale");
159
- assert.equal(result.available, false);
160
- assert.equal(result.activity, null);
161
- });
162
-
163
- it("does not fall back to non-running tasks", () => {
164
- const heartbeatPath = getWatcherHeartbeatPath(config);
165
- mkdirSync(dirname(heartbeatPath), { recursive: true });
166
- const oldTime = new Date(Date.now() - 120_000).toISOString();
167
- writeFileSync(heartbeatPath, JSON.stringify({
168
- status: "running",
169
- pid: process.pid,
170
- last_heartbeat_at: oldTime,
171
- }), "utf-8");
172
-
173
- // Done task with fresh heartbeat should not trigger fallback
174
- const taskDir = join(tempDir, ".patchwarden", "tasks", "test-task-004");
175
- mkdirSync(taskDir, { recursive: true });
176
- writeFileSync(join(taskDir, "status.json"), JSON.stringify({
177
- status: "done",
178
- task_id: "test-task-004",
179
- }), "utf-8");
180
- writeFileSync(join(taskDir, "runtime.json"), JSON.stringify({
181
- phase: "completed",
182
- last_heartbeat_at: new Date().toISOString(),
183
- current_command: null,
184
- }), "utf-8");
185
-
186
- const result = readWatcherStatus(config, Date.now());
187
- assert.equal(result.status, "stale");
188
- assert.equal(result.available, false);
189
- });
190
- });
@@ -1,247 +0,0 @@
1
- import { describe, it, before, after } from "node:test";
2
- import { strict as assert } from "node:assert";
3
- import {
4
- mkdtempSync,
5
- rmSync,
6
- writeFileSync,
7
- readFileSync,
8
- existsSync,
9
- readdirSync,
10
- unlinkSync,
11
- } from "node:fs";
12
- import { join } from "node:path";
13
- import { tmpdir } from "node:os";
14
- import { execSync } from "node:child_process";
15
- import {
16
- createWorktree,
17
- mergeWorktree,
18
- discardWorktree,
19
- readWorktreeStatus,
20
- getWorktreesDir,
21
- type WorktreeStatus,
22
- } from "../../goal/worktreeManager.js";
23
- import { PatchWardenError } from "../../errors.js";
24
-
25
- // ── Test fixtures ─────────────────────────────────────────────────
26
-
27
- /**
28
- * 在 dir 创建一个真实的 git 仓库:git init + 配置 user + 初始 commit。
29
- * createWorktree 要求 workspaceRoot 是一个至少有一个 commit 的 git 仓库。
30
- */
31
- function initGitRepo(dir: string): void {
32
- execSync("git init", { cwd: dir, stdio: "ignore" });
33
- execSync('git config user.email "test@patchwarden.local"', { cwd: dir, stdio: "ignore" });
34
- execSync('git config user.name "PatchWarden Test"', { cwd: dir, stdio: "ignore" });
35
- execSync('git config commit.gpgsign false', { cwd: dir, stdio: "ignore" });
36
- writeFileSync(join(dir, "README.md"), "init\n", "utf-8");
37
- execSync("git add README.md", { cwd: dir, stdio: "ignore" });
38
- execSync("git commit -m init", { cwd: dir, stdio: "ignore" });
39
- }
40
-
41
- function gitBranchList(cwd: string): string {
42
- return execSync("git branch --list", { cwd, stdio: ["pipe", "pipe", "ignore"] }).toString("utf-8");
43
- }
44
-
45
- function assertPatchWardenError(fn: () => void, reason: string): void {
46
- try {
47
- fn();
48
- assert.fail(`expected PatchWardenError("${reason}") to be thrown`);
49
- } catch (err) {
50
- assert.ok(err instanceof PatchWardenError, `expected PatchWardenError, got: ${err}`);
51
- assert.equal((err as PatchWardenError).reason, reason);
52
- }
53
- }
54
-
55
- // ── Tests ─────────────────────────────────────────────────────────
56
-
57
- describe("worktreeManager", () => {
58
- let workspaceRoot: string;
59
-
60
- before(() => {
61
- workspaceRoot = mkdtempSync(join(tmpdir(), "pw-wt-"));
62
- initGitRepo(workspaceRoot);
63
- });
64
-
65
- after(() => {
66
- rmSync(workspaceRoot, { recursive: true, force: true });
67
- });
68
-
69
- // ── 1. createWorktree 成功 ──
70
- it("createWorktree creates an active worktree with valid status.json", () => {
71
- const result = createWorktree("goal_20260101_demo", "subgoal-001", workspaceRoot);
72
-
73
- assert.ok(result.worktreeId.startsWith("wt_"), `worktreeId should start with "wt_", got: ${result.worktreeId}`);
74
- assert.ok(result.branch.startsWith("pw-goal_20260101_demo-subgoal-001"), `branch should be pw-<goal>-<subgoal>, got: ${result.branch}`);
75
- assert.ok(existsSync(result.worktreePath), "worktreePath should exist");
76
-
77
- const statusFile = join(result.worktreePath, "worktree_status.json");
78
- assert.ok(existsSync(statusFile), "worktree_status.json should exist");
79
-
80
- // 原子写:不应残留 .tmp 文件
81
- assert.ok(!existsSync(statusFile + ".tmp"), "status.json.tmp should not remain after atomic write");
82
-
83
- // JSON 合法且字段正确
84
- const status = JSON.parse(readFileSync(statusFile, "utf-8")) as WorktreeStatus;
85
- assert.equal(status.worktree_id, result.worktreeId);
86
- assert.equal(status.goal_id, "goal_20260101_demo");
87
- assert.equal(status.subgoal_id, "subgoal-001");
88
- assert.equal(status.status, "active");
89
- assert.equal(status.branch, result.branch);
90
- assert.equal(status.path, result.worktreePath);
91
- assert.ok(typeof status.created_at === "string" && status.created_at.length > 0);
92
-
93
- // worktree 内应检出主仓库的文件(README.md)
94
- assert.ok(existsSync(join(result.worktreePath, "README.md")), "worktree should check out README.md");
95
-
96
- // branch 应在 git 中存在
97
- assert.ok(gitBranchList(workspaceRoot).includes(result.branch), "branch should exist in git");
98
-
99
- // readWorktreeStatus 应返回一致的状态
100
- const readBack = readWorktreeStatus(result.worktreeId, workspaceRoot);
101
- assert.ok(readBack !== null);
102
- assert.equal(readBack.status, "active");
103
- });
104
-
105
- // ── 2. createWorktree git 命令失败清理 ──
106
- it("createWorktree cleans up and throws when git fails (non-git workspaceRoot)", () => {
107
- const nonGitDir = mkdtempSync(join(tmpdir(), "pw-wt-nogit-"));
108
- try {
109
- // 非 git 目录 → git worktree add 失败
110
- assertPatchWardenError(
111
- () => createWorktree("goal_x", "subgoal-001", nonGitDir),
112
- "worktree_create_failed"
113
- );
114
-
115
- // 不应残留任何 worktree 目录
116
- const worktreesDir = getWorktreesDir(nonGitDir);
117
- if (existsSync(worktreesDir)) {
118
- // 若 _workspacetrees 被创建,应为空(无半成品 worktree)
119
- const entries = readdirSync(worktreesDir);
120
- assert.equal(entries.length, 0, "no leftover worktree directories should remain after failure");
121
- }
122
- } finally {
123
- rmSync(nonGitDir, { recursive: true, force: true });
124
- }
125
- });
126
-
127
- // ── 3. mergeWorktree 成功 ──
128
- it("mergeWorktree merges the branch back and marks status merged", () => {
129
- const created = createWorktree("goal_merge", "subgoal-001", workspaceRoot);
130
-
131
- // 在 worktree 中产生一个新 commit,使 merge 有实际内容
132
- writeFileSync(join(created.worktreePath, "feature.txt"), "merged feature\n", "utf-8");
133
- execSync("git add feature.txt", { cwd: created.worktreePath, stdio: "ignore" });
134
- execSync("git commit -m feature", { cwd: created.worktreePath, stdio: "ignore" });
135
-
136
- const result = mergeWorktree(created.worktreeId, workspaceRoot);
137
- assert.equal(result.status, "merged");
138
-
139
- // 主工作区应出现 feature.txt(fast-forward 合并)
140
- assert.ok(existsSync(join(workspaceRoot, "feature.txt")), "feature.txt should appear in main workspace after merge");
141
-
142
- // status 应更新为 merged,merged_at 非空
143
- const status = readWorktreeStatus(created.worktreeId, workspaceRoot);
144
- assert.ok(status !== null);
145
- assert.equal(status.status, "merged");
146
- assert.ok(typeof status.merged_at === "string" && status.merged_at.length > 0, "merged_at should be set");
147
- });
148
-
149
- // ── 4. discardWorktree 成功 ──
150
- it("discardWorktree removes worktree, deletes branch, and archives status", () => {
151
- const created = createWorktree("goal_discard", "subgoal-001", workspaceRoot);
152
- const branch = created.branch;
153
- const worktreePath = created.worktreePath;
154
-
155
- const result = discardWorktree(created.worktreeId, workspaceRoot);
156
- assert.equal(result.status, "discarded");
157
-
158
- // worktree 目录应被移除
159
- assert.ok(!existsSync(worktreePath), "worktree directory should be removed after discard");
160
-
161
- // 临时 branch 应被删除
162
- assert.ok(!gitBranchList(workspaceRoot).includes(branch), "temporary branch should be deleted after discard");
163
-
164
- // 归档文件应存在且 status="discarded"
165
- const archiveFile = join(workspaceRoot, ".patchwarden", "worktree-archive", `${created.worktreeId}.json`);
166
- assert.ok(existsSync(archiveFile), "archived status file should exist");
167
- const archived = JSON.parse(readFileSync(archiveFile, "utf-8")) as WorktreeStatus;
168
- assert.equal(archived.status, "discarded");
169
- assert.ok(typeof archived.discarded_at === "string" && archived.discarded_at.length > 0, "discarded_at should be set");
170
- assert.equal(archived.worktree_id, created.worktreeId);
171
- });
172
-
173
- // ── 5. 路径逃逸拦截 ──
174
- it("blocks path escape via worktreeId containing ..", () => {
175
- // worktreeId 在 read/merge/discard 中由调用方提供,构造逃逸路径
176
- assertPatchWardenError(
177
- () => readWorktreeStatus("../../../outside", workspaceRoot),
178
- "workspace_path_escape"
179
- );
180
- assertPatchWardenError(
181
- () => mergeWorktree("../../../outside", workspaceRoot),
182
- "workspace_path_escape"
183
- );
184
- assertPatchWardenError(
185
- () => discardWorktree("../../../outside", workspaceRoot),
186
- "workspace_path_escape"
187
- );
188
- });
189
-
190
- // ── 6. 敏感路径拦截 ──
191
- it("blocks sensitive worktreeId via guardSensitivePath", () => {
192
- // "credentials" 命中 sensitiveGuard 的敏感模式
193
- assertPatchWardenError(
194
- () => readWorktreeStatus("credentials", workspaceRoot),
195
- "sensitive_path_blocked"
196
- );
197
- assertPatchWardenError(
198
- () => mergeWorktree("credentials", workspaceRoot),
199
- "sensitive_path_blocked"
200
- );
201
- assertPatchWardenError(
202
- () => discardWorktree("credentials", workspaceRoot),
203
- "sensitive_path_blocked"
204
- );
205
- });
206
-
207
- // ── 7. readWorktreeStatus 返回 null 当 status 不存在 ──
208
- it("readWorktreeStatus returns null when status file does not exist", () => {
209
- // 创建一个 worktree 目录但不含 status 文件
210
- const created = createWorktree("goal_missing", "subgoal-001", workspaceRoot);
211
- // 手动删除 status 文件模拟缺失
212
- unlinkSync(join(created.worktreePath, "worktree_status.json"));
213
- assert.equal(readWorktreeStatus(created.worktreeId, workspaceRoot), null);
214
- // 清理:删除该 worktree(避免污染后续测试)
215
- try {
216
- execSync(`git worktree remove --force "${created.worktreePath}"`, { cwd: workspaceRoot, stdio: "ignore" });
217
- } catch { /* ignore */ }
218
- try {
219
- execSync(`git branch -D ${created.branch}`, { cwd: workspaceRoot, stdio: "ignore" });
220
- } catch { /* ignore */ }
221
- });
222
-
223
- // ── 8. 非法状态转换:merge/discard 已 merged 的 worktree 抛错 ──
224
- it("rejects merge/discard on non-active worktree", () => {
225
- // 复用 #3 已 merged 的 worktree(goal_merge/subgoal-001)
226
- // 先找到它的 worktreeId:扫描 _workspacetrees 目录
227
- const worktreesDir = getWorktreesDir(workspaceRoot);
228
- let mergedId: string | null = null;
229
- for (const entry of readdirSync(worktreesDir)) {
230
- const s = readWorktreeStatus(entry, workspaceRoot);
231
- if (s && s.status === "merged" && s.goal_id === "goal_merge") {
232
- mergedId = entry;
233
- break;
234
- }
235
- }
236
- assert.ok(mergedId !== null, "should find the merged worktree from the merge test");
237
-
238
- assertPatchWardenError(
239
- () => mergeWorktree(mergedId as string, workspaceRoot),
240
- "invalid_worktree_state"
241
- );
242
- assertPatchWardenError(
243
- () => discardWorktree(mergedId as string, workspaceRoot),
244
- "invalid_worktree_state"
245
- );
246
- });
247
- });
@@ -1,70 +0,0 @@
1
- import { readFileSync, writeFileSync, 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 { writeTaskProgress } from "../taskProgress.js";
6
- import type { TaskStatus } from "./createTask.js";
7
-
8
- export function cancelTask(taskId: string) {
9
- return requestTaskTermination(taskId, false);
10
- }
11
-
12
- export function requestTaskTermination(taskId: string, force: boolean) {
13
- const config = getConfig();
14
- const taskDir = join(getTasksDir(config), taskId);
15
- const statusFile = join(taskDir, "status.json");
16
- guardReadPath(statusFile, config.workspaceRoot, config.tasksDir);
17
-
18
- if (!existsSync(statusFile)) throw new Error(`Task not found: "${taskId}"`);
19
-
20
- const data = JSON.parse(readFileSync(statusFile, "utf-8"));
21
- const currentStatus: TaskStatus = data.status;
22
- if (["done", "done_by_agent", "failed", "failed_verification", "failed_scope_violation", "failed_policy_violation", "canceled"].includes(currentStatus)) {
23
- return {
24
- task_id: taskId,
25
- previous_status: currentStatus,
26
- new_status: currentStatus,
27
- message: `Task is already ${currentStatus}. No action taken.`,
28
- };
29
- }
30
-
31
- const now = new Date().toISOString();
32
- if (currentStatus === "pending") {
33
- data.status = "canceled";
34
- data.phase = "canceled";
35
- data.canceled_at = now;
36
- data.cancel_reason = force ? "Killed before execution by user request." : "Canceled by user request.";
37
- data.updated_at = now;
38
- writeFileSync(statusFile, JSON.stringify(data, null, 2), "utf-8");
39
- writeTaskProgress(taskDir, "canceled", { note: data.cancel_reason, heartbeatAt: now });
40
- return {
41
- task_id: taskId,
42
- previous_status: "pending",
43
- new_status: "canceled",
44
- message: "Pending task canceled. It will not be executed by watcher.",
45
- };
46
- }
47
-
48
- data.cancel_requested = true;
49
- data.cancel_requested_at = now;
50
- data.force_kill_requested = force;
51
- if (force) data.kill_requested_at = now;
52
- data.phase = force ? "terminating" : "canceling";
53
- data.updated_at = now;
54
- writeFileSync(statusFile, JSON.stringify(data, null, 2), "utf-8");
55
- writeTaskProgress(taskDir, data.phase, {
56
- note: force ? "Immediate termination requested." : "Graceful cancellation requested.",
57
- heartbeatAt: now,
58
- });
59
-
60
- return {
61
- task_id: taskId,
62
- previous_status: "running",
63
- new_status: "running",
64
- cancel_requested: true,
65
- force_kill_requested: force,
66
- message: force
67
- ? "Kill requested. The runner that owns the child process will terminate it."
68
- : "Cancel requested. The runner will stop the child process safely.",
69
- };
70
- }