patchwarden 1.5.1 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (573) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +152 -25
  3. package/README.md +120 -23
  4. package/dist/assessments/agentAssessor.js +21 -12
  5. package/dist/assessments/assessmentStore.js +42 -34
  6. package/dist/config.d.ts +15 -0
  7. package/dist/config.js +66 -13
  8. package/dist/control/fileManager.d.ts +2 -0
  9. package/dist/control/fileManager.js +39 -0
  10. package/dist/control/middleware/auth.d.ts +6 -0
  11. package/dist/control/middleware/auth.js +14 -5
  12. package/dist/control/middleware/static.js +16 -3
  13. package/dist/control/routeTable.d.ts +24 -0
  14. package/dist/control/routeTable.js +263 -0
  15. package/dist/control/routes/audit.js +17 -7
  16. package/dist/control/routes/evidence.js +1 -1
  17. package/dist/control/routes/lineage.js +1 -1
  18. package/dist/control/routes/process.d.ts +7 -0
  19. package/dist/control/routes/process.js +329 -42
  20. package/dist/control/routes/sessions.d.ts +9 -1
  21. package/dist/control/routes/sessions.js +84 -21
  22. package/dist/control/routes/status.d.ts +46 -1
  23. package/dist/control/routes/status.js +72 -15
  24. package/dist/control/routes/taskActions.js +24 -60
  25. package/dist/control/routes/tasks.d.ts +4 -0
  26. package/dist/control/routes/tasks.js +61 -62
  27. package/dist/control/routes/workspace.js +50 -23
  28. package/dist/control/runtime.d.ts +3 -2
  29. package/dist/control/runtime.js +63 -30
  30. package/dist/control/server.js +36 -365
  31. package/dist/control/shared.d.ts +25 -1
  32. package/dist/control/shared.js +64 -11
  33. package/dist/direct/directAudit.js +234 -225
  34. package/dist/direct/directGuards.d.ts +2 -1
  35. package/dist/direct/directGuards.js +42 -29
  36. package/dist/direct/directPatch.d.ts +6 -1
  37. package/dist/direct/directPatch.js +55 -8
  38. package/dist/direct/directSessionStore.d.ts +16 -6
  39. package/dist/direct/directSessionStore.js +127 -35
  40. package/dist/direct/directVerification.js +8 -16
  41. package/dist/doctor.js +62 -52
  42. package/dist/goal/acceptanceEngine.d.ts +1 -1
  43. package/dist/goal/goalProgress.js +75 -69
  44. package/dist/goal/goalReport.js +5 -12
  45. package/dist/goal/goalStatus.d.ts +2 -0
  46. package/dist/goal/goalStatus.js +10 -3
  47. package/dist/goal/goalStore.d.ts +9 -2
  48. package/dist/goal/goalStore.js +152 -45
  49. package/dist/goal/handoffExport.js +6 -6
  50. package/dist/goal/specKitImport.js +97 -96
  51. package/dist/goal/subgoalSync.js +11 -9
  52. package/dist/goal/worktreeManager.d.ts +2 -0
  53. package/dist/goal/worktreeManager.js +209 -136
  54. package/dist/httpServer.js +111 -32
  55. package/dist/logging.js +5 -3
  56. package/dist/policy/projectPolicy.js +5 -2
  57. package/dist/release/releaseGate.d.ts +1 -1
  58. package/dist/release/releaseGate.js +78 -25
  59. package/dist/runner/agentInvocation.d.ts +14 -0
  60. package/dist/runner/agentInvocation.js +96 -4
  61. package/dist/runner/changeCapture.d.ts +13 -5
  62. package/dist/runner/changeCapture.js +243 -61
  63. package/dist/runner/postTaskCleanup.js +48 -19
  64. package/dist/runner/processSecurity.d.ts +56 -0
  65. package/dist/runner/processSecurity.js +373 -0
  66. package/dist/runner/runTask.d.ts +1 -1
  67. package/dist/runner/runTask.js +115 -87
  68. package/dist/runner/simpleProcess.d.ts +3 -0
  69. package/dist/runner/simpleProcess.js +50 -36
  70. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  71. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  72. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
  73. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  74. package/dist/runner/taskStatusStore.d.ts +11 -0
  75. package/dist/runner/taskStatusStore.js +24 -0
  76. package/dist/runner/watch.d.ts +14 -1
  77. package/dist/runner/watch.js +256 -48
  78. package/dist/security/contentRedaction.js +2 -2
  79. package/dist/security/discoveryTokenStore.d.ts +3 -2
  80. package/dist/security/discoveryTokenStore.js +17 -2
  81. package/dist/security/loopbackHost.d.ts +2 -0
  82. package/dist/security/loopbackHost.js +16 -0
  83. package/dist/security/pathGuard.js +32 -17
  84. package/dist/security/planGuard.js +2 -1
  85. package/dist/security/runtimeGuard.js +9 -6
  86. package/dist/security/secretComparison.d.ts +3 -0
  87. package/dist/security/secretComparison.js +11 -0
  88. package/dist/security/sensitiveGuard.d.ts +5 -0
  89. package/dist/security/sensitiveGuard.js +27 -8
  90. package/dist/security/toolInvocationGuard.d.ts +2 -2
  91. package/dist/security/toolInvocationGuard.js +1 -1
  92. package/dist/security/workspaceRootGuard.d.ts +8 -0
  93. package/dist/security/workspaceRootGuard.js +47 -0
  94. package/dist/smoke-test.js +24 -23
  95. package/dist/tools/catalog/index.d.ts +4 -0
  96. package/dist/tools/catalog/index.js +4 -0
  97. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  98. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  99. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +6 -15
  100. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  101. package/dist/tools/definitions/toolDefs.js +1177 -0
  102. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  103. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  104. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +1 -1
  105. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +99 -29
  106. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
  107. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +7 -7
  108. package/dist/tools/diagnostics/index.d.ts +6 -0
  109. package/dist/tools/diagnostics/index.js +6 -0
  110. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  111. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  112. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +12 -8
  113. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +23 -13
  114. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  115. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  116. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +6 -6
  117. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +1 -1
  118. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  119. package/dist/tools/direct/index.d.ts +3 -0
  120. package/dist/tools/direct/index.js +3 -0
  121. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  122. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  123. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  124. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  125. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  126. package/dist/tools/discovery/index.d.ts +3 -0
  127. package/dist/tools/discovery/index.js +3 -0
  128. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  129. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  130. package/dist/tools/dispatch/coreDispatch.js +32 -31
  131. package/dist/tools/dispatch/diagnosticDispatch.d.ts +3 -5
  132. package/dist/tools/dispatch/diagnosticDispatch.js +70 -68
  133. package/dist/tools/dispatch/directDispatch.js +11 -10
  134. package/dist/tools/dispatch/goalDispatch.js +5 -4
  135. package/dist/tools/dispatch/releaseDispatch.js +3 -2
  136. package/dist/tools/dispatch/validation.d.ts +7 -0
  137. package/dist/tools/dispatch/validation.js +71 -0
  138. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  139. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +1 -1
  140. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  141. package/dist/tools/goals/index.d.ts +3 -0
  142. package/dist/tools/goals/index.js +3 -0
  143. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  144. package/dist/tools/registry.d.ts +7 -11
  145. package/dist/tools/registry.js +17 -1178
  146. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  147. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  148. package/dist/tools/release/index.d.ts +2 -0
  149. package/dist/tools/release/index.js +2 -0
  150. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  151. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  152. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  153. package/dist/tools/tasks/cancelTask.js +85 -0
  154. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +10 -2
  155. package/dist/tools/{createTask.js → tasks/createTask.js} +42 -27
  156. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  157. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
  158. package/dist/tools/{evidencePack.js → tasks/evidencePack.js} +34 -22
  159. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  160. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  161. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
  162. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  163. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
  164. package/dist/tools/tasks/index.d.ts +18 -0
  165. package/dist/tools/tasks/index.js +18 -0
  166. package/dist/tools/tasks/killTask.d.ts +1 -0
  167. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  168. package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
  169. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
  170. package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
  171. package/dist/tools/tasks/retryTask.js +60 -0
  172. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  173. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +58 -30
  174. package/dist/tools/tasks/runVerification.js +38 -0
  175. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  176. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  177. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  178. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  179. package/dist/tools/workspace/applyPatch.js +53 -0
  180. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  181. package/dist/tools/workspace/index.d.ts +10 -0
  182. package/dist/tools/workspace/index.js +10 -0
  183. package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
  184. package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
  185. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  186. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  187. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  188. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  189. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  190. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  191. package/dist/tools/workspace/syncFile.js +134 -0
  192. package/dist/utils/atomicFile.d.ts +7 -0
  193. package/dist/utils/atomicFile.js +48 -0
  194. package/dist/utils/boundedFile.d.ts +8 -0
  195. package/dist/utils/boundedFile.js +93 -0
  196. package/dist/utils/lockedJsonFile.d.ts +15 -0
  197. package/dist/utils/lockedJsonFile.js +224 -0
  198. package/dist/utils/platform.d.ts +11 -0
  199. package/dist/utils/platform.js +26 -0
  200. package/dist/utils/stableJson.d.ts +2 -0
  201. package/dist/utils/stableJson.js +12 -0
  202. package/dist/version.d.ts +3 -2
  203. package/dist/version.js +3 -2
  204. package/dist/watcherStatus.d.ts +2 -2
  205. package/dist/watcherStatus.js +18 -0
  206. package/docs/CODE_WIKI.md +808 -143
  207. package/docs/architecture.md +15 -55
  208. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  209. package/docs/control-center/README.md +4 -4
  210. package/docs/dashboard-overview.md +4 -4
  211. package/docs/desktop-app.md +175 -0
  212. package/docs/evidence-pack-schema.md +3 -3
  213. package/docs/open-source-application.md +17 -11
  214. package/docs/release-checklist.md +1 -1
  215. package/docs/release-evidence.md +39 -29
  216. package/docs/task-safe-review-workflow.md +5 -5
  217. package/docs/threat-model.md +19 -1
  218. package/examples/config.example.json +4 -2
  219. package/package.json +27 -10
  220. package/scripts/README.md +15 -6
  221. package/scripts/build.js +11 -0
  222. package/scripts/checks/brand-check.js +3 -0
  223. package/scripts/checks/build-output-check.js +37 -0
  224. package/scripts/checks/control-center-smoke.js +91 -6
  225. package/scripts/checks/control-smoke.js +19 -8
  226. package/scripts/checks/http-mcp-smoke.js +82 -4
  227. package/scripts/checks/lifecycle-smoke.js +10 -10
  228. package/scripts/checks/mcp-manifest-check.js +1 -1
  229. package/scripts/checks/mcp-smoke.js +27 -3
  230. package/scripts/checks/package-manifest-check.js +20 -7
  231. package/scripts/checks/unit-tests.js +3 -5
  232. package/scripts/checks/watcher-supervisor-smoke.js +9 -0
  233. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  234. package/scripts/control/manage-patchwarden.ps1 +69 -4
  235. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  236. package/scripts/control/run-background-supervisor.ps1 +41 -0
  237. package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
  238. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  239. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  240. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  241. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  242. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  243. package/scripts/lib/clean-generated-output.js +11 -0
  244. package/scripts/lib/file-discovery.js +12 -0
  245. package/scripts/release/desktop-preflight.js +211 -0
  246. package/scripts/release/pack-clean.js +59 -14
  247. package/src/assessments/agentAssessor.ts +26 -12
  248. package/src/assessments/assessmentStore.ts +52 -40
  249. package/src/config.ts +70 -13
  250. package/src/control/fileManager.ts +40 -0
  251. package/src/control/middleware/auth.ts +22 -4
  252. package/src/control/middleware/static.ts +16 -3
  253. package/src/control/routeTable.ts +301 -0
  254. package/src/control/routes/audit.ts +14 -6
  255. package/src/control/routes/evidence.ts +1 -1
  256. package/src/control/routes/lineage.ts +1 -1
  257. package/src/control/routes/process.ts +341 -40
  258. package/src/control/routes/sessions.ts +88 -25
  259. package/src/control/routes/status.ts +97 -17
  260. package/src/control/routes/taskActions.ts +24 -58
  261. package/src/control/routes/tasks.ts +66 -58
  262. package/src/control/routes/workspace.ts +56 -25
  263. package/src/control/runtime.ts +69 -33
  264. package/src/control/server.ts +36 -342
  265. package/src/control/shared.ts +77 -11
  266. package/src/direct/directAudit.ts +17 -9
  267. package/src/direct/directGuards.ts +72 -44
  268. package/src/direct/directPatch.ts +95 -8
  269. package/src/direct/directSessionStore.ts +211 -55
  270. package/src/direct/directVerification.ts +9 -23
  271. package/src/doctor.ts +73 -59
  272. package/src/goal/acceptanceEngine.ts +1 -1
  273. package/src/goal/goalProgress.ts +17 -13
  274. package/src/goal/goalReport.ts +5 -13
  275. package/src/goal/goalStatus.ts +19 -3
  276. package/src/goal/goalStore.ts +241 -54
  277. package/src/goal/handoffExport.ts +6 -6
  278. package/src/goal/specKitImport.ts +12 -12
  279. package/src/goal/subgoalSync.ts +11 -8
  280. package/src/goal/worktreeManager.ts +139 -22
  281. package/src/httpServer.ts +113 -30
  282. package/src/logging.ts +10 -3
  283. package/src/policy/projectPolicy.ts +5 -2
  284. package/src/release/releaseGate.ts +74 -27
  285. package/src/runner/agentInvocation.ts +126 -4
  286. package/src/runner/changeCapture.ts +291 -57
  287. package/src/runner/postTaskCleanup.ts +41 -17
  288. package/src/runner/processSecurity.ts +433 -0
  289. package/src/runner/runTask.ts +138 -91
  290. package/src/runner/simpleProcess.ts +66 -31
  291. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  292. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
  293. package/src/runner/taskStatusStore.ts +54 -0
  294. package/src/runner/watch.ts +281 -51
  295. package/src/security/contentRedaction.ts +2 -2
  296. package/src/security/discoveryTokenStore.ts +18 -4
  297. package/src/security/loopbackHost.ts +19 -0
  298. package/src/security/pathGuard.ts +35 -16
  299. package/src/security/planGuard.ts +2 -1
  300. package/src/security/runtimeGuard.ts +9 -6
  301. package/src/security/secretComparison.ts +13 -0
  302. package/src/security/sensitiveGuard.ts +27 -8
  303. package/src/security/toolInvocationGuard.ts +2 -2
  304. package/src/security/workspaceRootGuard.ts +53 -0
  305. package/src/smoke-test.ts +25 -24
  306. package/src/tools/catalog/index.ts +4 -0
  307. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  308. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +7 -16
  309. package/src/tools/definitions/toolDefs.ts +1264 -0
  310. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  311. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +99 -32
  312. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +8 -8
  313. package/src/tools/diagnostics/index.ts +6 -0
  314. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  315. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +27 -17
  316. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  317. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +6 -6
  318. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +7 -5
  319. package/src/tools/direct/index.ts +3 -0
  320. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  321. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  322. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  323. package/src/tools/discovery/index.ts +3 -0
  324. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  325. package/src/tools/dispatch/coreDispatch.ts +36 -31
  326. package/src/tools/dispatch/diagnosticDispatch.ts +26 -21
  327. package/src/tools/dispatch/directDispatch.ts +11 -10
  328. package/src/tools/dispatch/goalDispatch.ts +5 -4
  329. package/src/tools/dispatch/releaseDispatch.ts +3 -2
  330. package/src/tools/dispatch/validation.ts +78 -0
  331. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  332. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +41 -26
  333. package/src/tools/goals/index.ts +3 -0
  334. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  335. package/src/tools/registry.ts +17 -1261
  336. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  337. package/src/tools/release/index.ts +2 -0
  338. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  339. package/src/tools/tasks/cancelTask.ts +109 -0
  340. package/src/tools/{createTask.ts → tasks/createTask.ts} +49 -26
  341. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
  342. package/src/tools/{evidencePack.ts → tasks/evidencePack.ts} +36 -22
  343. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  344. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
  345. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  346. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
  347. package/src/tools/tasks/index.ts +18 -0
  348. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
  349. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
  350. package/src/tools/tasks/retryTask.ts +69 -0
  351. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +69 -34
  352. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  353. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  354. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  355. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  356. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  357. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  358. package/src/tools/workspace/index.ts +10 -0
  359. package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
  360. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  361. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  362. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  363. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  364. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  365. package/src/tools/workspace/syncFile.ts +292 -0
  366. package/src/utils/atomicFile.ts +58 -0
  367. package/src/utils/boundedFile.ts +116 -0
  368. package/src/utils/lockedJsonFile.ts +251 -0
  369. package/src/utils/platform.ts +31 -0
  370. package/src/utils/stableJson.ts +12 -0
  371. package/src/version.ts +3 -2
  372. package/src/watcherStatus.ts +19 -1
  373. package/ui/desktop-bootstrap.js +11 -0
  374. package/ui/desktop-bridge.js +36 -0
  375. package/ui/desktop.css +160 -0
  376. package/ui/getting-started.js +93 -0
  377. package/ui/i18n.js +484 -0
  378. package/ui/log-parser.js +30 -0
  379. package/ui/pages/audit.html +12 -0
  380. package/ui/pages/dashboard.html +90 -147
  381. package/ui/pages/direct-sessions.html +123 -26
  382. package/ui/pages/getting-started.html +43 -0
  383. package/ui/pages/logs.html +105 -22
  384. package/ui/pages/settings.html +101 -0
  385. package/ui/pages/task-detail.html +12 -0
  386. package/ui/pages/tasks.html +54 -8
  387. package/ui/pages/workspace.html +38 -1
  388. package/ui/settings.js +245 -0
  389. package/PatchWarden-Control.cmd +0 -6
  390. package/Restart-PatchWarden-Control.cmd +0 -6
  391. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  392. package/dist/test/unit/acceptance-engine.test.js +0 -228
  393. package/dist/test/unit/agent-router.test.d.ts +0 -1
  394. package/dist/test/unit/agent-router.test.js +0 -287
  395. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  396. package/dist/test/unit/android-doctor.test.js +0 -118
  397. package/dist/test/unit/apply-patch.test.d.ts +0 -1
  398. package/dist/test/unit/apply-patch.test.js +0 -225
  399. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  400. package/dist/test/unit/audit-checks.test.js +0 -350
  401. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  402. package/dist/test/unit/chinese-path.test.js +0 -91
  403. package/dist/test/unit/command-guard.test.d.ts +0 -1
  404. package/dist/test/unit/command-guard.test.js +0 -160
  405. package/dist/test/unit/create-task.test.d.ts +0 -1
  406. package/dist/test/unit/create-task.test.js +0 -197
  407. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  408. package/dist/test/unit/diagnose-task.test.js +0 -457
  409. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  410. package/dist/test/unit/direct-guards.test.js +0 -328
  411. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  412. package/dist/test/unit/discovery-token-store.test.js +0 -139
  413. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  414. package/dist/test/unit/evidence-pack.test.js +0 -224
  415. package/dist/test/unit/get-task-status.test.d.ts +0 -1
  416. package/dist/test/unit/get-task-status.test.js +0 -174
  417. package/dist/test/unit/get-task-summary.test.d.ts +0 -1
  418. package/dist/test/unit/get-task-summary.test.js +0 -146
  419. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  420. package/dist/test/unit/goal-graph.test.js +0 -298
  421. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  422. package/dist/test/unit/goal-progress.test.js +0 -381
  423. package/dist/test/unit/goal-report.test.d.ts +0 -1
  424. package/dist/test/unit/goal-report.test.js +0 -159
  425. package/dist/test/unit/goal-status.test.d.ts +0 -1
  426. package/dist/test/unit/goal-status.test.js +0 -215
  427. package/dist/test/unit/goal-store.test.d.ts +0 -1
  428. package/dist/test/unit/goal-store.test.js +0 -253
  429. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  430. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  431. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  432. package/dist/test/unit/goal-tools-registry.test.js +0 -192
  433. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  434. package/dist/test/unit/handoff-export.test.js +0 -263
  435. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  436. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  437. package/dist/test/unit/logging.test.d.ts +0 -1
  438. package/dist/test/unit/logging.test.js +0 -397
  439. package/dist/test/unit/path-guard.test.d.ts +0 -1
  440. package/dist/test/unit/path-guard.test.js +0 -133
  441. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  442. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  443. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  444. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  445. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  446. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  447. package/dist/test/unit/release-gate.test.d.ts +0 -1
  448. package/dist/test/unit/release-gate.test.js +0 -242
  449. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  450. package/dist/test/unit/run-task-loop.test.js +0 -380
  451. package/dist/test/unit/safe-status.test.d.ts +0 -1
  452. package/dist/test/unit/safe-status.test.js +0 -165
  453. package/dist/test/unit/safe-views.test.d.ts +0 -1
  454. package/dist/test/unit/safe-views.test.js +0 -171
  455. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  456. package/dist/test/unit/schema-drift-check.test.js +0 -176
  457. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  458. package/dist/test/unit/sensitive-guard.test.js +0 -104
  459. package/dist/test/unit/spec-kit-import.test.d.ts +0 -1
  460. package/dist/test/unit/spec-kit-import.test.js +0 -341
  461. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  462. package/dist/test/unit/subgoal-sync.test.js +0 -183
  463. package/dist/test/unit/sync-file.test.d.ts +0 -1
  464. package/dist/test/unit/sync-file.test.js +0 -154
  465. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  466. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  467. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  468. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  469. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  470. package/dist/test/unit/toolSearch.test.js +0 -571
  471. package/dist/test/unit/wait-for-task.test.d.ts +0 -1
  472. package/dist/test/unit/wait-for-task.test.js +0 -144
  473. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  474. package/dist/test/unit/watcher-status.test.js +0 -169
  475. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  476. package/dist/test/unit/worktree-manager.test.js +0 -176
  477. package/dist/tools/applyPatch.js +0 -41
  478. package/dist/tools/cancelTask.d.ts +0 -30
  479. package/dist/tools/cancelTask.js +0 -64
  480. package/dist/tools/finalizeDirectSession.js +0 -84
  481. package/dist/tools/goalSubgoalTask.js +0 -110
  482. package/dist/tools/killTask.d.ts +0 -15
  483. package/dist/tools/retryTask.js +0 -45
  484. package/dist/tools/runVerification.js +0 -36
  485. package/dist/tools/syncFile.js +0 -65
  486. package/docs/control-center/control-center-daily-driver.md +0 -211
  487. package/docs/control-center/control-center-mvp.md +0 -205
  488. package/docs/control-center/control-center-phase2.md +0 -159
  489. package/docs/execution-plan-2026-07-09.md +0 -315
  490. package/docs/release-v0.6.0.md +0 -71
  491. package/docs/release-v0.6.1.md +0 -75
  492. package/docs/release-v0.6.4.md +0 -45
  493. package/docs/roadmap-execution-and-acceptance.md +0 -365
  494. package/src/test/unit/acceptance-engine.test.ts +0 -261
  495. package/src/test/unit/agent-router.test.ts +0 -342
  496. package/src/test/unit/android-doctor.test.ts +0 -158
  497. package/src/test/unit/apply-patch.test.ts +0 -293
  498. package/src/test/unit/audit-checks.test.ts +0 -567
  499. package/src/test/unit/chinese-path.test.ts +0 -106
  500. package/src/test/unit/command-guard.test.ts +0 -221
  501. package/src/test/unit/create-task.test.ts +0 -255
  502. package/src/test/unit/diagnose-task.test.ts +0 -544
  503. package/src/test/unit/direct-guards.test.ts +0 -467
  504. package/src/test/unit/discovery-token-store.test.ts +0 -181
  505. package/src/test/unit/evidence-pack.test.ts +0 -251
  506. package/src/test/unit/get-task-status.test.ts +0 -203
  507. package/src/test/unit/get-task-summary.test.ts +0 -173
  508. package/src/test/unit/goal-graph.test.ts +0 -347
  509. package/src/test/unit/goal-progress.test.ts +0 -538
  510. package/src/test/unit/goal-report.test.ts +0 -189
  511. package/src/test/unit/goal-status.test.ts +0 -270
  512. package/src/test/unit/goal-store.test.ts +0 -318
  513. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  514. package/src/test/unit/goal-tools-registry.test.ts +0 -245
  515. package/src/test/unit/handoff-export.test.ts +0 -295
  516. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  517. package/src/test/unit/logging.test.ts +0 -497
  518. package/src/test/unit/path-guard.test.ts +0 -180
  519. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  520. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  521. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  522. package/src/test/unit/release-gate.test.ts +0 -314
  523. package/src/test/unit/run-task-loop.test.ts +0 -425
  524. package/src/test/unit/safe-status.test.ts +0 -187
  525. package/src/test/unit/safe-views.test.ts +0 -184
  526. package/src/test/unit/schema-drift-check.test.ts +0 -260
  527. package/src/test/unit/sensitive-guard.test.ts +0 -124
  528. package/src/test/unit/spec-kit-import.test.ts +0 -429
  529. package/src/test/unit/subgoal-sync.test.ts +0 -236
  530. package/src/test/unit/sync-file.test.ts +0 -231
  531. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  532. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  533. package/src/test/unit/toolSearch.test.ts +0 -756
  534. package/src/test/unit/wait-for-task.test.ts +0 -176
  535. package/src/test/unit/watcher-status.test.ts +0 -190
  536. package/src/test/unit/worktree-manager.test.ts +0 -247
  537. package/src/tools/cancelTask.ts +0 -70
  538. package/src/tools/retryTask.ts +0 -53
  539. package/src/tools/syncFile.ts +0 -122
  540. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  541. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  542. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  543. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  544. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  545. /package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +0 -0
  546. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  547. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  548. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  549. /package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +0 -0
  550. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  551. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  552. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  553. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  554. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  555. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  556. /package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +0 -0
  557. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  558. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  559. /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
  560. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  561. /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
  562. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  563. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  564. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  565. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  566. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  567. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  568. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  569. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  570. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  571. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  572. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
  573. /package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +0 -0
