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
@@ -0,0 +1,387 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import { getConfig } from "../../config.js";
5
+ import { guardReadPath, guardWorkspacePath } from "../../security/pathGuard.js";
6
+ import { countRedactionsByCategory, redactSensitiveValue } from "../../security/contentRedaction.js";
7
+ import { getProjectPolicySummary } from "../../policy/projectPolicy.js";
8
+ import { getTaskLineage } from "./taskLineage.js";
9
+ import { getLastToolCatalogSnapshot } from "../catalog/toolCatalog.js";
10
+ import { PATCHWARDEN_VERSION, TOOL_SCHEMA_EPOCH } from "../../version.js";
11
+ import { PatchWardenError } from "../../errors.js";
12
+ import { buildGitEnvironment, resolveTrustedExecutable } from "../../runner/processSecurity.js";
13
+ import { atomicWriteFileSync, atomicWriteJsonFileSync } from "../../utils/atomicFile.js";
14
+ export function exportTaskEvidencePack(input) {
15
+ const lineageId = normalizeLineageId(input.lineage_id);
16
+ const maxItems = normalizeMaxItems(input.max_items);
17
+ const config = getConfig();
18
+ const lineage = getTaskLineage(lineageId, { max_items: maxItems });
19
+ const packDir = resolve(config.workspaceRoot, ".patchwarden", "evidence-packs", lineageId);
20
+ guardWorkspacePath(packDir, config.workspaceRoot);
21
+ mkdirSync(packDir, { recursive: true });
22
+ const policySummary = readPolicySummary(lineage.repo_path);
23
+ const catalog = safeCatalog();
24
+ const generatedAt = new Date().toISOString();
25
+ const filePaths = {
26
+ json: join(packDir, "evidence.json"),
27
+ markdown: join(packDir, "EVIDENCE.md"),
28
+ risk: join(packDir, "risk.json"),
29
+ verify: join(packDir, "verify.json"),
30
+ diffstat: join(packDir, "diffstat.json"),
31
+ lineage: join(packDir, "lineage.json"),
32
+ attestation: join(packDir, "attestation.json"),
33
+ redactions: join(packDir, "redactions.json"),
34
+ };
35
+ // Build v2 structured artifact contents (raw, before redaction).
36
+ const riskContent = buildRiskContent(lineage);
37
+ const verifyContent = buildVerifyContent(lineage);
38
+ const diffstatContent = buildDiffstatContent(lineage, config);
39
+ const lineageSummary = buildLineageSummary(lineage);
40
+ const attestationContent = buildAttestation(catalog, generatedAt, lineage.repo_path, config.workspaceRoot);
41
+ // Count redactions across all v2 raw contents for the audit trail.
42
+ const v2RawPayloads = [
43
+ riskContent,
44
+ verifyContent,
45
+ diffstatContent,
46
+ lineageSummary,
47
+ attestationContent,
48
+ ].map((value) => JSON.stringify(value));
49
+ const aggregated = new Map();
50
+ for (const raw of v2RawPayloads) {
51
+ for (const entry of countRedactionsByCategory(raw)) {
52
+ const existing = aggregated.get(entry.category);
53
+ if (existing)
54
+ existing.count += entry.count;
55
+ else
56
+ aggregated.set(entry.category, { category: entry.category, reason: entry.reason, count: entry.count });
57
+ }
58
+ }
59
+ const redactionsList = [...aggregated.values()];
60
+ const redactionsContent = {
61
+ redactions: redactionsList,
62
+ total_redacted: redactionsList.reduce((sum, entry) => sum + entry.count, 0),
63
+ bounded: true,
64
+ note: "Only categories and counts are recorded; original secret values are never persisted.",
65
+ };
66
+ // Redact and write each v2 file.
67
+ atomicWriteJsonFileSync(filePaths.risk, redactSensitiveValue(riskContent).value);
68
+ atomicWriteJsonFileSync(filePaths.verify, redactSensitiveValue(verifyContent).value);
69
+ atomicWriteJsonFileSync(filePaths.diffstat, redactSensitiveValue(diffstatContent).value);
70
+ atomicWriteJsonFileSync(filePaths.lineage, redactSensitiveValue(lineageSummary).value);
71
+ atomicWriteJsonFileSync(filePaths.attestation, redactSensitiveValue(attestationContent).value);
72
+ atomicWriteJsonFileSync(filePaths.redactions, redactSensitiveValue(redactionsContent).value);
73
+ const safePack = redactSensitiveValue({
74
+ evidence_pack_id: `evidence_${lineageId}`,
75
+ lineage_id: lineageId,
76
+ generated_at: generatedAt,
77
+ path: packDir,
78
+ files: filePaths,
79
+ lineage,
80
+ policy: {
81
+ valid: policySummary.valid,
82
+ issue_count: policySummary.issues.length,
83
+ release_readiness: policySummary.release_readiness,
84
+ },
85
+ catalog,
86
+ omitted: [
87
+ "stdout",
88
+ "stderr",
89
+ "diff",
90
+ "verification command logs",
91
+ "full artifact markdown",
92
+ "sensitive file contents",
93
+ "raw secret values (redactions.json stores only categories and counts)",
94
+ "full diff content (diffstat.json stores only file-level line counts)",
95
+ ],
96
+ next_action: buildNextAction(lineage),
97
+ bounded: true,
98
+ }).value;
99
+ atomicWriteJsonFileSync(safePack.files.json, safePack);
100
+ atomicWriteFileSync(safePack.files.markdown, buildEvidenceMarkdown(safePack));
101
+ return safePack;
102
+ }
103
+ export function listEvidencePacks(options = {}) {
104
+ const maxItems = normalizeMaxItems(options.max_items);
105
+ const config = getConfig();
106
+ const root = resolve(config.workspaceRoot, ".patchwarden", "evidence-packs");
107
+ guardWorkspacePath(root, config.workspaceRoot);
108
+ if (!existsSync(root))
109
+ return { evidence_packs: [], total: 0, truncated: false };
110
+ const packs = readdirSync(root, { withFileTypes: true })
111
+ .filter((entry) => entry.isDirectory())
112
+ .map((entry) => readEvidencePack(entry.name))
113
+ .filter((entry) => entry !== null)
114
+ .sort((a, b) => b.generated_at.localeCompare(a.generated_at));
115
+ return {
116
+ evidence_packs: packs.slice(0, maxItems),
117
+ total: packs.length,
118
+ truncated: packs.length > maxItems,
119
+ };
120
+ }
121
+ export function readEvidencePack(lineageId) {
122
+ const safeLineageId = normalizeLineageId(lineageId);
123
+ const config = getConfig();
124
+ const filePath = resolve(config.workspaceRoot, ".patchwarden", "evidence-packs", safeLineageId, "evidence.json");
125
+ guardReadPath(filePath, config.workspaceRoot, ".patchwarden/evidence-packs");
126
+ if (!existsSync(filePath))
127
+ return null;
128
+ const raw = readFileSync(filePath, "utf-8").replace(/^\uFEFF/, "");
129
+ return redactSensitiveValue(JSON.parse(raw)).value;
130
+ }
131
+ function normalizeLineageId(value) {
132
+ const lineageId = String(value || "").trim();
133
+ if (!/^[A-Za-z0-9_-]+$/.test(lineageId)) {
134
+ 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 });
135
+ }
136
+ return lineageId;
137
+ }
138
+ function normalizeMaxItems(value) {
139
+ if (value === undefined)
140
+ return 12;
141
+ if (!Number.isInteger(value) || value < 1 || value > 50) {
142
+ throw new Error("max_items must be an integer from 1 to 50.");
143
+ }
144
+ return value;
145
+ }
146
+ function readPolicySummary(repoPath) {
147
+ try {
148
+ const policy = getProjectPolicySummary(repoPath || ".");
149
+ return {
150
+ valid: Boolean(policy.valid),
151
+ issues: Array.isArray(policy.issues) ? policy.issues : [],
152
+ release_readiness: policy.release_readiness || null,
153
+ };
154
+ }
155
+ catch (err) {
156
+ return {
157
+ valid: false,
158
+ issues: [{ code: "policy_unavailable", message: err instanceof Error ? err.message : String(err) }],
159
+ release_readiness: null,
160
+ };
161
+ }
162
+ }
163
+ function safeCatalog() {
164
+ try {
165
+ const catalog = getLastToolCatalogSnapshot();
166
+ if (!catalog)
167
+ throw new Error("catalog snapshot unavailable");
168
+ return {
169
+ server_version: catalog.server_version,
170
+ schema_epoch: catalog.schema_epoch,
171
+ tool_profile: catalog.tool_profile,
172
+ tool_count: catalog.tool_count,
173
+ tool_manifest_sha256: catalog.tool_manifest_sha256,
174
+ };
175
+ }
176
+ catch {
177
+ return {
178
+ server_version: PATCHWARDEN_VERSION,
179
+ schema_epoch: TOOL_SCHEMA_EPOCH,
180
+ tool_profile: null,
181
+ tool_count: null,
182
+ tool_manifest_sha256: null,
183
+ };
184
+ }
185
+ }
186
+ function buildNextAction(lineage) {
187
+ if (lineage.stop_reason === "success")
188
+ return "Use this evidence pack for review or release readiness.";
189
+ return lineage.next_action || "Review lineage before exporting release evidence.";
190
+ }
191
+ function buildRiskContent(lineage) {
192
+ const risks = [];
193
+ for (const round of lineage.rounds) {
194
+ for (const check of round.fail_checks) {
195
+ risks.push({ source: "round", task_id: round.task_id, severity: "high", category: "fail_check", detail: check });
196
+ }
197
+ for (const check of round.warn_checks) {
198
+ risks.push({ source: "round", task_id: round.task_id, severity: "medium", category: "warn_check", detail: check });
199
+ }
200
+ }
201
+ for (const warning of lineage.warnings) {
202
+ risks.push({ source: "lineage", severity: "low", category: "warning", detail: warning });
203
+ }
204
+ const by_severity = {
205
+ high: risks.filter((risk) => risk.severity === "high").length,
206
+ medium: risks.filter((risk) => risk.severity === "medium").length,
207
+ low: risks.filter((risk) => risk.severity === "low").length,
208
+ };
209
+ return { risks, count: risks.length, by_severity };
210
+ }
211
+ function buildVerifyContent(lineage) {
212
+ const records = [];
213
+ for (const round of lineage.rounds) {
214
+ records.push({
215
+ source: "round",
216
+ task_id: round.task_id,
217
+ role: round.role,
218
+ verification_status: round.verification_status,
219
+ audit_verdict: round.audit_verdict,
220
+ passed: round.verification_status === "passed",
221
+ });
222
+ }
223
+ for (const session of lineage.tasks.direct_sessions) {
224
+ records.push({
225
+ source: "direct_session",
226
+ session_id: session.session_id,
227
+ verification_status: session.status || "unknown",
228
+ audit_verdict: session.audit_decision || "not_run",
229
+ passed: session.status === "passed",
230
+ command_count: session.command_count,
231
+ passed_commands: session.passed_commands,
232
+ failed_commands: session.failed_commands,
233
+ });
234
+ }
235
+ const passed = records.filter((record) => record.passed).length;
236
+ return {
237
+ records,
238
+ count: records.length,
239
+ summary: { total: records.length, passed, failed: records.length - passed },
240
+ latest_status: lineage.verification.latest_status,
241
+ overall_passed: lineage.verification.passed,
242
+ };
243
+ }
244
+ function buildDiffstatContent(lineage, config) {
245
+ const taskIds = [
246
+ lineage.tasks.main,
247
+ ...lineage.tasks.fix,
248
+ ...lineage.tasks.cleanup,
249
+ ].filter((id) => Boolean(id));
250
+ const files = [];
251
+ let totalAdditions = 0;
252
+ let totalDeletions = 0;
253
+ for (const taskId of taskIds) {
254
+ if (!/^[A-Za-z0-9_-]+$/.test(taskId))
255
+ continue;
256
+ const statsPath = resolve(config.workspaceRoot, config.tasksDir, taskId, "file-stats.json");
257
+ try {
258
+ guardWorkspacePath(statsPath, config.workspaceRoot);
259
+ if (!existsSync(statsPath))
260
+ continue;
261
+ const raw = readFileSync(statsPath, "utf-8").replace(/^\uFEFF/, "");
262
+ const stats = JSON.parse(raw);
263
+ if (!Array.isArray(stats.files))
264
+ continue;
265
+ for (const file of stats.files) {
266
+ const additions = Number(file.additions) || 0;
267
+ const deletions = Number(file.deletions) || 0;
268
+ files.push({
269
+ path: String(file.path || ""),
270
+ status: String(file.status || "unknown"),
271
+ additions,
272
+ deletions,
273
+ task_id: taskId,
274
+ });
275
+ totalAdditions += additions;
276
+ totalDeletions += deletions;
277
+ }
278
+ }
279
+ catch {
280
+ // Skip unreadable or malformed file-stats.json entries.
281
+ }
282
+ }
283
+ return {
284
+ files,
285
+ count: files.length,
286
+ totals: { additions: totalAdditions, deletions: totalDeletions },
287
+ };
288
+ }
289
+ function buildLineageSummary(lineage) {
290
+ return {
291
+ lineage_id: lineage.lineage_id,
292
+ goal: lineage.goal,
293
+ final_status: lineage.final_status,
294
+ stop_reason: lineage.stop_reason,
295
+ iterations_count: lineage.rounds.length,
296
+ task_counts: {
297
+ main: lineage.tasks.main ? 1 : 0,
298
+ fix: lineage.tasks.fix.length,
299
+ cleanup: lineage.tasks.cleanup.length,
300
+ direct_sessions: lineage.tasks.direct_sessions.length,
301
+ },
302
+ verification: lineage.verification,
303
+ worktree: {
304
+ isolation_mode: lineage.worktree.isolation_mode,
305
+ status: lineage.worktree.status,
306
+ },
307
+ agent_routing: lineage.agent_routing ? { selected_agent: lineage.agent_routing.selected_agent } : null,
308
+ warnings_count: lineage.warnings.length,
309
+ errors_count: lineage.errors.length,
310
+ truncated: lineage.truncated,
311
+ };
312
+ }
313
+ function readGitCommitShort(repoPath, workspaceRoot) {
314
+ try {
315
+ const cwd = guardWorkspacePath(repoPath || ".", workspaceRoot);
316
+ const env = buildGitEnvironment(cwd);
317
+ const git = resolveTrustedExecutable("git", cwd, { pathValue: env.PATH });
318
+ return execFileSync(git, ["rev-parse", "--short", "HEAD"], {
319
+ cwd,
320
+ encoding: "utf-8",
321
+ env,
322
+ stdio: ["ignore", "pipe", "ignore"],
323
+ timeout: 5000,
324
+ windowsHide: true,
325
+ }).trim();
326
+ }
327
+ catch {
328
+ return "unknown";
329
+ }
330
+ }
331
+ function buildAttestation(catalog, generatedAt, repoPath, workspaceRoot) {
332
+ return {
333
+ patchwarden_version: PATCHWARDEN_VERSION,
334
+ package_version: PATCHWARDEN_VERSION,
335
+ commit: readGitCommitShort(repoPath, workspaceRoot),
336
+ node_version: process.version,
337
+ os: { platform: process.platform, arch: process.arch },
338
+ tool_profile: catalog.tool_profile,
339
+ schema_epoch: catalog.schema_epoch,
340
+ generated_at: generatedAt,
341
+ };
342
+ }
343
+ function buildEvidenceMarkdown(pack) {
344
+ const direct = pack.lineage.tasks.direct_sessions
345
+ .map((entry) => `- ${entry.session_id}: status=${entry.status || "unknown"}, audit=${entry.audit_decision || "unknown"}`)
346
+ .join("\n");
347
+ const rounds = pack.lineage.rounds
348
+ .map((round) => `- ${round.role} ${round.task_id}: status=${round.status}, verification=${round.verification_status}, audit=${round.audit_verdict}`)
349
+ .join("\n");
350
+ return [
351
+ "# PatchWarden Evidence Pack",
352
+ "",
353
+ `- Evidence pack: ${pack.evidence_pack_id}`,
354
+ `- Lineage: ${pack.lineage_id}`,
355
+ `- Generated: ${pack.generated_at}`,
356
+ `- Final status: ${pack.lineage.final_status}`,
357
+ `- Stop reason: ${pack.lineage.stop_reason}`,
358
+ `- Worktree: ${pack.lineage.worktree.isolation_mode} (${pack.lineage.worktree.status})`,
359
+ `- Tool manifest: ${pack.catalog.tool_manifest_sha256 || "unavailable"}`,
360
+ "",
361
+ "## Verification Rounds",
362
+ rounds || "- None.",
363
+ "",
364
+ "## Direct Evidence",
365
+ direct || "- None.",
366
+ "",
367
+ "## Policy And Release",
368
+ `- Policy valid: ${pack.policy.valid}`,
369
+ `- Policy issue count: ${pack.policy.issue_count}`,
370
+ "",
371
+ "## Evidence Pack v2 Files",
372
+ "- `evidence.json` — full bounded evidence pack (machine-readable).",
373
+ "- `EVIDENCE.md` — human-readable evidence summary.",
374
+ "- `risk.json` — aggregated risk items with severity (high/medium/low).",
375
+ "- `verify.json` — structured verification records per round and direct session.",
376
+ "- `diffstat.json` — file-level additions/deletions without full diff content.",
377
+ "- `lineage.json` — bounded lineage summary (goal, status, task counts).",
378
+ "- `attestation.json` — version, commit, Node/OS, tool profile, schema epoch.",
379
+ "- `redactions.json` — redaction categories and counts (no original secret values).",
380
+ "",
381
+ "## Omitted",
382
+ ...pack.omitted.map((item) => `- ${item}`),
383
+ "",
384
+ `Next action: ${pack.next_action}`,
385
+ "",
386
+ ].join("\n");
387
+ }
@@ -1,9 +1,9 @@
1
1
  import { readFileSync, existsSync, statSync } from "node:fs";
