patchwarden 1.5.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (597) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +194 -26
  3. package/README.md +160 -22
  4. package/dist/assessments/agentAssessor.d.ts +1 -1
  5. package/dist/assessments/agentAssessor.js +24 -15
  6. package/dist/assessments/assessmentStore.d.ts +1 -1
  7. package/dist/assessments/assessmentStore.js +44 -36
  8. package/dist/assessments/confirmCli.js +5 -4
  9. package/dist/config.d.ts +22 -0
  10. package/dist/config.js +98 -11
  11. package/dist/control/fileManager.d.ts +2 -0
  12. package/dist/control/fileManager.js +39 -0
  13. package/dist/control/middleware/auth.d.ts +16 -0
  14. package/dist/control/middleware/auth.js +17 -0
  15. package/dist/control/middleware/static.d.ts +3 -0
  16. package/dist/control/middleware/static.js +78 -0
  17. package/dist/control/routeTable.d.ts +24 -0
  18. package/dist/control/routeTable.js +263 -0
  19. package/dist/control/routes/audit.d.ts +15 -0
  20. package/dist/control/routes/audit.js +287 -0
  21. package/dist/control/routes/evidence.d.ts +4 -0
  22. package/dist/control/routes/evidence.js +96 -0
  23. package/dist/control/routes/lineage.d.ts +3 -0
  24. package/dist/control/routes/lineage.js +71 -0
  25. package/dist/control/routes/policy.d.ts +3 -0
  26. package/dist/control/routes/policy.js +81 -0
  27. package/dist/control/routes/process.d.ts +12 -0
  28. package/dist/control/routes/process.js +487 -0
  29. package/dist/control/routes/sessions.d.ts +30 -0
  30. package/dist/control/routes/sessions.js +287 -0
  31. package/dist/control/routes/status.d.ts +51 -0
  32. package/dist/control/routes/status.js +307 -0
  33. package/dist/control/routes/taskActions.d.ts +21 -0
  34. package/dist/control/routes/taskActions.js +197 -0
  35. package/dist/control/routes/tasks.d.ts +24 -0
  36. package/dist/control/routes/tasks.js +309 -0
  37. package/dist/control/routes/workspace.d.ts +15 -0
  38. package/dist/control/routes/workspace.js +220 -0
  39. package/dist/control/runtime.d.ts +92 -0
  40. package/dist/control/runtime.js +425 -0
  41. package/dist/control/server.d.ts +13 -0
  42. package/dist/control/server.js +150 -0
  43. package/dist/control/shared.d.ts +59 -0
  44. package/dist/control/shared.js +341 -0
  45. package/dist/controlCenter.d.ts +6 -0
  46. package/dist/controlCenter.js +7 -2197
  47. package/dist/direct/directAudit.js +234 -225
  48. package/dist/direct/directGuards.d.ts +2 -1
  49. package/dist/direct/directGuards.js +72 -37
  50. package/dist/direct/directPatch.d.ts +6 -1
  51. package/dist/direct/directPatch.js +55 -8
  52. package/dist/direct/directSessionStore.d.ts +16 -6
  53. package/dist/direct/directSessionStore.js +127 -35
  54. package/dist/direct/directVerification.js +8 -16
  55. package/dist/doctor.d.ts +18 -1
  56. package/dist/doctor.js +614 -373
  57. package/dist/goal/acceptanceEngine.d.ts +1 -1
  58. package/dist/goal/goalProgress.js +75 -69
  59. package/dist/goal/goalReport.d.ts +54 -0
  60. package/dist/goal/goalReport.js +197 -0
  61. package/dist/goal/goalStatus.d.ts +8 -0
  62. package/dist/goal/goalStatus.js +10 -3
  63. package/dist/goal/goalStore.d.ts +9 -2
  64. package/dist/goal/goalStore.js +152 -45
  65. package/dist/goal/handoffExport.js +6 -6
  66. package/dist/goal/specKitImport.d.ts +63 -0
  67. package/dist/goal/specKitImport.js +221 -0
  68. package/dist/goal/subgoalSync.js +13 -10
  69. package/dist/goal/worktreeManager.d.ts +2 -0
  70. package/dist/goal/worktreeManager.js +209 -136
  71. package/dist/httpServer.js +126 -44
  72. package/dist/index.js +7 -4
  73. package/dist/logging.d.ts +7 -1
  74. package/dist/logging.js +13 -3
  75. package/dist/policy/projectPolicy.js +5 -2
  76. package/dist/release/releaseGate.d.ts +1 -1
  77. package/dist/release/releaseGate.js +78 -25
  78. package/dist/runner/agentInvocation.d.ts +14 -0
  79. package/dist/runner/agentInvocation.js +96 -4
  80. package/dist/runner/changeCapture.d.ts +14 -6
  81. package/dist/runner/changeCapture.js +287 -81
  82. package/dist/runner/cli.js +7 -6
  83. package/dist/runner/postTaskCleanup.js +60 -7
  84. package/dist/runner/processSecurity.d.ts +56 -0
  85. package/dist/runner/processSecurity.js +373 -0
  86. package/dist/runner/runTask.d.ts +1 -1
  87. package/dist/runner/runTask.js +339 -287
  88. package/dist/runner/simpleProcess.d.ts +3 -0
  89. package/dist/runner/simpleProcess.js +52 -38
  90. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  91. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  92. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
  93. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  94. package/dist/runner/taskStatusStore.d.ts +11 -0
  95. package/dist/runner/taskStatusStore.js +24 -0
  96. package/dist/runner/watch.d.ts +14 -1
  97. package/dist/runner/watch.js +265 -54
  98. package/dist/security/contentRedaction.d.ts +6 -0
  99. package/dist/security/contentRedaction.js +24 -2
  100. package/dist/security/discoveryTokenStore.d.ts +3 -2
  101. package/dist/security/discoveryTokenStore.js +17 -2
  102. package/dist/security/loopbackHost.d.ts +2 -0
  103. package/dist/security/loopbackHost.js +16 -0
  104. package/dist/security/pathGuard.js +32 -17
  105. package/dist/security/planGuard.js +2 -1
  106. package/dist/security/runtimeGuard.js +9 -6
  107. package/dist/security/secretComparison.d.ts +3 -0
  108. package/dist/security/secretComparison.js +11 -0
  109. package/dist/security/sensitiveGuard.d.ts +5 -0
  110. package/dist/security/sensitiveGuard.js +27 -8
  111. package/dist/security/toolInvocationGuard.d.ts +2 -2
  112. package/dist/security/toolInvocationGuard.js +1 -1
  113. package/dist/security/workspaceRootGuard.d.ts +8 -0
  114. package/dist/security/workspaceRootGuard.js +47 -0
  115. package/dist/smoke-test.js +280 -273
  116. package/dist/tools/catalog/index.d.ts +4 -0
  117. package/dist/tools/catalog/index.js +4 -0
  118. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  119. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  120. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +28 -15
  121. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  122. package/dist/tools/definitions/toolDefs.js +1177 -0
  123. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  124. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  125. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +8 -63
  126. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +111 -37
  127. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
  128. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +10 -10
  129. package/dist/tools/diagnostics/index.d.ts +6 -0
  130. package/dist/tools/diagnostics/index.js +6 -0
  131. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  132. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  133. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +14 -10
  134. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +25 -15
  135. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  136. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  137. package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +1 -1
  138. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +8 -8
  139. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +2 -2
  140. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  141. package/dist/tools/direct/index.d.ts +3 -0
  142. package/dist/tools/direct/index.js +3 -0
  143. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  144. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  145. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  146. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  147. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  148. package/dist/tools/discovery/index.d.ts +3 -0
  149. package/dist/tools/discovery/index.js +3 -0
  150. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  151. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  152. package/dist/tools/dispatch/coreDispatch.d.ts +9 -0
  153. package/dist/tools/dispatch/coreDispatch.js +283 -0
  154. package/dist/tools/dispatch/diagnosticDispatch.d.ts +12 -0
  155. package/dist/tools/dispatch/diagnosticDispatch.js +80 -0
  156. package/dist/tools/dispatch/directDispatch.d.ts +8 -0
  157. package/dist/tools/dispatch/directDispatch.js +116 -0
  158. package/dist/tools/dispatch/goalDispatch.d.ts +8 -0
  159. package/dist/tools/dispatch/goalDispatch.js +92 -0
  160. package/dist/tools/dispatch/releaseDispatch.d.ts +8 -0
  161. package/dist/tools/dispatch/releaseDispatch.js +46 -0
  162. package/dist/tools/dispatch/types.d.ts +23 -0
  163. package/dist/tools/dispatch/types.js +15 -0
  164. package/dist/tools/dispatch/validation.d.ts +7 -0
  165. package/dist/tools/dispatch/validation.js +71 -0
  166. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  167. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +2 -2
  168. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  169. package/dist/tools/goals/index.d.ts +3 -0
  170. package/dist/tools/goals/index.js +3 -0
  171. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  172. package/dist/tools/registry.d.ts +10 -14
  173. package/dist/tools/registry.js +47 -1651
  174. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  175. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  176. package/dist/tools/release/index.d.ts +2 -0
  177. package/dist/tools/release/index.js +2 -0
  178. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  179. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  180. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  181. package/dist/tools/tasks/cancelTask.js +85 -0
  182. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
  183. package/dist/tools/{createTask.js → tasks/createTask.js} +46 -31
  184. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  185. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
  186. package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +6 -0
  187. package/dist/tools/tasks/evidencePack.js +387 -0
  188. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  189. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  190. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
  191. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  192. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
  193. package/dist/tools/tasks/index.d.ts +18 -0
  194. package/dist/tools/tasks/index.js +18 -0
  195. package/dist/tools/tasks/killTask.d.ts +1 -0
  196. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  197. package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
  198. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
  199. package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
  200. package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +2 -2
  201. package/dist/tools/tasks/retryTask.js +60 -0
  202. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  203. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +59 -31
  204. package/dist/tools/tasks/runVerification.js +38 -0
  205. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  206. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  207. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  208. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  209. package/dist/tools/workspace/applyPatch.js +53 -0
  210. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  211. package/dist/tools/workspace/index.d.ts +10 -0
  212. package/dist/tools/workspace/index.js +10 -0
  213. package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
  214. package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
  215. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  216. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  217. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  218. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  219. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  220. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  221. package/dist/tools/workspace/syncFile.js +134 -0
  222. package/dist/utils/atomicFile.d.ts +7 -0
  223. package/dist/utils/atomicFile.js +48 -0
  224. package/dist/utils/boundedFile.d.ts +8 -0
  225. package/dist/utils/boundedFile.js +93 -0
  226. package/dist/utils/lockedJsonFile.d.ts +15 -0
  227. package/dist/utils/lockedJsonFile.js +224 -0
  228. package/dist/utils/platform.d.ts +11 -0
  229. package/dist/utils/platform.js +26 -0
  230. package/dist/utils/stableJson.d.ts +2 -0
  231. package/dist/utils/stableJson.js +12 -0
  232. package/dist/version.d.ts +3 -2
  233. package/dist/version.js +3 -2
  234. package/dist/watcherStatus.d.ts +2 -2
  235. package/dist/watcherStatus.js +18 -0
  236. package/docs/CODE_WIKI.md +1648 -0
  237. package/docs/agentseal-integration.md +150 -0
  238. package/docs/architecture.md +23 -0
  239. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  240. package/docs/assets/patchwarden-oss-demo.gif +0 -0
  241. package/docs/control-center/README.md +4 -4
  242. package/docs/dashboard-overview.md +86 -0
  243. package/docs/demo.md +8 -0
  244. package/docs/desktop-app.md +175 -0
  245. package/docs/direct-session-workflow.md +98 -0
  246. package/docs/evidence-pack-schema.md +215 -0
  247. package/docs/lineage-evidence-pack-workflow.md +127 -0
  248. package/docs/mcp-inspector-testing.md +200 -0
  249. package/docs/open-source-application.md +168 -0
  250. package/docs/opencode-worker.md +151 -0
  251. package/docs/openhands-worker.md +181 -0
  252. package/docs/release-checklist.md +1 -1
  253. package/docs/release-evidence.md +82 -0
  254. package/docs/spec-kit-integration.md +131 -0
  255. package/docs/task-safe-review-workflow.md +98 -0
  256. package/docs/threat-model.md +97 -0
  257. package/docs/user-feedback.md +40 -0
  258. package/docs/why-patchwarden.md +110 -0
  259. package/examples/config.example.json +4 -2
  260. package/package.json +28 -11
  261. package/scripts/README.md +15 -6
  262. package/scripts/build.js +11 -0
  263. package/scripts/checks/brand-check.js +3 -0
  264. package/scripts/checks/build-output-check.js +37 -0
  265. package/scripts/checks/control-center-smoke.js +447 -6
  266. package/scripts/checks/control-smoke.js +19 -8
  267. package/scripts/checks/http-mcp-smoke.js +82 -4
  268. package/scripts/checks/lifecycle-smoke.js +39 -33
  269. package/scripts/checks/mcp-manifest-check.js +1 -1
  270. package/scripts/checks/mcp-smoke.js +30 -4
  271. package/scripts/checks/package-manifest-check.js +20 -7
  272. package/scripts/checks/unit-tests.js +3 -5
  273. package/scripts/checks/watcher-supervisor-smoke.js +9 -0
  274. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  275. package/scripts/control/manage-patchwarden.ps1 +69 -4
  276. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  277. package/scripts/control/run-background-supervisor.ps1 +41 -0
  278. package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
  279. package/scripts/generate-demo-gif.py +320 -0
  280. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  281. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  282. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  283. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  284. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  285. package/scripts/lib/clean-generated-output.js +11 -0
  286. package/scripts/lib/file-discovery.js +12 -0
  287. package/scripts/release/desktop-preflight.js +211 -0
  288. package/scripts/release/pack-clean.js +59 -14
  289. package/src/assessments/agentAssessor.ts +29 -15
  290. package/src/assessments/assessmentStore.ts +55 -43
  291. package/src/assessments/confirmCli.ts +5 -4
  292. package/src/config.ts +105 -11
  293. package/src/control/fileManager.ts +40 -0
  294. package/src/control/middleware/auth.ts +35 -0
  295. package/src/control/middleware/static.ts +84 -0
  296. package/src/control/routeTable.ts +301 -0
  297. package/src/control/routes/audit.ts +329 -0
  298. package/src/control/routes/evidence.ts +107 -0
  299. package/src/control/routes/lineage.ts +92 -0
  300. package/src/control/routes/policy.ts +81 -0
  301. package/src/control/routes/process.ts +505 -0
  302. package/src/control/routes/sessions.ts +314 -0
  303. package/src/control/routes/status.ts +405 -0
  304. package/src/control/routes/taskActions.ts +214 -0
  305. package/src/control/routes/tasks.ts +331 -0
  306. package/src/control/routes/workspace.ts +234 -0
  307. package/src/control/runtime.ts +508 -0
  308. package/src/control/server.ts +165 -0
  309. package/src/control/shared.ts +360 -0
  310. package/src/controlCenter.ts +7 -2347
  311. package/src/direct/directAudit.ts +17 -9
  312. package/src/direct/directGuards.ts +100 -51
  313. package/src/direct/directPatch.ts +95 -8
  314. package/src/direct/directSessionStore.ts +211 -55
  315. package/src/direct/directVerification.ts +9 -23
  316. package/src/doctor.ts +766 -492
  317. package/src/goal/acceptanceEngine.ts +1 -1
  318. package/src/goal/goalProgress.ts +17 -13
  319. package/src/goal/goalReport.ts +263 -0
  320. package/src/goal/goalStatus.ts +25 -3
  321. package/src/goal/goalStore.ts +241 -54
  322. package/src/goal/handoffExport.ts +6 -6
  323. package/src/goal/specKitImport.ts +355 -0
  324. package/src/goal/subgoalSync.ts +15 -10
  325. package/src/goal/worktreeManager.ts +139 -22
  326. package/src/httpServer.ts +130 -44
  327. package/src/index.ts +7 -4
  328. package/src/logging.ts +20 -4
  329. package/src/policy/projectPolicy.ts +5 -2
  330. package/src/release/releaseGate.ts +74 -27
  331. package/src/runner/agentInvocation.ts +126 -4
  332. package/src/runner/changeCapture.ts +342 -80
  333. package/src/runner/cli.ts +7 -6
  334. package/src/runner/postTaskCleanup.ts +55 -7
  335. package/src/runner/processSecurity.ts +433 -0
  336. package/src/runner/runTask.ts +441 -292
  337. package/src/runner/simpleProcess.ts +68 -33
  338. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  339. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
  340. package/src/runner/taskStatusStore.ts +54 -0
  341. package/src/runner/watch.ts +290 -57
  342. package/src/security/contentRedaction.ts +31 -2
  343. package/src/security/discoveryTokenStore.ts +18 -4
  344. package/src/security/loopbackHost.ts +19 -0
  345. package/src/security/pathGuard.ts +35 -16
  346. package/src/security/planGuard.ts +2 -1
  347. package/src/security/runtimeGuard.ts +9 -6
  348. package/src/security/secretComparison.ts +13 -0
  349. package/src/security/sensitiveGuard.ts +27 -8
  350. package/src/security/toolInvocationGuard.ts +2 -2
  351. package/src/security/workspaceRootGuard.ts +53 -0
  352. package/src/smoke-test.ts +276 -273
  353. package/src/tools/catalog/index.ts +4 -0
  354. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  355. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +29 -16
  356. package/src/tools/definitions/toolDefs.ts +1264 -0
  357. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  358. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +196 -89
  359. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +11 -11
  360. package/src/tools/diagnostics/index.ts +6 -0
  361. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  362. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +29 -19
  363. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  364. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +9 -9
  365. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +11 -9
  366. package/src/tools/direct/index.ts +3 -0
  367. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  368. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  369. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  370. package/src/tools/discovery/index.ts +3 -0
  371. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  372. package/src/tools/dispatch/coreDispatch.ts +379 -0
  373. package/src/tools/dispatch/diagnosticDispatch.ts +106 -0
  374. package/src/tools/dispatch/directDispatch.ts +168 -0
  375. package/src/tools/dispatch/goalDispatch.ts +128 -0
  376. package/src/tools/dispatch/releaseDispatch.ts +66 -0
  377. package/src/tools/dispatch/types.ts +24 -0
  378. package/src/tools/dispatch/validation.ts +78 -0
  379. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  380. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +43 -28
  381. package/src/tools/goals/index.ts +3 -0
  382. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  383. package/src/tools/registry.ts +54 -1858
  384. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  385. package/src/tools/release/index.ts +2 -0
  386. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  387. package/src/tools/tasks/cancelTask.ts +109 -0
  388. package/src/tools/{createTask.ts → tasks/createTask.ts} +56 -33
  389. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
  390. package/src/tools/tasks/evidencePack.ts +504 -0
  391. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  392. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
  393. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  394. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
  395. package/src/tools/tasks/index.ts +18 -0
  396. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
  397. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
  398. package/src/tools/tasks/retryTask.ts +69 -0
  399. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +70 -35
  400. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  401. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  402. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  403. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  404. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  405. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  406. package/src/tools/workspace/index.ts +10 -0
  407. package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
  408. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  409. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  410. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  411. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  412. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  413. package/src/tools/workspace/syncFile.ts +292 -0
  414. package/src/utils/atomicFile.ts +58 -0
  415. package/src/utils/boundedFile.ts +116 -0
  416. package/src/utils/lockedJsonFile.ts +251 -0
  417. package/src/utils/platform.ts +31 -0
  418. package/src/utils/stableJson.ts +12 -0
  419. package/src/version.ts +3 -2
  420. package/src/watcherStatus.ts +19 -1
  421. package/ui/desktop-bootstrap.js +11 -0
  422. package/ui/desktop-bridge.js +36 -0
  423. package/ui/desktop.css +160 -0
  424. package/ui/getting-started.js +93 -0
  425. package/ui/i18n.js +484 -0
  426. package/ui/log-parser.js +30 -0
  427. package/ui/pages/audit.html +204 -3
  428. package/ui/pages/dashboard.html +937 -79
  429. package/ui/pages/direct-sessions.html +609 -60
  430. package/ui/pages/getting-started.html +43 -0
  431. package/ui/pages/logs.html +105 -22
  432. package/ui/pages/settings.html +101 -0
  433. package/ui/pages/task-detail.html +468 -438
  434. package/ui/pages/tasks.html +648 -65
  435. package/ui/pages/workspace.html +38 -1
  436. package/ui/settings.js +245 -0
  437. package/PatchWarden-Control.cmd +0 -6
  438. package/Restart-PatchWarden-Control.cmd +0 -6
  439. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  440. package/dist/test/unit/acceptance-engine.test.js +0 -228
  441. package/dist/test/unit/agent-router.test.d.ts +0 -1
  442. package/dist/test/unit/agent-router.test.js +0 -287
  443. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  444. package/dist/test/unit/android-doctor.test.js +0 -118
  445. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  446. package/dist/test/unit/audit-checks.test.js +0 -350
  447. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  448. package/dist/test/unit/chinese-path.test.js +0 -91
  449. package/dist/test/unit/command-guard.test.d.ts +0 -1
  450. package/dist/test/unit/command-guard.test.js +0 -160
  451. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  452. package/dist/test/unit/diagnose-task.test.js +0 -457
  453. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  454. package/dist/test/unit/direct-guards.test.js +0 -213
  455. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  456. package/dist/test/unit/discovery-token-store.test.js +0 -139
  457. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  458. package/dist/test/unit/evidence-pack.test.js +0 -130
  459. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  460. package/dist/test/unit/goal-graph.test.js +0 -298
  461. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  462. package/dist/test/unit/goal-progress.test.js +0 -381
  463. package/dist/test/unit/goal-status.test.d.ts +0 -1
  464. package/dist/test/unit/goal-status.test.js +0 -215
  465. package/dist/test/unit/goal-store.test.d.ts +0 -1
  466. package/dist/test/unit/goal-store.test.js +0 -253
  467. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  468. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  469. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  470. package/dist/test/unit/goal-tools-registry.test.js +0 -190
  471. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  472. package/dist/test/unit/handoff-export.test.js +0 -263
  473. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  474. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  475. package/dist/test/unit/logging.test.d.ts +0 -1
  476. package/dist/test/unit/logging.test.js +0 -397
  477. package/dist/test/unit/path-guard.test.d.ts +0 -1
  478. package/dist/test/unit/path-guard.test.js +0 -109
  479. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  480. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  481. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  482. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  483. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  484. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  485. package/dist/test/unit/release-gate.test.d.ts +0 -1
  486. package/dist/test/unit/release-gate.test.js +0 -242
  487. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  488. package/dist/test/unit/run-task-loop.test.js +0 -380
  489. package/dist/test/unit/safe-status.test.d.ts +0 -1
  490. package/dist/test/unit/safe-status.test.js +0 -165
  491. package/dist/test/unit/safe-views.test.d.ts +0 -1
  492. package/dist/test/unit/safe-views.test.js +0 -171
  493. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  494. package/dist/test/unit/schema-drift-check.test.js +0 -176
  495. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  496. package/dist/test/unit/sensitive-guard.test.js +0 -104
  497. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  498. package/dist/test/unit/subgoal-sync.test.js +0 -183
  499. package/dist/test/unit/sync-file.test.d.ts +0 -1
  500. package/dist/test/unit/sync-file.test.js +0 -154
  501. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  502. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  503. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  504. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  505. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  506. package/dist/test/unit/toolSearch.test.js +0 -571
  507. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  508. package/dist/test/unit/watcher-status.test.js +0 -169
  509. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  510. package/dist/test/unit/worktree-manager.test.js +0 -176
  511. package/dist/tools/applyPatch.js +0 -41
  512. package/dist/tools/cancelTask.d.ts +0 -30
  513. package/dist/tools/cancelTask.js +0 -64
  514. package/dist/tools/evidencePack.js +0 -168
  515. package/dist/tools/finalizeDirectSession.js +0 -84
  516. package/dist/tools/goalSubgoalTask.js +0 -110
  517. package/dist/tools/killTask.d.ts +0 -15
  518. package/dist/tools/retryTask.js +0 -45
  519. package/dist/tools/runVerification.js +0 -36
  520. package/dist/tools/syncFile.js +0 -65
  521. package/docs/control-center/control-center-daily-driver.md +0 -211
  522. package/docs/control-center/control-center-mvp.md +0 -205
  523. package/docs/control-center/control-center-phase2.md +0 -159
  524. package/docs/release-v0.6.0.md +0 -71
  525. package/docs/release-v0.6.1.md +0 -75
  526. package/docs/release-v0.6.4.md +0 -45
  527. package/src/test/unit/acceptance-engine.test.ts +0 -261
  528. package/src/test/unit/agent-router.test.ts +0 -342
  529. package/src/test/unit/android-doctor.test.ts +0 -158
  530. package/src/test/unit/audit-checks.test.ts +0 -567
  531. package/src/test/unit/chinese-path.test.ts +0 -106
  532. package/src/test/unit/command-guard.test.ts +0 -221
  533. package/src/test/unit/diagnose-task.test.ts +0 -544
  534. package/src/test/unit/direct-guards.test.ts +0 -297
  535. package/src/test/unit/discovery-token-store.test.ts +0 -181
  536. package/src/test/unit/evidence-pack.test.ts +0 -142
  537. package/src/test/unit/goal-graph.test.ts +0 -347
  538. package/src/test/unit/goal-progress.test.ts +0 -538
  539. package/src/test/unit/goal-status.test.ts +0 -270
  540. package/src/test/unit/goal-store.test.ts +0 -318
  541. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  542. package/src/test/unit/goal-tools-registry.test.ts +0 -243
  543. package/src/test/unit/handoff-export.test.ts +0 -295
  544. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  545. package/src/test/unit/logging.test.ts +0 -497
  546. package/src/test/unit/path-guard.test.ts +0 -150
  547. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  548. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  549. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  550. package/src/test/unit/release-gate.test.ts +0 -314
  551. package/src/test/unit/run-task-loop.test.ts +0 -425
  552. package/src/test/unit/safe-status.test.ts +0 -187
  553. package/src/test/unit/safe-views.test.ts +0 -184
  554. package/src/test/unit/schema-drift-check.test.ts +0 -260
  555. package/src/test/unit/sensitive-guard.test.ts +0 -124
  556. package/src/test/unit/subgoal-sync.test.ts +0 -236
  557. package/src/test/unit/sync-file.test.ts +0 -231
  558. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  559. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  560. package/src/test/unit/toolSearch.test.ts +0 -756
  561. package/src/test/unit/watcher-status.test.ts +0 -190
  562. package/src/test/unit/worktree-manager.test.ts +0 -247
  563. package/src/tools/cancelTask.ts +0 -70
  564. package/src/tools/evidencePack.ts +0 -205
  565. package/src/tools/retryTask.ts +0 -53
  566. package/src/tools/syncFile.ts +0 -122
  567. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  568. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  569. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  570. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  571. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  572. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  573. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  574. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  575. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  576. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  577. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  578. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  579. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  580. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  581. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  582. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  583. /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
  584. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  585. /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
  586. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  587. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  588. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  589. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  590. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  591. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  592. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  593. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  594. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  595. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  596. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
  597. /package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +0 -0