@@ -1,9 +1,9 @@
1
- import { getConfig } from "../config.js";
2
- import { guardWorkspacePath } from "../security/pathGuard.js";
1
+ import { getConfig } from "../../config.js";
2
+ import { guardWorkspacePath } from "../../security/pathGuard.js";
3
3
  import {
4
4
  runReleaseGateCheck,
5
5
  type ReleaseStage,
6
- } from "../release/releaseGate.js";
6
+ } from "../../release/releaseGate.js";
7
7
 
8
8
  export interface CheckReleaseGateInput {
9
9
  repo_path: string;
@@ -0,0 +1,2 @@
1
+ export * from "./checkReleaseGate.js";
2
+ export * from "./releaseMode.js";
@@ -1,7 +1,7 @@
1
- import { execFileSync, execSync } from "node:child_process";
2
- import { existsSync, mkdirSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, readdirSync, rmSync, statSync } from "node:fs";
3
3
  import { join, relative, resolve, sep, isAbsolute } from "node:path";
4
- import { getConfig } from "../config.js";
4
+ import { getConfig } from "../../config.js";
5
5
  import {
6
6
  checkCiVerified,
7
7
  checkGitHubReleaseVerified,
@@ -9,9 +9,12 @@ import {
9
9
  runReleaseGateCheck,
10
10
  type ReleaseStage,
11
11
  type ReleaseStageStatus,
12
- } from "../release/releaseGate.js";
13
- import { guardTestCommand } from "../security/commandGuard.js";
14
- import { guardWorkspacePath } from "../security/pathGuard.js";
12
+ } from "../../release/releaseGate.js";
13
+ import { guardTestCommand } from "../../security/commandGuard.js";
14
+ import { guardWorkspacePath } from "../../security/pathGuard.js";
15
+ import { resolveTrustedCommandLine, buildGitEnvironment, resolveTrustedExecutable } from "../../runner/processSecurity.js";
16
+ import { atomicWriteJsonFileSync } from "../../utils/atomicFile.js";
17
+ import { runSimpleProcessSync } from "../../runner/simpleProcess.js";
15
18
  import {
16
19
  commandAllowedByProjectPolicy,
17
20
  getProjectPolicySummary,
@@ -19,7 +22,7 @@ import {
19
22
  readPackageJson,
20
23
  resolveVersionFromPolicy,
21
24
  type ProjectPolicySummary,
22
- } from "../policy/projectPolicy.js";
25
+ } from "../../policy/projectPolicy.js";
23
26
 
24
27
  export interface ReleaseCheckInput {
25
28
  repo_path: string;
@@ -146,12 +149,19 @@ export function releasePrepare(input: ReleasePrepareInput): ReleaseModeResult {
146
149
  continue;
147
150
  }
148
151
  try {
149
- execSync(command, {
152
+ const invocation = resolveTrustedCommandLine(command, repoPath);
153
+ const execution = runSimpleProcessSync({
154
+ command: invocation.command,
155
+ args: invocation.argsPrefix,
150
156
  cwd: repoPath,
151
- encoding: "utf-8",
152
- timeout: timeoutMs,
153
- stdio: ["ignore", "pipe", "pipe"],
157
+ timeoutMs,
154
158
  });
159
+ if (execution.spawnError) throw new Error(execution.spawnError);
160
+ if (execution.timedOut) throw new Error(`Command timed out after ${timeoutMs}ms`);
161
+ if (execution.exitCode !== 0) {
162
+ const status = execution.exitCode ?? -1;
163
+ throw Object.assign(new Error(`Command exited with code ${status}`), { status });
164
+ }
155
165
  results.push({ command, status: "passed", exit_code: 0, reason: null });
156
166
  } catch (err) {
157
167
  const code = err && typeof err === "object" && "status" in err
@@ -280,7 +290,7 @@ export function releaseCleanup(input: ReleaseCleanupInput): ReleaseModeResult {
280
290
  const reportDir = join(repoPath, CLEANUP_REPORT_DIR);
281
291
  mkdirSync(reportDir, { recursive: true });
282
292
  const reportPath = join(reportDir, `${report.generated_at.replace(/[-:.TZ]/g, "").slice(0, 14)}.json`);
283
- writeFileSync(reportPath, JSON.stringify(report, null, 2) + "\n", "utf-8");
293
+ atomicWriteJsonFileSync(reportPath, report);
284
294
 
285
295
  return {
286
296
  ok: skipped.length === 0 || dryRun,
@@ -379,10 +389,12 @@ function walkFrom(base: string, dir: string, visit: (path: string, relPath: stri
379
389
  function isTrackedByGit(root: string, relPath: string): boolean {
380
390
  if (!isGitWorktree(root)) return false;
381
391
  try {
382
- execFileSync("git", ["ls-files", "--error-unmatch", "--", relPath], {
392
+ const env = buildGitEnvironment(root);
393
+ execFileSync(resolveTrustedExecutable("git", root, { pathValue: env.PATH }), ["ls-files", "--error-unmatch", "--", relPath], {
383
394
  cwd: root,
384
395
  stdio: "ignore",
385
396
  windowsHide: true,
397
+ env,
386
398
  });
387
399
  return true;
388
400
  } catch {
@@ -393,32 +405,38 @@ function isTrackedByGit(root: string, relPath: string): boolean {
393
405
  function isIgnoredOrUntracked(root: string, relPath: string): boolean {
394
406
  if (!isGitWorktree(root)) return true;
395
407
  try {
396
- execFileSync("git", ["check-ignore", "-q", "--", relPath], {
397
- cwd: root,
398
- stdio: "ignore",
399
- windowsHide: true,
400
- });
401
- return true;
402
- } catch {
408
+ const env = buildGitEnvironment(root);
409
+ const git = resolveTrustedExecutable("git", root, { pathValue: env.PATH });
403
410
  try {
404
- const output = execFileSync("git", ["ls-files", "--others", "--exclude-standard", "--", relPath], {
411
+ execFileSync(git, ["check-ignore", "-q", "--", relPath], {
412
+ cwd: root,
413
+ stdio: "ignore",
414
+ windowsHide: true,
415
+ env,
416
+ });
417
+ return true;
418
+ } catch {
419
+ const output = execFileSync(git, ["ls-files", "--others", "--exclude-standard", "--", relPath], {
405
420
  cwd: root,
406
421
  encoding: "utf-8",
407
422
  windowsHide: true,
423
+ env,
408
424
  });
409
425
  return output.trim().split(/\r?\n/).filter(Boolean).includes(relPath);
410
- } catch {
411
- return false;
412
426
  }
427
+ } catch {
428
+ return false;
413
429
  }
414
430
  }
415
431
 
416
432
  function isGitWorktree(root: string): boolean {
417
433
  try {
418
- return execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
434
+ const env = buildGitEnvironment(root);
435
+ return execFileSync(resolveTrustedExecutable("git", root, { pathValue: env.PATH }), ["rev-parse", "--is-inside-work-tree"], {
419
436
  cwd: root,
420
437
  encoding: "utf-8",
421
438
  windowsHide: true,
439
+ env,
422
440
  }).trim() === "true";
423
441
  } catch {
424
442
  return false;
@@ -0,0 +1,109 @@
1
+ import { 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 "../../runner/taskProgress.js";
6
+ import { mutateTaskStatus } from "../../runner/taskStatusStore.js";
7
+ import type { TaskStatus } from "./createTask.js";
8
+
9
+ export interface TaskTerminationResponse {
10
+ task_id: string;
11
+ previous_status: TaskStatus;
12
+ new_status: TaskStatus;
13
+ message: string;
14
+ cancel_requested?: boolean;
15
+ force_kill_requested?: boolean;
16
+ }
17
+
18
+ interface TaskTerminationOutcome {
19
+ response: TaskTerminationResponse;
20
+ progress: {
21
+ phase: "canceled" | "canceling" | "terminating";
22
+ note: string;
23
+ } | null;
24
+ }
25
+
26
+ export function cancelTask(taskId: string) {
27
+ return requestTaskTermination(taskId, false);
28
+ }
29
+
30
+ export function requestTaskTermination(taskId: string, force: boolean) {
31
+ const config = getConfig();
32
+ const taskDir = join(getTasksDir(config), taskId);
33
+ const statusFile = join(taskDir, "status.json");
34
+ guardReadPath(statusFile, config.workspaceRoot, config.tasksDir);
35
+
36
+ if (!existsSync(statusFile)) throw new Error(`Task not found: "${taskId}"`);
37
+
38
+ const now = new Date().toISOString();
39
+ const outcome = mutateTaskStatus<TaskTerminationOutcome>(statusFile, (current) => {
40
+ const currentStatus = current.status as TaskStatus;
41
+ if (["done", "done_by_agent", "failed", "failed_verification", "failed_scope_violation", "failed_policy_violation", "canceled"].includes(currentStatus)) {
42
+ return { result: {
43
+ response: {
44
+ task_id: taskId,
45
+ previous_status: currentStatus,
46
+ new_status: currentStatus,
47
+ message: `Task is already ${currentStatus}. No action taken.`,
48
+ },
49
+ progress: null,
50
+ } };
51
+ }
52
+
53
+ if (currentStatus === "pending") {
54
+ const reason = force ? "Killed before execution by user request." : "Canceled by user request.";
55
+ const next = {
56
+ ...current,
57
+ status: "canceled",
58
+ phase: "canceled",
59
+ canceled_at: now,
60
+ cancel_reason: reason,
61
+ updated_at: now,
62
+ };
63
+ return { next, result: {
64
+ response: {
65
+ task_id: taskId,
66
+ previous_status: "pending" as const,
67
+ new_status: "canceled" as const,
68
+ message: "Pending task canceled. It will not be executed by watcher.",
69
+ },
70
+ progress: { phase: "canceled" as const, note: reason },
71
+ } };
72
+ }
73
+
74
+ const phase = force ? "terminating" as const : "canceling" as const;
75
+ const next = {
76
+ ...current,
77
+ cancel_requested: true,
78
+ cancel_requested_at: now,
79
+ force_kill_requested: force,
80
+ ...(force ? { kill_requested_at: now } : {}),
81
+ phase,
82
+ updated_at: now,
83
+ };
84
+ return { next, result: {
85
+ response: {
86
+ task_id: taskId,
87
+ previous_status: "running" as const,
88
+ new_status: "running" as const,
89
+ cancel_requested: true,
90
+ force_kill_requested: force,
91
+ message: force
92
+ ? "Kill requested. The runner that owns the child process will terminate it."
93
+ : "Cancel requested. The runner will stop the child process safely.",
94
+ },
95
+ progress: {
96
+ phase,
97
+ note: force ? "Immediate termination requested." : "Graceful cancellation requested.",
98
+ },
99
+ } };
100
+ });
101
+
102
+ if (outcome.progress) {
103
+ writeTaskProgress(taskDir, outcome.progress.phase, {
104
+ note: outcome.progress.note,
105
+ heartbeatAt: now,
106
+ });
107
+ }
108
+ return outcome.response;
109
+ }
@@ -2,12 +2,12 @@ import { mkdirSync, writeFileSync, existsSync, statSync, readFileSync } from "no
2
2
  import { randomBytes } from "node:crypto";
3
3
  import { resolve, join, dirname } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { getTasksDir, getPlansDir, getConfig } from "../config.js";
6
- import { guardPath, guardWorkspacePath, guardReadPath } from "../security/pathGuard.js";
7
- import { guardTestCommand } from "../security/commandGuard.js";
8
- import { guardRuntimeSelfModification } from "../security/runtimeGuard.js";
9
- import { guardPlanContent } from "../security/planGuard.js";
10
- import { assessRisk } from "../security/riskEngine.js";
5
+ import { getTasksDir, getPlansDir, getConfig } from "../../config.js";
6
+ import { guardPath, guardWorkspacePath, guardReadPath } from "../../security/pathGuard.js";
7
+ import { guardTestCommand } from "../../security/commandGuard.js";
8
+ import { guardRuntimeSelfModification } from "../../security/runtimeGuard.js";
9
+ import { guardPlanContent } from "../../security/planGuard.js";
10
+ import { assessRisk } from "../../security/riskEngine.js";
11
11
  import {
12
12
  createAssessment,
13
13
  readAssessment,
@@ -16,27 +16,29 @@ import {
16
16
  createAssessmentDir,
17
17
  type AssessmentRecord,
18
18
  type AgentAssessmentSummary,
19
- } from "../assessments/assessmentStore.js";
20
- import { runAgentAssessment } from "../assessments/agentAssessor.js";
21
- import { captureRepoSnapshot } from "../runner/changeCapture.js";
22
- import { writeTaskProgress } from "../taskProgress.js";
23
- import { PatchWardenError } from "../errors.js";
24
- import { savePlan } from "./savePlan.js";
19
+ } from "../../assessments/assessmentStore.js";
20
+ import { runAgentAssessment } from "../../assessments/agentAssessor.js";
21
+ import { captureRepoSnapshot } from "../../runner/changeCapture.js";
22
+ import { writeTaskProgress } from "../../runner/taskProgress.js";
23
+ import { PatchWardenError } from "../../errors.js";
24
+ import { savePlan } from "../goals/savePlan.js";
25
25
  import {
26
26
  expandTaskTemplate,
27
27
  TASK_TEMPLATE_NAMES,
28
28
  type ChangePolicy,
29
29
  type TaskTemplateName,
30
- } from "./taskTemplates.js";
31
- import { PATCHWARDEN_VERSION } from "../version.js";
32
- import { getLastToolCatalogSnapshot, resolveToolProfile } from "./toolCatalog.js";
30
+ } from "../taskTemplates.js";
31
+ import { PATCHWARDEN_VERSION, ARTIFACT_SCHEMA_VERSION } from "../../version.js";
32
+ import { getLastToolCatalogSnapshot, resolveToolProfile } from "../catalog/toolCatalog.js";
33
33
  import {
34
34
  derivePendingReason,
35
35
  readWatcherStatus,
36
36
  type PendingReason,
37
37
  type WatcherStatusSnapshot,
38
- } from "../watcherStatus.js";
39
- import { routeAgent, type AgentRouteResult } from "../agents/agentRouter.js";
38
+ } from "../../watcherStatus.js";
39
+ import { routeAgent, type AgentRouteResult } from "../../agents/agentRouter.js";
40
+ import { redactSensitiveValue } from "../../security/contentRedaction.js";
41
+ import { atomicWriteJsonFileSync } from "../../utils/atomicFile.js";
40
42
 
41
43
  export type TaskStatus =
42
44
  | "pending"
@@ -110,6 +112,14 @@ export interface CreateTaskInput {
110
112
  // v0.8.0: Goal Session 关联
111
113
  goal_id?: string;
112
114
  subgoal_id?: string;
115
+ /** Internal provenance supplied by retry_task; not exposed in MCP schemas. */
116
+ retry_metadata?: {
117
+ retry_of: string;
118
+ retry_count: number;
119
+ plan_source: "saved" | "inline" | "template";
120
+ template: string | null;
121
+ change_policy: ChangePolicy;
122
+ };
113
123
  }
114
124
 
115
125
  export interface AssessOnlyOutput {
@@ -589,12 +599,25 @@ export async function createTask(input: CreateTaskInput): Promise<CreateTaskResu
589
599
 
590
600
  const status: TaskStatus = "pending";
591
601
  const statusFile = join(taskDir, "status.json");
602
+ const retryMetadata = effectiveInput.retry_metadata;
603
+ const safeAcceptance = redactSensitiveValue({
604
+ goal: effectiveInput.goal || null,
605
+ scope: effectiveInput.scope ?? null,
606
+ forbidden: effectiveInput.forbidden ?? null,
607
+ verification: effectiveInput.verification ?? null,
608
+ done_evidence: effectiveInput.done_evidence ?? null,
609
+ }).value;
592
610
  const statusData = {
611
+ schema_version: ARTIFACT_SCHEMA_VERSION,
593
612
  task_id: taskId,
594
613
  plan_id: planId,
595
- plan_source: planSource,
596
- template: effectiveInput.template || null,
597
- change_policy: changePolicy,
614
+ plan_source: retryMetadata?.plan_source ?? planSource,
615
+ template: retryMetadata?.template ?? effectiveInput.template ?? null,
616
+ change_policy: retryMetadata?.change_policy ?? changePolicy,
617
+ ...(retryMetadata ? {
618
+ retry_of: retryMetadata.retry_of,
619
+ retry_count: retryMetadata.retry_count,
620
+ } : {}),
598
621
  agent: effectiveInput.agent,
599
622
  workspace_root: resolve(config.workspaceRoot),
600
623
  repo_path: resolvedRepoPath,
@@ -611,11 +634,11 @@ export async function createTask(input: CreateTaskInput): Promise<CreateTaskResu
611
634
  current_command: null as string | null,
612
635
  error: null as string | null,
613
636
  // v0.7.2: 验收标准绑定到任务创建
614
- goal: effectiveInput.goal || null,
615
- scope: effectiveInput.scope ?? null,
616
- forbidden: effectiveInput.forbidden ?? null,
617
- verification: effectiveInput.verification ?? null,
618
- done_evidence: effectiveInput.done_evidence ?? null,
637
+ goal: safeAcceptance.goal,
638
+ scope: safeAcceptance.scope,
639
+ forbidden: safeAcceptance.forbidden,
640
+ verification: safeAcceptance.verification,
641
+ done_evidence: safeAcceptance.done_evidence,
619
642
  acceptance_status: null as AcceptanceStatus,
620
643
  // v0.8.0: Goal Session 关联
621
644
  goal_id: effectiveInput.goal_id ?? null,
@@ -624,7 +647,7 @@ export async function createTask(input: CreateTaskInput): Promise<CreateTaskResu
624
647
  ...(agentSelectionReason ? { agent_selection_reason: agentSelectionReason } : {}),
625
648
  };
626
649
 
627
- writeFileSync(statusFile, JSON.stringify(statusData, null, 2), "utf-8");
650
+ atomicWriteJsonFileSync(statusFile, statusData);
628
651
  writeTaskProgress(taskDir, "queued", {
629
652
  heartbeatAt: statusData.last_heartbeat_at,
630
653
  note: `Waiting for watcher. Timeout: ${timeoutSeconds} seconds.`,
@@ -1,11 +1,12 @@
1
1
  import { existsSync, readFileSync, statSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
- import { getTasksDir, getConfig, type PatchWardenConfig } from "../config.js";
4
- import { guardReadPath } from "../security/pathGuard.js";
5
- import { guardSensitivePath } from "../security/sensitiveGuard.js";
6
- import { redactSensitiveContent } from "../security/contentRedaction.js";
7
- import { readTaskRuntime } from "../taskRuntime.js";
8
- import { isWatcherOwningTask, readWatcherInstanceId } from "../watcherStatus.js";
3
+ import { getTasksDir, getConfig, type PatchWardenConfig } from "../../config.js";
4
+ import { guardReadPath } from "../../security/pathGuard.js";
5
+ import { guardSensitivePath } from "../../security/sensitiveGuard.js";
6
+ import { redactSensitiveContent } from "../../security/contentRedaction.js";
7
+ import { readTextFileTailLinesSync } from "../../utils/boundedFile.js";
8
+ import { readTaskRuntime } from "../../runner/taskRuntime.js";
9
+ import { isWatcherOwningTask, readWatcherInstanceId } from "../../watcherStatus.js";
9
10
 
10
11
  // ── v0.7.0: Task diagnosis types ──────────────────────────────────
11
12
 
@@ -118,8 +119,7 @@ function fileAgeSeconds(filePath: string, nowMs: number): { age: number; size: n
118
119
  function readTailLines(filePath: string, lines: number): string | null {
119
120
  if (!existsSync(filePath)) return null;
120
121
  try {
121
- const raw = readFileSync(filePath, "utf-8");
122
- const tail = raw.split("\n").slice(-lines).join("\n");
122
+ const tail = readTextFileTailLinesSync(filePath, lines, 512 * 1024);
123
123
  return redactSensitiveContent(tail).content;
124
124
  } catch {
125
125
  return null;
@@ -1,14 +1,16 @@
1
- import { execSync } from "node:child_process";
2
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
3
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";
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
8
  import { getTaskLineage, type SafeTaskLineage } from "./taskLineage.js";
9
- import { getLastToolCatalogSnapshot } from "./toolCatalog.js";
10
- import { PATCHWARDEN_VERSION, TOOL_SCHEMA_EPOCH } from "../version.js";
11
- import { PatchWardenError } from "../errors.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";
12
14
 
13
15
  export interface SafeEvidencePack {
14
16
  evidence_pack_id: string;
@@ -72,7 +74,7 @@ export function exportTaskEvidencePack(input: { lineage_id: string; max_items?:
72
74
  const verifyContent = buildVerifyContent(lineage);
73
75
  const diffstatContent = buildDiffstatContent(lineage, config);
74
76
  const lineageSummary = buildLineageSummary(lineage);
75
- const attestationContent = buildAttestation(catalog, generatedAt);
77
+ const attestationContent = buildAttestation(catalog, generatedAt, lineage.repo_path, config.workspaceRoot);
76
78
 
77
79
  // Count redactions across all v2 raw contents for the audit trail.
78
80
  const v2RawPayloads = [
@@ -99,12 +101,12 @@ export function exportTaskEvidencePack(input: { lineage_id: string; max_items?:
99
101
  };
100
102
 
101
103
  // Redact and write each v2 file.
102
- writeFileSync(filePaths.risk, JSON.stringify(redactSensitiveValue(riskContent).value, null, 2) + "\n", "utf-8");
103
- writeFileSync(filePaths.verify, JSON.stringify(redactSensitiveValue(verifyContent).value, null, 2) + "\n", "utf-8");
104
- writeFileSync(filePaths.diffstat, JSON.stringify(redactSensitiveValue(diffstatContent).value, null, 2) + "\n", "utf-8");
105
- writeFileSync(filePaths.lineage, JSON.stringify(redactSensitiveValue(lineageSummary).value, null, 2) + "\n", "utf-8");
106
- writeFileSync(filePaths.attestation, JSON.stringify(redactSensitiveValue(attestationContent).value, null, 2) + "\n", "utf-8");
107
- writeFileSync(filePaths.redactions, JSON.stringify(redactSensitiveValue(redactionsContent).value, null, 2) + "\n", "utf-8");
104
+ atomicWriteJsonFileSync(filePaths.risk, redactSensitiveValue(riskContent).value);
105
+ atomicWriteJsonFileSync(filePaths.verify, redactSensitiveValue(verifyContent).value);
106
+ atomicWriteJsonFileSync(filePaths.diffstat, redactSensitiveValue(diffstatContent).value);
107
+ atomicWriteJsonFileSync(filePaths.lineage, redactSensitiveValue(lineageSummary).value);
108
+ atomicWriteJsonFileSync(filePaths.attestation, redactSensitiveValue(attestationContent).value);
109
+ atomicWriteJsonFileSync(filePaths.redactions, redactSensitiveValue(redactionsContent).value);
108
110
 
109
111
  const safePack = redactSensitiveValue({
110
112
  evidence_pack_id: `evidence_${lineageId}`,
@@ -133,8 +135,8 @@ export function exportTaskEvidencePack(input: { lineage_id: string; max_items?:
133
135
  bounded: true,
134
136
  }).value as SafeEvidencePack;
135
137
 
136
- writeFileSync(safePack.files.json, JSON.stringify(safePack, null, 2) + "\n", "utf-8");
137
- writeFileSync(safePack.files.markdown, buildEvidenceMarkdown(safePack), "utf-8");
138
+ atomicWriteJsonFileSync(safePack.files.json, safePack);
139
+ atomicWriteFileSync(safePack.files.markdown, buildEvidenceMarkdown(safePack));
138
140
  return safePack;
139
141
  }
140
142
 
@@ -410,9 +412,19 @@ function buildLineageSummary(lineage: SafeTaskLineage): {
410
412
  };
411
413
  }
412
414
 
413
- function readGitCommitShort(): string {
415
+ function readGitCommitShort(repoPath: string, workspaceRoot: string): string {
414
416
  try {
415
- return execSync("git rev-parse --short HEAD", { encoding: "utf-8", timeout: 5000 }).trim();
417
+ const cwd = guardWorkspacePath(repoPath || ".", workspaceRoot);
418
+ const env = buildGitEnvironment(cwd);
419
+ const git = resolveTrustedExecutable("git", cwd, { pathValue: env.PATH });
420
+ return execFileSync(git, ["rev-parse", "--short", "HEAD"], {
421
+ cwd,
422
+ encoding: "utf-8",
423
+ env,
424
+ stdio: ["ignore", "pipe", "ignore"],
425
+ timeout: 5000,
426
+ windowsHide: true,
427
+ }).trim();
416
428
  } catch {
417
429
  return "unknown";
418
430
  }
@@ -420,7 +432,9 @@ function readGitCommitShort(): string {
420
432
 
421
433
  function buildAttestation(
422
434
  catalog: SafeEvidencePack["catalog"],
423
- generatedAt: string
435
+ generatedAt: string,
436
+ repoPath: string,
437
+ workspaceRoot: string,
424
438
  ): {
425
439
  patchwarden_version: string;
426
440
  package_version: string;
@@ -434,7 +448,7 @@ function buildAttestation(
434
448
  return {
435
449
  patchwarden_version: PATCHWARDEN_VERSION,
436
450
  package_version: PATCHWARDEN_VERSION,
437
- commit: readGitCommitShort(),
451
+ commit: readGitCommitShort(repoPath, workspaceRoot),
438
452
  node_version: process.version,
439
453
  os: { platform: process.platform, arch: process.arch },
440
454
  tool_profile: catalog.tool_profile,
@@ -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
 
9
9
  export interface GetTaskFileOutput {
@@ -1,17 +1,17 @@
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";
3
+ import { getTasksDir, getConfig } from "../../config.js";
4
+ import { guardReadPath } from "../../security/pathGuard.js";
5
+ import { guardSensitivePath } from "../../security/sensitiveGuard.js";
6
6
  import type { TaskStatus } from "./createTask.js";
7
7
  import type { TaskPhase } from "./createTask.js";
8
- import { readTaskRuntime } from "../taskRuntime.js";
8
+ import { readTaskRuntime } from "../../runner/taskRuntime.js";
9
9
  import {
10
10
  derivePendingReason,
11
11
  readWatcherStatus,
12
12
  type PendingReason,
13
13
  type WatcherStatusSnapshot,
14
- } from "../watcherStatus.js";
14
+ } from "../../watcherStatus.js";
15
15
 
16
16
  export interface GetTaskStatusOutput {
17
17
  task_id: string;
@@ -34,6 +34,8 @@ export interface GetTaskStatusOutput {
34
34
  finished_at?: string;
35
35
  changed_files?: Array<{ path: string; change: string }>;
36
36
  out_of_scope_changes?: Array<{ path: string; change: string }>;
37
+ new_out_of_scope_changes?: Array<{ path: string; change: string }>;
38
+ artifact_hygiene_counts?: Record<string, unknown>;
37
39
  verify_status?: "passed" | "failed" | "skipped";
38
40
  verify_commands?: string[];
39
41
  diff_available?: boolean;
@@ -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
 
7
8
  export function getTaskStdoutTail(taskId: string, lines = 80) {
8
9
  const config = getConfig();
@@ -19,7 +20,10 @@ export function getTaskStdoutTail(taskId: string, lines = 80) {
19
20
  const stdoutLog = join(taskDir, "stdout.log");
20
21
  const stderrLog = join(taskDir, "stderr.log");
21
22
  const resultFile = join(taskDir, "result.md");
22
- const maxLines = Math.min(lines, 200);
23
+ const maxLines = Number.isFinite(lines)
24
+ ? Math.max(1, Math.min(200, Math.trunc(lines)))
25
+ : 80;
26
+ const stderrLines = Math.min(maxLines, 40);
23
27
 
24
28
  let stdoutTail = "";
25
29
  let stderrTail = "";
@@ -27,22 +31,20 @@ export function getTaskStdoutTail(taskId: string, lines = 80) {
27
31
 
28
32
  // All log files are optional — don't throw if missing
29
33
  if (existsSync(stdoutLog)) {
30
- const raw = readFileSync(stdoutLog, "utf-8");
31
- stdoutTail = raw.split("\n").slice(-maxLines).join("\n");
34
+ stdoutTail = readTextFileTailLinesSync(stdoutLog, maxLines, config.maxReadFileBytes);
32
35
  source = "stdout.log";
33
36
  }
34
37
  if (existsSync(stderrLog)) {
35
- const raw = readFileSync(stderrLog, "utf-8");
36
- stderrTail = raw.split("\n").slice(-Math.min(maxLines, 40)).join("\n");
38
+ stderrTail = readTextFileTailLinesSync(stderrLog, stderrLines, config.maxReadFileBytes);
37
39
  }
38
40
 
39
41
  // Fallback to result.md
40
42
  if (!stdoutTail && existsSync(resultFile)) {
41
- const raw = readFileSync(resultFile, "utf-8");
43
+ const raw = readTextFilePrefixSync(resultFile, config.maxReadFileBytes).content;
42
44
  const stdoutMatch = raw.match(/## Agent stdout\s*\n+```\s*([\s\S]*?)```/i);
43
45
  const stderrMatch = raw.match(/## Agent stderr\s*\n+```\s*([\s\S]*?)```/i);
44
46
  if (stdoutMatch) stdoutTail = stdoutMatch[1].split("\n").slice(-maxLines).join("\n");
45
- if (stderrMatch && !stderrTail) stderrTail = stderrMatch[1].split("\n").slice(-Math.min(maxLines, 40)).join("\n");
47
+ if (stderrMatch && !stderrTail) stderrTail = stderrMatch[1].split("\n").slice(-stderrLines).join("\n");
46
48
  if (source === "none") source = "result.md";
47
49
  }
48
50