2
2
  import { resolve, join } from "node:path";
3
- import { getTasksDir, getConfig } from "../config.js";
4
- import { guardPath } from "../security/pathGuard.js";
5
- import { guardSensitivePath } from "../security/sensitiveGuard.js";
6
- import { redactSensitiveContent } from "../security/contentRedaction.js";
3
+ import { getTasksDir, getConfig } from "../../config.js";
4
+ import { guardPath } from "../../security/pathGuard.js";
5
+ import { guardSensitivePath } from "../../security/sensitiveGuard.js";
6
+ import { redactSensitiveContent } from "../../security/contentRedaction.js";
7
7
  import { getTaskStatus } from "./getTaskStatus.js";
8
8
  export const TASK_READ_ONLY_FILES = [
9
9
  "result.md",
@@ -1,6 +1,6 @@
1
1
  import type { TaskStatus } from "./createTask.js";
2
2
  import type { TaskPhase } from "./createTask.js";
3
- import { type PendingReason, type WatcherStatusSnapshot } from "../watcherStatus.js";
3
+ import { type PendingReason, type WatcherStatusSnapshot } from "../../watcherStatus.js";
4
4
  export interface GetTaskStatusOutput {
5
5
  task_id: string;
6
6
  plan_id: string;
@@ -28,6 +28,11 @@ export interface GetTaskStatusOutput {
28
28
  path: string;
29
29
  change: string;
30
30
  }>;
31
+ new_out_of_scope_changes?: Array<{
32
+ path: string;
33
+ change: string;
34
+ }>;
35
+ artifact_hygiene_counts?: Record<string, unknown>;
31
36
  verify_status?: "passed" | "failed" | "skipped";
32
37
  verify_commands?: string[];
33
38
  diff_available?: boolean;
@@ -1,10 +1,10 @@
1
1
  import { readFileSync, existsSync } from "node:fs";
2
2
  import { resolve, join } from "node:path";
3
- import { getTasksDir, getConfig } from "../config.js";
4
- import { guardReadPath } from "../security/pathGuard.js";
5
- import { guardSensitivePath } from "../security/sensitiveGuard.js";
6
- import { readTaskRuntime } from "../taskRuntime.js";
7
- import { derivePendingReason, readWatcherStatus, } from "../watcherStatus.js";
3
+ import { getTasksDir, getConfig } from "../../config.js";
4
+ import { guardReadPath } from "../../security/pathGuard.js";
5
+ import { guardSensitivePath } from "../../security/sensitiveGuard.js";
6
+ import { readTaskRuntime } from "../../runner/taskRuntime.js";
7
+ import { derivePendingReason, readWatcherStatus, } from "../../watcherStatus.js";
8
8
  export function getTaskStatus(taskId) {
9
9
  const config = getConfig();
10
10
  const tasksDir = getTasksDir(config);
@@ -1,8 +1,9 @@
1
- import { readFileSync, existsSync } from "node:fs";
1
+ import { existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { getTasksDir, getConfig } from "../config.js";
4
- import { guardReadPath } from "../security/pathGuard.js";
5
- import { redactSensitiveContent } from "../security/contentRedaction.js";
3
+ import { getTasksDir, getConfig } from "../../config.js";
4
+ import { guardReadPath } from "../../security/pathGuard.js";
5
+ import { redactSensitiveContent } from "../../security/contentRedaction.js";
6
+ import { readTextFilePrefixSync, readTextFileTailLinesSync } from "../../utils/boundedFile.js";
6
7
  export function getTaskStdoutTail(taskId, lines = 80) {
7
8
  const config = getConfig();
8
9
  const tasksDir = getTasksDir(config);
@@ -16,29 +17,30 @@ export function getTaskStdoutTail(taskId, lines = 80) {
16
17
  const stdoutLog = join(taskDir, "stdout.log");
17
18
  const stderrLog = join(taskDir, "stderr.log");
18
19
  const resultFile = join(taskDir, "result.md");
19
- const maxLines = Math.min(lines, 200);
20
+ const maxLines = Number.isFinite(lines)
21
+ ? Math.max(1, Math.min(200, Math.trunc(lines)))
22
+ : 80;
23
+ const stderrLines = Math.min(maxLines, 40);
20
24
  let stdoutTail = "";
21
25
  let stderrTail = "";
22
26
  let source = "none";
23
27
  // All log files are optional — don't throw if missing
24
28
  if (existsSync(stdoutLog)) {
25
- const raw = readFileSync(stdoutLog, "utf-8");
26
- stdoutTail = raw.split("\n").slice(-maxLines).join("\n");
29
+ stdoutTail = readTextFileTailLinesSync(stdoutLog, maxLines, config.maxReadFileBytes);
27
30
  source = "stdout.log";
28
31
  }
29
32
  if (existsSync(stderrLog)) {
30
- const raw = readFileSync(stderrLog, "utf-8");
31
- stderrTail = raw.split("\n").slice(-Math.min(maxLines, 40)).join("\n");
33
+ stderrTail = readTextFileTailLinesSync(stderrLog, stderrLines, config.maxReadFileBytes);
32
34
  }
33
35
  // Fallback to result.md
34
36
  if (!stdoutTail && existsSync(resultFile)) {
35
- const raw = readFileSync(resultFile, "utf-8");
37
+ const raw = readTextFilePrefixSync(resultFile, config.maxReadFileBytes).content;
36
38
  const stdoutMatch = raw.match(/## Agent stdout\s*\n+```\s*([\s\S]*?)```/i);
37
39
  const stderrMatch = raw.match(/## Agent stderr\s*\n+```\s*([\s\S]*?)```/i);
38
40
  if (stdoutMatch)
39
41
  stdoutTail = stdoutMatch[1].split("\n").slice(-maxLines).join("\n");
40
42
  if (stderrMatch && !stderrTail)
41
- stderrTail = stderrMatch[1].split("\n").slice(-Math.min(maxLines, 40)).join("\n");
43
+ stderrTail = stderrMatch[1].split("\n").slice(-stderrLines).join("\n");
42
44
  if (source === "none")
43
45
  source = "result.md";
44
46
  }
@@ -1,9 +1,10 @@
1
- import { existsSync, readFileSync } from "node:fs";
1
+ import { existsSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
- import { getConfig, getTasksDir } from "../config.js";
4
- import { guardReadPath } from "../security/pathGuard.js";
3
+ import { getConfig, getTasksDir } from "../../config.js";
4
+ import { guardReadPath } from "../../security/pathGuard.js";
5
5
  import { getTaskStatus } from "./getTaskStatus.js";
6
- import { redactSensitiveValue } from "../security/contentRedaction.js";
6
+ import { redactSensitiveValue } from "../../security/contentRedaction.js";
7
+ import { readTextFilePrefixSync, readTextFileTailLinesSync, } from "../../utils/boundedFile.js";
7
8
  const TERMINAL_STATUSES = new Set([
8
9
  "done",
9
10
  "done_by_agent",
@@ -30,7 +31,8 @@ export function getTaskSummary(taskId, options = {}) {
30
31
  ?? status.new_out_of_scope_changes
31
32
  ?? result.out_of_scope_changes
32
33
  ?? status.out_of_scope_changes);
33
- const verifyStatus = String(verify.status ?? result.verify_status ?? result.verify?.status ?? status.verify_status ?? "not_available");
34
+ const resultVerify = asRecord(result.verify);
35
+ const verifyStatus = String(verify.status ?? result.verify_status ?? resultVerify.status ?? status.verify_status ?? "not_available");
34
36
  const errors = [status.error, ...asArray(result.errors), ...asArray(result.known_issues)]
35
37
  .filter((value) => typeof value === "string" && value.trim() !== "");
36
38
  const warnings = asArray(result.warnings).filter((value) => typeof value === "string");
@@ -61,16 +63,16 @@ export function getTaskSummary(taskId, options = {}) {
61
63
  const acceptanceFile = join(taskDir, "acceptance.json");
62
64
  if (existsSync(acceptanceFile)) {
63
65
  try {
64
- const acceptance = JSON.parse(readFileSync(acceptanceFile, "utf-8"));
66
+ const acceptance = asRecord(JSON.parse(readTextFilePrefixSync(acceptanceFile, 1024 * 1024).content));
65
67
  if (acceptance.status === "accepted") {
66
68
  acceptanceStatus = "ready_for_review";
67
- acceptanceReviewedAt = acceptance.reviewed_at || null;
68
- acceptanceReviewer = acceptance.reviewer || null;
69
+ acceptanceReviewedAt = typeof acceptance.reviewed_at === "string" ? acceptance.reviewed_at : null;
70
+ acceptanceReviewer = typeof acceptance.reviewer === "string" ? acceptance.reviewer : null;
69
71
  }
70
72
  else if (acceptance.status === "rejected") {
71
73
  acceptanceStatus = "failed";
72
- acceptanceReviewedAt = acceptance.reviewed_at || null;
73
- acceptanceReviewer = acceptance.reviewer || null;
74
+ acceptanceReviewedAt = typeof acceptance.reviewed_at === "string" ? acceptance.reviewed_at : null;
75
+ acceptanceReviewer = typeof acceptance.reviewer === "string" ? acceptance.reviewer : null;
74
76
  if (acceptance.notes) {
75
77
  warnings.push(`Task was rejected by ${acceptance.reviewer || "human"} at ${acceptance.reviewed_at || "unknown time"}: ${acceptance.notes}`);
76
78
  }
@@ -102,11 +104,13 @@ export function getTaskSummary(taskId, options = {}) {
102
104
  const artifactHygiene = asRecord(result.artifact_hygiene ?? changedFilesRead.data.artifact_hygiene ?? {
103
105
  counts: status.artifact_hygiene_counts || {},
104
106
  });
105
- const verifyCommands = asArray(verify.commands ?? result.verify_commands ?? result.verify?.commands);
107
+ const verifyCommands = asArray(verify.commands ?? result.verify_commands ?? resultVerify.commands);
106
108
  const testLogSummary = summarizeTestLog(join(taskDir, "test.log"));
107
109
  const verificationSummary = buildVerificationSummary(verifyStatus, verifyCommands, testLogSummary);
108
110
  // Extract failed command detail from verify records
109
- const failedVerify = verifyCommands.find((cmd) => ["failed", "timed_out", "canceled"].includes(cmd?.status));
111
+ const failedVerify = verifyCommands
112
+ .map(asRecord)
113
+ .find((command) => ["failed", "timed_out", "canceled"].includes(String(command.status || "")));
110
114
  const failedCommandDetail = failedVerify ? {
111
115
  command: String(failedVerify.command || ""),
112
116
  exit_code: failedVerify.exit_code ?? null,
@@ -145,7 +149,7 @@ export function getTaskSummary(taskId, options = {}) {
145
149
  test_summary: verificationSummary.headline,
146
150
  diff_available: Boolean((status.diff_available ?? (changedFiles.length > 0)) &&
147
151
  (artifacts["diff.patch"] || artifacts["git.diff"])),
148
- diff_truncated: Boolean(status.diff_truncated || result.warnings?.some?.((warning) => warning.includes("diff.patch was truncated"))),
152
+ diff_truncated: Boolean(status.diff_truncated || asArray(result.warnings).some((warning) => typeof warning === "string" && warning.includes("diff.patch was truncated"))),
149
153
  result_available: artifacts["result.md"],
150
154
  result_json_available: artifacts["result.json"],
151
155
  verify_available: artifacts["verify.json"],
@@ -194,7 +198,9 @@ function buildCompactSummary(output, maxItems) {
194
198
  const manifestRead = tryReadJson(join(taskDir, "artifact_manifest.json"));
195
199
  const manifest = asRecord(manifestRead.data);
196
200
  const releaseArtifacts = asArray(manifest.artifacts);
197
- const releaseArtifactPaths = releaseArtifacts.map((a) => String(a.path || "")).slice(0, maxItems);
201
+ const releaseArtifactPaths = releaseArtifacts
202
+ .map((artifact) => String(asRecord(artifact).path || ""))
203
+ .slice(0, maxItems);
198
204
  const compact = {
199
205
  view: "compact",
200
206
  task_id: String(output.task_id),
@@ -243,7 +249,7 @@ function tryReadJson(path) {
243
249
  if (!existsSync(path))
244
250
  return { data: {} };
245
251
  try {
246
- return { data: JSON.parse(readFileSync(path, "utf-8")) };
252
+ return { data: asRecord(JSON.parse(readTextFilePrefixSync(path, 2 * 1024 * 1024).content)) };
247
253
  }
248
254
  catch (error) {
249
255
  return { data: {}, error: error instanceof Error ? error.message : String(error) };
@@ -253,27 +259,30 @@ function asArray(value) {
253
259
  return Array.isArray(value) ? value : [];
254
260
  }
255
261
  function asRecord(value) {
256
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
262
+ return value && typeof value === "object" && !Array.isArray(value)
263
+ ? value
264
+ : {};
257
265
  }
258
266
  function summarizeTestLog(path) {
259
267
  if (!existsSync(path))
260
268
  return "test.log missing";
261
- const text = readFileSync(path, "utf-8");
269
+ const text = readTextFileTailLinesSync(path, 200, 512 * 1024);
262
270
  const exit = text.match(/Exit\s*code:\s*([^\r\n]+)/i)?.[1]?.trim();
263
271
  return exit ? `Exit code: ${exit}` : text.trim().slice(0, 500) || "test.log empty";
264
272
  }
265
273
  function readResultFallback(path) {
266
274
  if (!existsSync(path))
267
275
  return "";
268
- const text = readFileSync(path, "utf-8");
276
+ const text = readTextFilePrefixSync(path, 1024 * 1024).content;
269
277
  return text.match(/## Summary\s+([\s\S]*?)(?:\n## |\n---|$)/i)?.[1]?.trim().slice(0, 1000) || "";
270
278
  }
271
279
  function buildVerificationSummary(status, commands, testLogSummary) {
272
- const passed = commands.filter((command) => command?.status === "passed").length;
273
- const failed = commands.filter((command) => ["failed", "timed_out", "canceled"].includes(command?.status)).length;
274
- const skipped = commands.filter((command) => command?.status === "skipped").length;
280
+ const commandRecords = commands.map(asRecord);
281
+ const passed = commandRecords.filter((command) => command.status === "passed").length;
282
+ const failed = commandRecords.filter((command) => ["failed", "timed_out", "canceled"].includes(String(command.status || ""))).length;
283
+ const skipped = commandRecords.filter((command) => command.status === "skipped").length;
275
284
  const evidenceText = [
276
- ...commands.flatMap((command) => [command?.stdout_tail, command?.stderr_tail]),
285
+ ...commandRecords.flatMap((command) => [command.stdout_tail, command.stderr_tail]),
277
286
  testLogSummary,
278
287
  ].filter((value) => typeof value === "string").join("\n");
279
288
  const headline = extractTestHeadline(evidenceText)
@@ -304,10 +313,8 @@ function readLogTail(path, lines) {
304
313
  if (!existsSync(path))
305
314
  return "(file not found)";
306
315
  try {
307
- const raw = readFileSync(path, "utf-8");
308
- if (raw.length === 0)
309
- return "(empty)";
310
- return raw.split("\n").slice(-lines).join("\n");
316
+ const tail = readTextFileTailLinesSync(path, lines);
317
+ return tail || "(empty)";
311
318
  }
312
319
  catch {
313
320
  return "(unreadable)";
@@ -0,0 +1,18 @@
1
+ export * from "./cancelTask.js";
2
+ export * from "./createTask.js";
3
+ export * from "./diagnoseTask.js";
4
+ export * from "./evidencePack.js";
5
+ export * from "./getTaskFile.js";
6
+ export * from "./getTaskProgress.js";
7
+ export * from "./getTaskStatus.js";
8
+ export * from "./getTaskStdoutTail.js";
9
+ export * from "./getTaskSummary.js";
10
+ export * from "./killTask.js";
11
+ export * from "./listTasks.js";
12
+ export * from "./reconcileTasks.js";
13
+ export * from "./retryTask.js";
14
+ export * from "./runTaskLoop.js";
15
+ export * from "./runVerification.js";
16
+ export * from "./taskLineage.js";
17
+ export * from "./taskOutputs.js";
18
+ export * from "./waitForTask.js";