@@ -1,169 +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 } from "node:fs";
4
- import { join, dirname } from "node:path";
5
- import { tmpdir } from "node:os";
6
- import { readWatcherStatus, getWatcherHeartbeatPath } from "../../watcherStatus.js";
7
- function makeConfig(workspaceRoot) {
8
- return {
9
- workspaceRoot,
10
- plansDir: ".patchwarden/plans",
11
- tasksDir: ".patchwarden/tasks",
12
- assessmentsDir: ".patchwarden/assessments",
13
- assessmentTtlSeconds: 3600,
14
- agents: { codex: { command: "codex", args: ["exec", "{prompt}"] } },
15
- allowedTestCommands: ["npm test"],
16
- repoAllowedTestCommands: {},
17
- maxReadFileBytes: 200_000,
18
- defaultTaskTimeoutSeconds: 900,
19
- maxTaskTimeoutSeconds: 3600,
20
- watcherStaleSeconds: 30,
21
- directSessionsDir: ".patchwarden/direct-sessions",
22
- directSessionTtlSeconds: 3600,
23
- directMaxPatchBytes: 200_000,
24
- directMaxFileBytes: 500_000,
25
- };
26
- }
27
- describe("readWatcherStatus — watcher stale fallback", () => {
28
- let tempDir;
29
- let config;
30
- beforeEach(() => {
31
- tempDir = mkdtempSync(join(tmpdir(), "pw-watcher-test-"));
32
- config = makeConfig(tempDir);
33
- mkdirSync(join(tempDir, ".patchwarden", "tasks"), { recursive: true });
34
- });
35
- afterEach(() => {
36
- rmSync(tempDir, { recursive: true, force: true });
37
- });
38
- it("returns missing when no heartbeat and no running task", () => {
39
- const result = readWatcherStatus(config, Date.now());
40
- assert.equal(result.status, "missing");
41
- assert.equal(result.available, false);
42
- assert.equal(result.activity, null);
43
- });
44
- it("returns healthy when watcher heartbeat is fresh", () => {
45
- const heartbeatPath = getWatcherHeartbeatPath(config);
46
- mkdirSync(dirname(heartbeatPath), { recursive: true });
47
- writeFileSync(heartbeatPath, JSON.stringify({
48
- status: "running",
49
- pid: process.pid,
50
- last_heartbeat_at: new Date().toISOString(),
51
- }), "utf-8");
52
- const result = readWatcherStatus(config, Date.now());
53
- assert.equal(result.status, "healthy");
54
- assert.equal(result.available, true);
55
- assert.equal(result.activity, null);
56
- });
57
- it("returns stale when watcher heartbeat is old and no running task", () => {
58
- const heartbeatPath = getWatcherHeartbeatPath(config);
59
- mkdirSync(dirname(heartbeatPath), { recursive: true });
60
- const oldTime = new Date(Date.now() - 120_000).toISOString();
61
- writeFileSync(heartbeatPath, JSON.stringify({
62
- status: "running",
63
- pid: process.pid,
64
- last_heartbeat_at: oldTime,
65
- }), "utf-8");
66
- const result = readWatcherStatus(config, Date.now());
67
- assert.equal(result.status, "stale");
68
- assert.equal(result.available, false);
69
- assert.equal(result.activity, null);
70
- });
71
- it("returns healthy when watcher heartbeat is stale but a running task has fresh heartbeat", () => {
72
- // Write stale watcher heartbeat
73
- const heartbeatPath = getWatcherHeartbeatPath(config);
74
- mkdirSync(dirname(heartbeatPath), { recursive: true });
75
- const oldTime = new Date(Date.now() - 120_000).toISOString();
76
- writeFileSync(heartbeatPath, JSON.stringify({
77
- status: "running",
78
- pid: process.pid,
79
- last_heartbeat_at: oldTime,
80
- }), "utf-8");
81
- // Create a running task with fresh heartbeat
82
- const taskDir = join(tempDir, ".patchwarden", "tasks", "test-task-001");
83
- mkdirSync(taskDir, { recursive: true });
84
- writeFileSync(join(taskDir, "status.json"), JSON.stringify({
85
- status: "running",
86
- task_id: "test-task-001",
87
- }), "utf-8");
88
- writeFileSync(join(taskDir, "runtime.json"), JSON.stringify({
89
- phase: "executing_agent",
90
- last_heartbeat_at: new Date().toISOString(),
91
- current_command: "codex exec",
92
- }), "utf-8");
93
- const result = readWatcherStatus(config, Date.now());
94
- assert.equal(result.status, "healthy");
95
- assert.equal(result.available, true);
96
- assert.ok(result.activity);
97
- assert.match(result.activity, /test-task-001/);
98
- });
99
- it("returns healthy when watcher heartbeat is missing but a running task has fresh heartbeat", () => {
100
- // No watcher heartbeat file at all
101
- // Create a running task with fresh heartbeat
102
- const taskDir = join(tempDir, ".patchwarden", "tasks", "test-task-002");
103
- mkdirSync(taskDir, { recursive: true });
104
- writeFileSync(join(taskDir, "status.json"), JSON.stringify({
105
- status: "running",
106
- task_id: "test-task-002",
107
- }), "utf-8");
108
- writeFileSync(join(taskDir, "runtime.json"), JSON.stringify({
109
- phase: "running_tests",
110
- last_heartbeat_at: new Date().toISOString(),
111
- current_command: "npm test",
112
- }), "utf-8");
113
- const result = readWatcherStatus(config, Date.now());
114
- assert.equal(result.status, "healthy");
115
- assert.equal(result.available, true);
116
- assert.ok(result.activity);
117
- assert.match(result.activity, /test-task-002/);
118
- });
119
- it("returns stale when both watcher heartbeat and task heartbeat are old", () => {
120
- const heartbeatPath = getWatcherHeartbeatPath(config);
121
- mkdirSync(dirname(heartbeatPath), { recursive: true });
122
- const oldTime = new Date(Date.now() - 120_000).toISOString();
123
- writeFileSync(heartbeatPath, JSON.stringify({
124
- status: "running",
125
- pid: process.pid,
126
- last_heartbeat_at: oldTime,
127
- }), "utf-8");
128
- const taskDir = join(tempDir, ".patchwarden", "tasks", "test-task-003");
129
- mkdirSync(taskDir, { recursive: true });
130
- writeFileSync(join(taskDir, "status.json"), JSON.stringify({
131
- status: "running",
132
- task_id: "test-task-003",
133
- }), "utf-8");
134
- writeFileSync(join(taskDir, "runtime.json"), JSON.stringify({
135
- phase: "executing_agent",
136
- last_heartbeat_at: oldTime,
137
- current_command: "codex exec",
138
- }), "utf-8");
139
- const result = readWatcherStatus(config, Date.now());
140
- assert.equal(result.status, "stale");
141
- assert.equal(result.available, false);
142
- assert.equal(result.activity, null);
143
- });
144
- it("does not fall back to non-running tasks", () => {
145
- const heartbeatPath = getWatcherHeartbeatPath(config);
146
- mkdirSync(dirname(heartbeatPath), { recursive: true });
147
- const oldTime = new Date(Date.now() - 120_000).toISOString();
148
- writeFileSync(heartbeatPath, JSON.stringify({
149
- status: "running",
150
- pid: process.pid,
151
- last_heartbeat_at: oldTime,
152
- }), "utf-8");
153
- // Done task with fresh heartbeat should not trigger fallback
154
- const taskDir = join(tempDir, ".patchwarden", "tasks", "test-task-004");
155
- mkdirSync(taskDir, { recursive: true });
156
- writeFileSync(join(taskDir, "status.json"), JSON.stringify({
157
- status: "done",
158
- task_id: "test-task-004",
159
- }), "utf-8");
160
- writeFileSync(join(taskDir, "runtime.json"), JSON.stringify({
161
- phase: "completed",
162
- last_heartbeat_at: new Date().toISOString(),
163
- current_command: null,
164
- }), "utf-8");
165
- const result = readWatcherStatus(config, Date.now());
166
- assert.equal(result.status, "stale");
167
- assert.equal(result.available, false);
168
- });
169
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,176 +0,0 @@
1
- import { describe, it, before, after } from "node:test";
2
- import { strict as assert } from "node:assert";
3
- import { mkdtempSync, rmSync, writeFileSync, readFileSync, existsSync, readdirSync, unlinkSync, } from "node:fs";
4
- import { join } from "node:path";
5
- import { tmpdir } from "node:os";
6
- import { execSync } from "node:child_process";
7
- import { createWorktree, mergeWorktree, discardWorktree, readWorktreeStatus, getWorktreesDir, } from "../../goal/worktreeManager.js";
8
- import { PatchWardenError } from "../../errors.js";
9
- // ── Test fixtures ─────────────────────────────────────────────────
10
- /**
11
- * 在 dir 创建一个真实的 git 仓库:git init + 配置 user + 初始 commit。
12
- * createWorktree 要求 workspaceRoot 是一个至少有一个 commit 的 git 仓库。
13
- */
14
- function initGitRepo(dir) {
15
- execSync("git init", { cwd: dir, stdio: "ignore" });
16
- execSync('git config user.email "test@patchwarden.local"', { cwd: dir, stdio: "ignore" });
17
- execSync('git config user.name "PatchWarden Test"', { cwd: dir, stdio: "ignore" });
18
- execSync('git config commit.gpgsign false', { cwd: dir, stdio: "ignore" });
19
- writeFileSync(join(dir, "README.md"), "init\n", "utf-8");
20
- execSync("git add README.md", { cwd: dir, stdio: "ignore" });
21
- execSync("git commit -m init", { cwd: dir, stdio: "ignore" });
22
- }
23
- function gitBranchList(cwd) {
24
- return execSync("git branch --list", { cwd, stdio: ["pipe", "pipe", "ignore"] }).toString("utf-8");
25
- }
26
- function assertPatchWardenError(fn, reason) {
27
- try {
28
- fn();
29
- assert.fail(`expected PatchWardenError("${reason}") to be thrown`);
30
- }
31
- catch (err) {
32
- assert.ok(err instanceof PatchWardenError, `expected PatchWardenError, got: ${err}`);
33
- assert.equal(err.reason, reason);
34
- }
35
- }
36
- // ── Tests ─────────────────────────────────────────────────────────
37
- describe("worktreeManager", () => {
38
- let workspaceRoot;
39
- before(() => {
40
- workspaceRoot = mkdtempSync(join(tmpdir(), "pw-wt-"));
41
- initGitRepo(workspaceRoot);
42
- });
43
- after(() => {
44
- rmSync(workspaceRoot, { recursive: true, force: true });
45
- });
46
- // ── 1. createWorktree 成功 ──
47
- it("createWorktree creates an active worktree with valid status.json", () => {
48
- const result = createWorktree("goal_20260101_demo", "subgoal-001", workspaceRoot);
49
- assert.ok(result.worktreeId.startsWith("wt_"), `worktreeId should start with "wt_", got: ${result.worktreeId}`);
50
- assert.ok(result.branch.startsWith("pw-goal_20260101_demo-subgoal-001"), `branch should be pw-<goal>-<subgoal>, got: ${result.branch}`);
51
- assert.ok(existsSync(result.worktreePath), "worktreePath should exist");
52
- const statusFile = join(result.worktreePath, "worktree_status.json");
53
- assert.ok(existsSync(statusFile), "worktree_status.json should exist");
54
- // 原子写:不应残留 .tmp 文件
55
- assert.ok(!existsSync(statusFile + ".tmp"), "status.json.tmp should not remain after atomic write");
56
- // JSON 合法且字段正确
57
- const status = JSON.parse(readFileSync(statusFile, "utf-8"));
58
- assert.equal(status.worktree_id, result.worktreeId);
59
- assert.equal(status.goal_id, "goal_20260101_demo");
60
- assert.equal(status.subgoal_id, "subgoal-001");
61
- assert.equal(status.status, "active");
62
- assert.equal(status.branch, result.branch);
63
- assert.equal(status.path, result.worktreePath);
64
- assert.ok(typeof status.created_at === "string" && status.created_at.length > 0);
65
- // worktree 内应检出主仓库的文件(README.md)
66
- assert.ok(existsSync(join(result.worktreePath, "README.md")), "worktree should check out README.md");
67
- // branch 应在 git 中存在
68
- assert.ok(gitBranchList(workspaceRoot).includes(result.branch), "branch should exist in git");
69
- // readWorktreeStatus 应返回一致的状态
70
- const readBack = readWorktreeStatus(result.worktreeId, workspaceRoot);
71
- assert.ok(readBack !== null);
72
- assert.equal(readBack.status, "active");
73
- });
74
- // ── 2. createWorktree git 命令失败清理 ──
75
- it("createWorktree cleans up and throws when git fails (non-git workspaceRoot)", () => {
76
- const nonGitDir = mkdtempSync(join(tmpdir(), "pw-wt-nogit-"));
77
- try {
78
- // 非 git 目录 → git worktree add 失败
79
- assertPatchWardenError(() => createWorktree("goal_x", "subgoal-001", nonGitDir), "worktree_create_failed");
80
- // 不应残留任何 worktree 目录
81
- const worktreesDir = getWorktreesDir(nonGitDir);
82
- if (existsSync(worktreesDir)) {
83
- // 若 _workspacetrees 被创建,应为空(无半成品 worktree)
84
- const entries = readdirSync(worktreesDir);
85
- assert.equal(entries.length, 0, "no leftover worktree directories should remain after failure");
86
- }
87
- }
88
- finally {
89
- rmSync(nonGitDir, { recursive: true, force: true });
90
- }
91
- });
92
- // ── 3. mergeWorktree 成功 ──
93
- it("mergeWorktree merges the branch back and marks status merged", () => {
94
- const created = createWorktree("goal_merge", "subgoal-001", workspaceRoot);
95
- // 在 worktree 中产生一个新 commit,使 merge 有实际内容
96
- writeFileSync(join(created.worktreePath, "feature.txt"), "merged feature\n", "utf-8");
97
- execSync("git add feature.txt", { cwd: created.worktreePath, stdio: "ignore" });
98
- execSync("git commit -m feature", { cwd: created.worktreePath, stdio: "ignore" });
99
- const result = mergeWorktree(created.worktreeId, workspaceRoot);
100
- assert.equal(result.status, "merged");
101
- // 主工作区应出现 feature.txt(fast-forward 合并)
102
- assert.ok(existsSync(join(workspaceRoot, "feature.txt")), "feature.txt should appear in main workspace after merge");
103
- // status 应更新为 merged,merged_at 非空
104
- const status = readWorktreeStatus(created.worktreeId, workspaceRoot);
105
- assert.ok(status !== null);
106
- assert.equal(status.status, "merged");
107
- assert.ok(typeof status.merged_at === "string" && status.merged_at.length > 0, "merged_at should be set");
108
- });
109
- // ── 4. discardWorktree 成功 ──
110
- it("discardWorktree removes worktree, deletes branch, and archives status", () => {
111
- const created = createWorktree("goal_discard", "subgoal-001", workspaceRoot);
112
- const branch = created.branch;
113
- const worktreePath = created.worktreePath;
114
- const result = discardWorktree(created.worktreeId, workspaceRoot);
115
- assert.equal(result.status, "discarded");
116
- // worktree 目录应被移除
117
- assert.ok(!existsSync(worktreePath), "worktree directory should be removed after discard");
118
- // 临时 branch 应被删除
119
- assert.ok(!gitBranchList(workspaceRoot).includes(branch), "temporary branch should be deleted after discard");
120
- // 归档文件应存在且 status="discarded"
121
- const archiveFile = join(workspaceRoot, ".patchwarden", "worktree-archive", `${created.worktreeId}.json`);
122
- assert.ok(existsSync(archiveFile), "archived status file should exist");
123
- const archived = JSON.parse(readFileSync(archiveFile, "utf-8"));
124
- assert.equal(archived.status, "discarded");
125
- assert.ok(typeof archived.discarded_at === "string" && archived.discarded_at.length > 0, "discarded_at should be set");
126
- assert.equal(archived.worktree_id, created.worktreeId);
127
- });
128
- // ── 5. 路径逃逸拦截 ──
129
- it("blocks path escape via worktreeId containing ..", () => {
130
- // worktreeId 在 read/merge/discard 中由调用方提供,构造逃逸路径
131
- assertPatchWardenError(() => readWorktreeStatus("../../../outside", workspaceRoot), "workspace_path_escape");
132
- assertPatchWardenError(() => mergeWorktree("../../../outside", workspaceRoot), "workspace_path_escape");
133
- assertPatchWardenError(() => discardWorktree("../../../outside", workspaceRoot), "workspace_path_escape");
134
- });
135
- // ── 6. 敏感路径拦截 ──
136
- it("blocks sensitive worktreeId via guardSensitivePath", () => {
137
- // "credentials" 命中 sensitiveGuard 的敏感模式
138
- assertPatchWardenError(() => readWorktreeStatus("credentials", workspaceRoot), "sensitive_path_blocked");
139
- assertPatchWardenError(() => mergeWorktree("credentials", workspaceRoot), "sensitive_path_blocked");
140
- assertPatchWardenError(() => discardWorktree("credentials", workspaceRoot), "sensitive_path_blocked");
141
- });
142
- // ── 7. readWorktreeStatus 返回 null 当 status 不存在 ──
143
- it("readWorktreeStatus returns null when status file does not exist", () => {
144
- // 创建一个 worktree 目录但不含 status 文件
145
- const created = createWorktree("goal_missing", "subgoal-001", workspaceRoot);
146
- // 手动删除 status 文件模拟缺失
147
- unlinkSync(join(created.worktreePath, "worktree_status.json"));
148
- assert.equal(readWorktreeStatus(created.worktreeId, workspaceRoot), null);
149
- // 清理:删除该 worktree(避免污染后续测试)
150
- try {
151
- execSync(`git worktree remove --force "${created.worktreePath}"`, { cwd: workspaceRoot, stdio: "ignore" });
152
- }
153
- catch { /* ignore */ }
154
- try {
155
- execSync(`git branch -D ${created.branch}`, { cwd: workspaceRoot, stdio: "ignore" });
156
- }
157
- catch { /* ignore */ }
158
- });
159
- // ── 8. 非法状态转换:merge/discard 已 merged 的 worktree 抛错 ──
160
- it("rejects merge/discard on non-active worktree", () => {
161
- // 复用 #3 已 merged 的 worktree(goal_merge/subgoal-001)
162
- // 先找到它的 worktreeId:扫描 _workspacetrees 目录
163
- const worktreesDir = getWorktreesDir(workspaceRoot);
164
- let mergedId = null;
165
- for (const entry of readdirSync(worktreesDir)) {
166
- const s = readWorktreeStatus(entry, workspaceRoot);
167
- if (s && s.status === "merged" && s.goal_id === "goal_merge") {
168
- mergedId = entry;
169
- break;
170
- }
171
- }
172
- assert.ok(mergedId !== null, "should find the merged worktree from the merge test");
173
- assertPatchWardenError(() => mergeWorktree(mergedId, workspaceRoot), "invalid_worktree_state");
174
- assertPatchWardenError(() => discardWorktree(mergedId, workspaceRoot), "invalid_worktree_state");
175
- });
176
- });
@@ -1,41 +0,0 @@
1
- import { getConfig } from "../config.js";
2
- import { readDirectSession, appendDirectSessionOperation, } from "../direct/directSessionStore.js";
3
- import { guardDirectSessionActive, guardDirectWritePath, guardDirectPatchSize, } from "../direct/directGuards.js";
4
- import { applyPatchOperations, validateExpectedSha256, computeFileSha256, } from "../direct/directPatch.js";
5
- export function applyPatch(input) {
6
- const config = getConfig();
7
- // 1. Read session and guard active (not expired, not finalized)
8
- const session = readDirectSession(input.session_id);
9
- guardDirectSessionActive(session);
10
- // 2. Guard write path (inside repo, not sensitive, not binary,
11
- // not node_modules/release/dist)
12
- const resolvedPath = guardDirectWritePath(input.path, session.resolved_repo_path, config.workspaceRoot);
13
- // 3. Calculate patch size and guard against directMaxPatchBytes
14
- const patchBytes = JSON.stringify(input.operations).length;
15
- guardDirectPatchSize(patchBytes);
16
- // 4. Validate expected_sha256 matches current file hash
17
- const beforeSha256 = validateExpectedSha256(resolvedPath, input.expected_sha256);
18
- // 5. Apply patch operations
19
- const patchResult = applyPatchOperations(resolvedPath, input.operations);
20
- // Re-read the file from disk to compute the authoritative after_sha256
21
- const afterSha256 = computeFileSha256(resolvedPath);
22
- // 6. Append operation record to session
23
- appendDirectSessionOperation(input.session_id, {
24
- index: session.operations.length,
25
- timestamp: new Date().toISOString(),
26
- path: input.path,
27
- before_sha256: beforeSha256,
28
- after_sha256: afterSha256,
29
- operations_applied: patchResult.operations_applied,
30
- bytes_changed: patchResult.bytes_changed,
31
- });
32
- // 7. Return result
33
- return {
34
- path: input.path,
35
- before_sha256: beforeSha256,
36
- after_sha256: afterSha256,
37
- operations_applied: patchResult.operations_applied,
38
- bytes_changed: patchResult.bytes_changed,
39
- next_action: "Call run_verification to test the changes, or apply_patch for more edits.",
40
- };
41
- }
@@ -1,30 +0,0 @@
1
- export declare function cancelTask(taskId: string): {
2
- task_id: string;
3
- previous_status: string;
4
- new_status: string;
5
- message: string;
6
- cancel_requested?: undefined;
7
- force_kill_requested?: undefined;
8
- } | {
9
- task_id: string;
10
- previous_status: string;
11
- new_status: string;
12
- cancel_requested: boolean;
13
- force_kill_requested: boolean;
14
- message: string;
15
- };
16
- export declare function requestTaskTermination(taskId: string, force: boolean): {
17
- task_id: string;
18
- previous_status: string;
19
- new_status: string;
20
- message: string;
21
- cancel_requested?: undefined;
22
- force_kill_requested?: undefined;
23
- } | {
24
- task_id: string;
25
- previous_status: string;
26
- new_status: string;
27
- cancel_requested: boolean;
28
- force_kill_requested: boolean;
29
- message: string;
30
- };
@@ -1,64 +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
- export function cancelTask(taskId) {
7
- return requestTaskTermination(taskId, false);
8
- }
9
- export function requestTaskTermination(taskId, force) {
10
- const config = getConfig();
11
- const taskDir = join(getTasksDir(config), taskId);
12
- const statusFile = join(taskDir, "status.json");
13
- guardReadPath(statusFile, config.workspaceRoot, config.tasksDir);
14
- if (!existsSync(statusFile))
15
- throw new Error(`Task not found: "${taskId}"`);
16
- const data = JSON.parse(readFileSync(statusFile, "utf-8"));
17
- const currentStatus = data.status;
18
- if (["done", "done_by_agent", "failed", "failed_verification", "failed_scope_violation", "failed_policy_violation", "canceled"].includes(currentStatus)) {
19
- return {
20
- task_id: taskId,
21
- previous_status: currentStatus,
22
- new_status: currentStatus,
23
- message: `Task is already ${currentStatus}. No action taken.`,
24
- };
25
- }
26
- const now = new Date().toISOString();
27
- if (currentStatus === "pending") {
28
- data.status = "canceled";
29
- data.phase = "canceled";
30
- data.canceled_at = now;
31
- data.cancel_reason = force ? "Killed before execution by user request." : "Canceled by user request.";
32
- data.updated_at = now;
33
- writeFileSync(statusFile, JSON.stringify(data, null, 2), "utf-8");
34
- writeTaskProgress(taskDir, "canceled", { note: data.cancel_reason, heartbeatAt: now });
35
- return {
36
- task_id: taskId,
37
- previous_status: "pending",
38
- new_status: "canceled",
39
- message: "Pending task canceled. It will not be executed by watcher.",
40
- };
41
- }
42
- data.cancel_requested = true;
43
- data.cancel_requested_at = now;
44
- data.force_kill_requested = force;
45
- if (force)
46
- data.kill_requested_at = now;
47
- data.phase = force ? "terminating" : "canceling";
48
- data.updated_at = now;
49
- writeFileSync(statusFile, JSON.stringify(data, null, 2), "utf-8");
50
- writeTaskProgress(taskDir, data.phase, {
51
- note: force ? "Immediate termination requested." : "Graceful cancellation requested.",
52
- heartbeatAt: now,
53
- });
54
- return {
55
- task_id: taskId,
56
- previous_status: "running",
57
- new_status: "running",
58
- cancel_requested: true,
59
- force_kill_requested: force,
60
- message: force
61
- ? "Kill requested. The runner that owns the child process will terminate it."
62
- : "Cancel requested. The runner will stop the child process safely.",
63
- };
64
- }
@@ -1,168 +0,0 @@
1
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { join, resolve } from "node:path";
3
- import { getConfig } from "../config.js";
4
- import { guardReadPath, guardWorkspacePath } from "../security/pathGuard.js";
5
- import { redactSensitiveValue } from "../security/contentRedaction.js";
6
- import { getProjectPolicySummary } from "../policy/projectPolicy.js";
7
- import { getTaskLineage } from "./taskLineage.js";
8
- import { getLastToolCatalogSnapshot } from "./toolCatalog.js";
9
- import { PATCHWARDEN_VERSION, TOOL_SCHEMA_EPOCH } from "../version.js";
10
- import { PatchWardenError } from "../errors.js";
11
- export function exportTaskEvidencePack(input) {
12
- const lineageId = normalizeLineageId(input.lineage_id);
13
- const maxItems = normalizeMaxItems(input.max_items);
14
- const config = getConfig();
15
- const lineage = getTaskLineage(lineageId, { max_items: maxItems });
16
- const packDir = resolve(config.workspaceRoot, ".patchwarden", "evidence-packs", lineageId);
17
- guardWorkspacePath(packDir, config.workspaceRoot);
18
- mkdirSync(packDir, { recursive: true });
19
- const policySummary = readPolicySummary(lineage.repo_path);
20
- const catalog = safeCatalog();
21
- const generatedAt = new Date().toISOString();
22
- const safePack = redactSensitiveValue({
23
- evidence_pack_id: `evidence_${lineageId}`,
24
- lineage_id: lineageId,
25
- generated_at: generatedAt,
26
- path: packDir,
27
- files: {
28
- json: join(packDir, "evidence.json"),
29
- markdown: join(packDir, "EVIDENCE.md"),
30
- },
31
- lineage,
32
- policy: {
33
- valid: policySummary.valid,
34
- issue_count: policySummary.issues.length,
35
- release_readiness: policySummary.release_readiness,
36
- },
37
- catalog,
38
- omitted: ["stdout", "stderr", "diff", "verification command logs", "full artifact markdown", "sensitive file contents"],
39
- next_action: buildNextAction(lineage),
40
- bounded: true,
41
- }).value;
42
- writeFileSync(safePack.files.json, JSON.stringify(safePack, null, 2) + "\n", "utf-8");
43
- writeFileSync(safePack.files.markdown, buildEvidenceMarkdown(safePack), "utf-8");
44
- return safePack;
45
- }
46
- export function listEvidencePacks(options = {}) {
47
- const maxItems = normalizeMaxItems(options.max_items);
48
- const config = getConfig();
49
- const root = resolve(config.workspaceRoot, ".patchwarden", "evidence-packs");
50
- guardWorkspacePath(root, config.workspaceRoot);
51
- if (!existsSync(root))
52
- return { evidence_packs: [], total: 0, truncated: false };
53
- const packs = readdirSync(root, { withFileTypes: true })
54
- .filter((entry) => entry.isDirectory())
55
- .map((entry) => readEvidencePack(entry.name))
56
- .filter((entry) => entry !== null)
57
- .sort((a, b) => b.generated_at.localeCompare(a.generated_at));
58
- return {
59
- evidence_packs: packs.slice(0, maxItems),
60
- total: packs.length,
61
- truncated: packs.length > maxItems,
62
- };
63
- }
64
- export function readEvidencePack(lineageId) {
65
- const safeLineageId = normalizeLineageId(lineageId);
66
- const config = getConfig();
67
- const filePath = resolve(config.workspaceRoot, ".patchwarden", "evidence-packs", safeLineageId, "evidence.json");
68
- guardReadPath(filePath, config.workspaceRoot, ".patchwarden/evidence-packs");
69
- if (!existsSync(filePath))
70
- return null;
71
- const raw = readFileSync(filePath, "utf-8").replace(/^\uFEFF/, "");
72
- return redactSensitiveValue(JSON.parse(raw)).value;
73
- }
74
- function normalizeLineageId(value) {
75
- const lineageId = String(value || "").trim();
76
- if (!/^[A-Za-z0-9_-]+$/.test(lineageId)) {
77
- throw new PatchWardenError("invalid_lineage_id", "lineage_id may contain only letters, numbers, underscores, and hyphens.", "Pass a lineage_id returned by run_task_loop.", true, { lineage_id: lineageId });
78
- }
79
- return lineageId;
80
- }
81
- function normalizeMaxItems(value) {
82
- if (value === undefined)
83
- return 12;
84
- if (!Number.isInteger(value) || value < 1 || value > 50) {
85
- throw new Error("max_items must be an integer from 1 to 50.");
86
- }
87
- return value;
88
- }
89
- function readPolicySummary(repoPath) {
90
- try {
91
- const policy = getProjectPolicySummary(repoPath || ".");
92
- return {
93
- valid: Boolean(policy.valid),
94
- issues: Array.isArray(policy.issues) ? policy.issues : [],
95
- release_readiness: policy.release_readiness || null,
96
- };
97
- }
98
- catch (err) {
99
- return {
100
- valid: false,
101
- issues: [{ code: "policy_unavailable", message: err instanceof Error ? err.message : String(err) }],
102
- release_readiness: null,
103
- };
104
- }
105
- }
106
- function safeCatalog() {
107
- try {
108
- const catalog = getLastToolCatalogSnapshot();
109
- if (!catalog)
110
- throw new Error("catalog snapshot unavailable");
111
- return {
112
- server_version: catalog.server_version,
113
- schema_epoch: catalog.schema_epoch,
114
- tool_profile: catalog.tool_profile,
115
- tool_count: catalog.tool_count,
116
- tool_manifest_sha256: catalog.tool_manifest_sha256,
117
- };
118
- }
119
- catch {
120
- return {
121
- server_version: PATCHWARDEN_VERSION,
122
- schema_epoch: TOOL_SCHEMA_EPOCH,
123
- tool_profile: null,
124
- tool_count: null,
125
- tool_manifest_sha256: null,
126
- };
127
- }
128
- }
129
- function buildNextAction(lineage) {
130
- if (lineage.stop_reason === "success")
131
- return "Use this evidence pack for review or release readiness.";
132
- return lineage.next_action || "Review lineage before exporting release evidence.";
133
- }
134
- function buildEvidenceMarkdown(pack) {
135
- const direct = pack.lineage.tasks.direct_sessions
136
- .map((entry) => `- ${entry.session_id}: status=${entry.status || "unknown"}, audit=${entry.audit_decision || "unknown"}`)
137
- .join("\n");
138
- const rounds = pack.lineage.rounds
139
- .map((round) => `- ${round.role} ${round.task_id}: status=${round.status}, verification=${round.verification_status}, audit=${round.audit_verdict}`)
140
- .join("\n");
141
- return [
142
- "# PatchWarden Evidence Pack",
143
- "",
144
- `- Evidence pack: ${pack.evidence_pack_id}`,
145
- `- Lineage: ${pack.lineage_id}`,
146
- `- Generated: ${pack.generated_at}`,
147
- `- Final status: ${pack.lineage.final_status}`,
148
- `- Stop reason: ${pack.lineage.stop_reason}`,
149
- `- Worktree: ${pack.lineage.worktree.isolation_mode} (${pack.lineage.worktree.status})`,
150
- `- Tool manifest: ${pack.catalog.tool_manifest_sha256 || "unavailable"}`,
151
- "",
152
- "## Verification Rounds",
153
- rounds || "- None.",
154
- "",
155
- "## Direct Evidence",
156
- direct || "- None.",
157
- "",
158
- "## Policy And Release",
159
- `- Policy valid: ${pack.policy.valid}`,
160
- `- Policy issue count: ${pack.policy.issue_count}`,
161
- "",
162
- "## Omitted",
163
- ...pack.omitted.map((item) => `- ${item}`),
164
- "",
165
- `Next action: ${pack.next_action}`,
166
- "",
167
- ].join("\n");
168
- }