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
package/dist/logging.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { createHash } from "node:crypto";
2
- import { appendFileSync, mkdirSync } from "node:fs";
2
+ import { mkdirSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { redactSensitiveContent } from "./security/contentRedaction.js";
5
- import { stableJsonStringify } from "./tools/toolRegistry.js";
5
+ import { stableJsonStringify } from "./utils/stableJson.js";
6
6
  import { getConfig } from "./config.js";
7
+ import { appendBoundedTextFileSync } from "./utils/boundedFile.js";
8
+ const MAX_INVOCATION_LOG_BYTES = 5 * 1024 * 1024;
7
9
  // ── Helpers ───────────────────────────────────────────────────────
8
10
  /**
9
11
  * Returns true when PATCHWARDEN_VERBOSE_LOG is set to "true".
@@ -179,7 +181,7 @@ export function logToolInvocation(entry, options) {
179
181
  }
180
182
  mkdirSync(logsDir, { recursive: true });
181
183
  const logFilePath = join(logsDir, "invocation.log");
182
- appendFileSync(logFilePath, JSON.stringify(entry) + "\n", "utf8");
184
+ appendBoundedTextFileSync(logFilePath, JSON.stringify(entry) + "\n", MAX_INVOCATION_LOG_BYTES);
183
185
  }
184
186
  catch (err) {
185
187
  // 日志失败不应阻断主流程,仅向 stderr 记录错误
@@ -276,10 +276,13 @@ function pathMatchesPattern(relPath, pattern) {
276
276
  relPath.toLowerCase().startsWith(`${normalizedPattern.toLowerCase()}/`);
277
277
  }
278
278
  function parseGithubRepo(repository) {
279
+ const repositoryObject = repository && typeof repository === "object" && !Array.isArray(repository)
280
+ ? repository
281
+ : null;
279
282
  const raw = typeof repository === "string"
280
283
  ? repository
281
- : repository && typeof repository === "object" && typeof repository.url === "string"
282
- ? repository.url
284
+ : typeof repositoryObject?.url === "string"
285
+ ? repositoryObject.url
283
286
  : "";
284
287
  const match = raw.match(/github\.com[:/](.+?\/.+?)(?:\.git)?(?:[#?].*)?$/i);
285
288
  return match ? match[1] : null;
@@ -30,7 +30,7 @@ export interface PackedCheckResult extends StageCheckResult {
30
30
  }
31
31
  export interface HttpResponse {
32
32
  statusCode: number;
33
- data: any;
33
+ data: unknown;
34
34
  }
35
35
  export type HttpGetFn = (url: string, headers?: Record<string, string>, timeoutMs?: number) => Promise<HttpResponse>;
36
36
  export interface ReleaseGateOptions {
@@ -14,11 +14,13 @@
14
14
  * Network errors resolve to "not_checked" so transient issues don't block.
15
15
  * GITHUB_TOKEN is used as Bearer auth but never logged or returned.
16
16
  */
17
- import { execSync } from "node:child_process";
18
17
  import { createHash } from "node:crypto";
19
- import { readFileSync, writeFileSync } from "node:fs";
18
+ import { readFileSync } from "node:fs";
20
19
  import { join } from "node:path";
21
20
  import { get } from "node:https";
21
+ import { resolveTrustedCommandLine } from "../runner/processSecurity.js";
22
+ import { runSimpleProcessSync } from "../runner/simpleProcess.js";
23
+ import { atomicWriteJsonFileSync } from "../utils/atomicFile.js";
22
24
  // ── Constants ──────────────────────────────────────────────────────
23
25
  const STAGE_ORDER = [
24
26
  "local_ready",
@@ -29,6 +31,7 @@ const STAGE_ORDER = [
29
31
  ];
30
32
  const LOCAL_TIMEOUT_MS = 300000;
31
33
  const REMOTE_TIMEOUT_MS = 10000;
34
+ const MAX_REMOTE_RESPONSE_BYTES = 5 * 1024 * 1024;
32
35
  const USER_AGENT = "PatchWarden-ReleaseGate";
33
36
  const PACK_FORBIDDEN_PATTERNS = [
34
37
  /(^|[\\/])\.env$/,
@@ -55,6 +58,14 @@ export function computeTokenDigest(token) {
55
58
  export async function httpsGetJson(url, headers, timeoutMs) {
56
59
  const timeout = timeoutMs ?? REMOTE_TIMEOUT_MS;
57
60
  return new Promise((resolve, reject) => {
61
+ let settled = false;
62
+ const fail = (error) => {
63
+ if (settled)
64
+ return;
65
+ settled = true;
66
+ clearTimeout(timer);
67
+ reject(error);
68
+ };
58
69
  const timer = setTimeout(() => {
59
70
  req.destroy(new Error(`Request timed out after ${timeout}ms`));
60
71
  }, timeout);
@@ -65,9 +76,31 @@ export async function httpsGetJson(url, headers, timeoutMs) {
65
76
  ...(headers || {}),
66
77
  },
67
78
  }, (res) => {
79
+ const declaredLength = Number(res.headers["content-length"]);
80
+ if (Number.isFinite(declaredLength) && declaredLength > MAX_REMOTE_RESPONSE_BYTES) {
81
+ res.destroy();
82
+ fail(new Error(`Response exceeds ${MAX_REMOTE_RESPONSE_BYTES} bytes`));
83
+ return;
84
+ }
68
85
  const chunks = [];
69
- res.on("data", (chunk) => chunks.push(chunk));
86
+ let totalBytes = 0;
87
+ let oversized = false;
88
+ res.on("data", (chunk) => {
89
+ if (oversized)
90
+ return;
91
+ totalBytes += chunk.length;
92
+ if (totalBytes > MAX_REMOTE_RESPONSE_BYTES) {
93
+ oversized = true;
94
+ res.destroy();
95
+ fail(new Error(`Response exceeds ${MAX_REMOTE_RESPONSE_BYTES} bytes`));
96
+ return;
97
+ }
98
+ chunks.push(chunk);
99
+ });
70
100
  res.on("end", () => {
101
+ if (oversized || settled)
102
+ return;
103
+ settled = true;
71
104
  clearTimeout(timer);
72
105
  const body = Buffer.concat(chunks).toString("utf-8");
73
106
  let data = null;
@@ -81,11 +114,10 @@ export async function httpsGetJson(url, headers, timeoutMs) {
81
114
  }
82
115
  resolve({ statusCode: res.statusCode ?? 0, data });
83
116
  });
117
+ res.on("aborted", () => fail(new Error("Response was aborted before completion")));
118
+ res.on("error", fail);
84
119
  });
85
- req.on("error", (err) => {
86
- clearTimeout(timer);
87
- reject(err);
88
- });
120
+ req.on("error", fail);
89
121
  });
90
122
  }
91
123
  /**
@@ -138,12 +170,14 @@ export function checkLocalReady(repoPath) {
138
170
  const commands = ["npm.cmd run build", "npm.cmd test", "npm.cmd run doctor:ci"];
139
171
  for (const cmdStr of commands) {
140
172
  try {
141
- execSync(cmdStr, {
142
- cwd: repoPath,
143
- encoding: "utf-8",
144
- timeout: LOCAL_TIMEOUT_MS,
145
- stdio: ["ignore", "pipe", "pipe"],
146
- });
173
+ const result = runLocalCommand(repoPath, cmdStr, LOCAL_TIMEOUT_MS);
174
+ if (result.spawnError)
175
+ throw new Error(result.spawnError);
176
+ if (result.timedOut)
177
+ throw new Error(`Command timed out after ${LOCAL_TIMEOUT_MS}ms`);
178
+ if (result.exitCode !== 0) {
179
+ throw Object.assign(new Error(`Command exited with code ${result.exitCode ?? -1}`), { status: result.exitCode ?? -1 });
180
+ }
147
181
  }
148
182
  catch (err) {
149
183
  if (err && typeof err === "object" && "status" in err) {
@@ -170,12 +204,14 @@ export function checkLocalReady(repoPath) {
170
204
  export function checkPackedReady(repoPath) {
171
205
  let packOutput;
172
206
  try {
173
- packOutput = execSync("npm.cmd pack --dry-run --json", {
174
- cwd: repoPath,
175
- encoding: "utf-8",
176
- timeout: LOCAL_TIMEOUT_MS,
177
- stdio: ["ignore", "pipe", "pipe"],
178
- });
207
+ const result = runLocalCommand(repoPath, "npm.cmd pack --dry-run --json", LOCAL_TIMEOUT_MS, 10 * 1024 * 1024);
208
+ if (result.spawnError)
209
+ throw new Error(result.spawnError);
210
+ if (result.timedOut)
211
+ throw new Error(`npm pack timed out after ${LOCAL_TIMEOUT_MS}ms`);
212
+ if (result.exitCode !== 0)
213
+ throw new Error(`npm pack exited with code ${result.exitCode ?? -1}`);
214
+ packOutput = result.stdout;
179
215
  }
180
216
  catch (err) {
181
217
  return {
@@ -240,7 +276,7 @@ export function checkPackedReady(repoPath) {
240
276
  };
241
277
  const manifestPath = join(repoPath, "release-artifact-manifest.json");
242
278
  try {
243
- writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
279
+ atomicWriteJsonFileSync(manifestPath, manifest);
244
280
  }
245
281
  catch (err) {
246
282
  return {
@@ -264,6 +300,17 @@ export function checkPackedReady(repoPath) {
264
300
  }
265
301
  return { status: "passed", manifestPath };
266
302
  }
303
+ function runLocalCommand(repoPath, commandLine, timeoutMs, maxStdoutBytes = 512 * 1024) {
304
+ const invocation = resolveTrustedCommandLine(commandLine, repoPath);
305
+ return runSimpleProcessSync({
306
+ command: invocation.command,
307
+ args: invocation.argsPrefix,
308
+ cwd: repoPath,
309
+ timeoutMs,
310
+ maxStdoutBytes,
311
+ maxStderrBytes: 512 * 1024,
312
+ });
313
+ }
267
314
  /**
268
315
  * Stage 3: published_verified
269
316
  * Queries the npm registry and confirms the given version exists.
@@ -280,11 +327,12 @@ export async function checkPublishedVerified(packageName, version, httpGet) {
280
327
  if (res.statusCode === 404) {
281
328
  return { status: "failed", reason: `Package "${packageName}" not found on npm registry` };
282
329
  }
283
- const data = res.data;
284
- if (!data || !data.versions || typeof data.versions !== "object") {
330
+ const data = asRecord(res.data);
331
+ const versions = asRecord(data?.versions);
332
+ if (!versions) {
285
333
  return { status: "failed", reason: "npm registry response missing versions object" };
286
334
  }
287
- if (Object.prototype.hasOwnProperty.call(data.versions, version)) {
335
+ if (Object.prototype.hasOwnProperty.call(versions, version)) {
288
336
  return { status: "passed" };
289
337
  }
290
338
  return {
@@ -340,12 +388,12 @@ export async function checkCiVerified(repo, branch, httpGet) {
340
388
  if (res.statusCode !== 200) {
341
389
  return { status: "not_checked", reason: `GitHub API returned ${res.statusCode}` };
342
390
  }
343
- const data = res.data;
391
+ const data = asRecord(res.data);
344
392
  const runs = Array.isArray(data?.workflow_runs) ? data.workflow_runs : [];
345
393
  if (runs.length === 0) {
346
394
  return { status: "not_checked", reason: `No CI runs found for branch "${branch}"` };
347
395
  }
348
- const conclusion = runs[0]?.conclusion;
396
+ const conclusion = asRecord(runs[0])?.conclusion;
349
397
  if (conclusion === "success") {
350
398
  return { status: "passed" };
351
399
  }
@@ -364,6 +412,11 @@ export async function checkCiVerified(repo, branch, httpGet) {
364
412
  };
365
413
  }
366
414
  }
415
+ function asRecord(value) {
416
+ return value && typeof value === "object" && !Array.isArray(value)
417
+ ? value
418
+ : null;
419
+ }
367
420
  // ── Orchestrator ───────────────────────────────────────────────────
368
421
  /**
369
422
  * Run release-gate stages up to and including targetStage, in order.
@@ -6,7 +6,21 @@ export interface AgentInvocation {
6
6
  commandLabel: string;
7
7
  promptMode: "inline" | "file";
8
8
  promptFilePath?: string;
9
+ environmentVariableNames: string[];
10
+ blockedEnvironmentVariableNames: string[];
9
11
  }
12
+ export interface ResolvedAgentLaunch {
13
+ command: string;
14
+ argsPrefix: string[];
15
+ }
16
+ /**
17
+ * Resolve the Windows npm shim used by OpenCode to its native executable.
18
+ * Node cannot spawn .cmd/.ps1 shims directly with shell disabled (EINVAL), and
19
+ * enabling a shell would make the task prompt part of shell parsing.
20
+ */
21
+ export declare function resolveAgentExecutable(agentName: string, command: string, platform?: NodeJS.Platform, pathValue?: string, fileExists?: (path: string) => boolean, cwd?: string): string;
22
+ /** Resolve a configured Agent to a native executable or verified npm CLI. */
23
+ export declare function resolveAgentLaunch(agentName: string, command: string, platform?: NodeJS.Platform, pathValue?: string, fileExists?: (path: string) => boolean, cwd?: string, adapterName?: string, readText?: (path: string) => string): ResolvedAgentLaunch;
10
24
  /**
11
25
  * Build agent invocation parameters from config.
12
26
  * Replaces {repo}, {prompt}, and {prompt_file} placeholders.
@@ -1,5 +1,89 @@
1
- import { basename } from "node:path";
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { basename, win32 } from "node:path";
2
3
  import { guardAgentCommand, sanitizePromptArg } from "../security/commandGuard.js";
4
+ import { resolveTrustedExecutable, sanitizeTrustedPath } from "./processSecurity.js";
5
+ const KNOWN_AGENT_NPM_PACKAGES = Object.freeze({
6
+ codex: "@openai/codex",
7
+ claude: "@anthropic-ai/claude-code",
8
+ gemini: "@google/gemini-cli",
9
+ copilot: "@github/copilot",
10
+ qwen: "@qwen-code/qwen-code",
11
+ opencode: "opencode-ai",
12
+ });
13
+ /**
14
+ * Resolve the Windows npm shim used by OpenCode to its native executable.
15
+ * Node cannot spawn .cmd/.ps1 shims directly with shell disabled (EINVAL), and
16
+ * enabling a shell would make the task prompt part of shell parsing.
17
+ */
18
+ export function resolveAgentExecutable(agentName, command, platform = process.platform, pathValue = process.env.PATH || "", fileExists = existsSync, cwd = process.cwd()) {
19
+ return resolveAgentLaunch(agentName, command, platform, pathValue, fileExists, cwd).command;
20
+ }
21
+ /** Resolve a configured Agent to a native executable or verified npm CLI. */
22
+ export function resolveAgentLaunch(agentName, command, platform = process.platform, pathValue = process.env.PATH || "", fileExists = existsSync, cwd = process.cwd(), adapterName = agentName, readText = (path) => readFileSync(path, "utf-8")) {
23
+ if (platform !== "win32")
24
+ return { command, argsPrefix: [] };
25
+ const commandName = win32.basename(command).toLowerCase();
26
+ if (adapterName === "opencode" && new Set(["opencode", "opencode.cmd", "opencode.ps1", "opencode.bat"]).has(commandName)) {
27
+ const roots = win32.isAbsolute(command)
28
+ ? [win32.dirname(command)]
29
+ : sanitizeTrustedPath(pathValue, cwd, "win32").split(win32.delimiter).filter(Boolean);
30
+ for (const root of roots) {
31
+ const nativeExecutable = win32.join(root, "node_modules", "opencode-ai", "bin", "opencode.exe");
32
+ if (fileExists(nativeExecutable))
33
+ return { command: nativeExecutable, argsPrefix: [] };
34
+ }
35
+ }
36
+ const resolved = resolveTrustedExecutable(command, cwd, {
37
+ platform,
38
+ pathValue,
39
+ fileExists,
40
+ });
41
+ if (!/\.(?:cmd|bat|ps1)$/i.test(resolved))
42
+ return { command: resolved, argsPrefix: [] };
43
+ const packageName = KNOWN_AGENT_NPM_PACKAGES[adapterName] || KNOWN_AGENT_NPM_PACKAGES[agentName];
44
+ if (!packageName) {
45
+ throw new Error(`Windows shell shim is not allowed for Agent "${agentName}": ${resolved}`);
46
+ }
47
+ const packageRoot = win32.resolve(win32.dirname(resolved), "node_modules", ...packageName.split("/"));
48
+ const manifestPath = win32.join(packageRoot, "package.json");
49
+ if (!fileExists(manifestPath)) {
50
+ throw new Error(`Verified npm package manifest not found for Agent "${agentName}": ${manifestPath}`);
51
+ }
52
+ let manifest;
53
+ try {
54
+ manifest = JSON.parse(readText(manifestPath));
55
+ }
56
+ catch {
57
+ throw new Error(`Invalid npm package manifest for Agent "${agentName}": ${manifestPath}`);
58
+ }
59
+ if (manifest.name !== packageName) {
60
+ throw new Error(`Unexpected npm package identity for Agent "${agentName}"`);
61
+ }
62
+ const shimName = win32.basename(resolved).replace(/\.(?:cmd|bat|ps1)$/i, "");
63
+ const binPath = resolvePackageBin(manifest.bin, [agentName, adapterName, shimName]);
64
+ if (!binPath)
65
+ throw new Error(`npm package does not declare a CLI for Agent "${agentName}"`);
66
+ const cliPath = win32.resolve(packageRoot, binPath);
67
+ const relativeCli = win32.relative(packageRoot, cliPath);
68
+ if (!relativeCli || relativeCli === ".." || relativeCli.startsWith(`..${win32.sep}`) || win32.isAbsolute(relativeCli) || !fileExists(cliPath)) {
69
+ throw new Error(`npm CLI entry escapes or is missing for Agent "${agentName}"`);
70
+ }
71
+ const node = resolveTrustedExecutable("node", cwd, { platform, pathValue, fileExists });
72
+ return { command: node, argsPrefix: [cliPath] };
73
+ }
74
+ function resolvePackageBin(bin, names) {
75
+ if (typeof bin === "string")
76
+ return bin;
77
+ if (!bin || typeof bin !== "object" || Array.isArray(bin))
78
+ return null;
79
+ const entries = bin;
80
+ for (const name of names) {
81
+ if (typeof entries[name] === "string")
82
+ return entries[name];
83
+ }
84
+ const values = Object.values(entries).filter((value) => typeof value === "string");
85
+ return values.length === 1 ? values[0] : null;
86
+ }
3
87
  /**
4
88
  * Build agent invocation parameters from config.
5
89
  * Replaces {repo}, {prompt}, and {prompt_file} placeholders.
@@ -7,6 +91,12 @@ import { guardAgentCommand, sanitizePromptArg } from "../security/commandGuard.j
7
91
  */
8
92
  export function buildAgentInvocation(agentName, repoPath, prompt, config, promptFilePath) {
9
93
  const agentCmd = guardAgentCommand(agentName, config);
94
+ const launch = resolveAgentLaunch(agentName, agentCmd.command, process.platform, process.env.PATH || "", existsSync, repoPath, config.agents[agentName]?.adapter || agentName);
95
+ const environmentVariableNames = [...(config.agents[agentName]?.envAllowlist ?? [])];
96
+ const blockedEnvironmentVariableNames = [
97
+ "CONTROL_PLANE_API_KEY",
98
+ config.http?.ownerTokenEnv || "PATCHWARDEN_OWNER_TOKEN",
99
+ ];
10
100
  const sanitizedPrompt = sanitizePromptArg(prompt);
11
101
  const hasPromptFilePlaceholder = agentCmd.args.includes("{prompt_file}");
12
102
  const promptMode = hasPromptFilePlaceholder && promptFilePath ? "file" : "inline";
@@ -20,11 +110,13 @@ export function buildAgentInvocation(agentName, repoPath, prompt, config, prompt
20
110
  return arg;
21
111
  });
22
112
  return {
23
- command: agentCmd.command,
24
- args: resolvedArgs,
113
+ command: launch.command,
114
+ args: [...launch.argsPrefix, ...resolvedArgs],
25
115
  cwd: repoPath,
26
- commandLabel: `${basename(agentCmd.command)} (configured agent command)`,
116
+ commandLabel: `${basename(launch.command)} (configured agent command)`,
27
117
  promptMode,
118
+ environmentVariableNames,
119
+ blockedEnvironmentVariableNames,
28
120
  ...(promptMode === "file" && promptFilePath ? { promptFilePath } : {}),
29
121
  };
30
122
  }
@@ -51,6 +51,8 @@ export interface ChangeArtifacts {
51
51
  diff: string;
52
52
  diff_available: boolean;
53
53
  diff_truncated: boolean;
54
+ diff_redacted?: boolean;
55
+ diff_redaction_categories?: string[];
54
56
  diff_size_bytes: number;
55
57
  additions: number;
56
58
  deletions: number;
@@ -66,10 +68,16 @@ export interface ChangeArtifacts {
66
68
  unavailable_reason: string | null;
67
69
  artifact_hygiene: ArtifactHygiene;
68
70
  }
69
- export declare function captureRepoSnapshot(repoPath: string): Promise<RepoSnapshot>;
71
+ export declare function captureRepoSnapshot(repoPath: string, signal?: AbortSignal): Promise<RepoSnapshot>;
70
72
  export declare function writeSnapshot(taskDir: string, filename: string, snapshot: RepoSnapshot): void;
71
- export declare function buildChangeArtifacts(repoPath: string, before: RepoSnapshot, after: RepoSnapshot): Promise<ChangeArtifacts>;
72
- export declare function compareSnapshots(before: RepoSnapshot, after: RepoSnapshot): ChangedFile[];
73
+ export declare function buildChangeArtifacts(repoPath: string, before: RepoSnapshot, after: RepoSnapshot, signal?: AbortSignal): Promise<ChangeArtifacts>;
74
+ export declare function sanitizeDiffEvidence(input: string, maxBytes?: number): {
75
+ content: string;
76
+ truncated: boolean;
77
+ redacted: boolean;
78
+ redaction_categories: string[];
79
+ };
80
+ export declare function compareSnapshots(before: RepoSnapshot, after: RepoSnapshot, platform?: NodeJS.Platform): ChangedFile[];
73
81
  export declare function emptyArtifactHygiene(): ArtifactHygiene;
74
82
  export interface ExternalDirtyFile {
75
83
  path: string;
@@ -81,13 +89,13 @@ export interface ExternalDirtyFile {
81
89
  * Extract files that are dirty in the workspace but outside the target repo.
82
90
  * Used to establish a baseline before task execution.
83
91
  */
84
- export declare function extractExternalDirtyFiles(workspaceSnapshot: RepoSnapshot, repoPath: string, workspaceRoot: string): ExternalDirtyFile[];
92
+ export declare function extractExternalDirtyFiles(workspaceSnapshot: RepoSnapshot, repoPath: string, workspaceRoot: string, platform?: NodeJS.Platform): ExternalDirtyFile[];
85
93
  /**
86
94
  * Compare external dirty files between baseline and post-task snapshots.
87
95
  * Returns files that are NEW (not present in baseline) or CHANGED
88
96
  * (same path but different sha256, meaning the task modified them).
89
97
  */
90
- export declare function findNewExternalDirtyFiles(baseline: ExternalDirtyFile[], current: ExternalDirtyFile[]): ExternalDirtyFile[];
98
+ export declare function findNewExternalDirtyFiles(baseline: ExternalDirtyFile[], current: ExternalDirtyFile[], platform?: NodeJS.Platform): ExternalDirtyFile[];
91
99
  export interface ArtifactManifestEntry {
92
100
  path: string;
93
101
  type: string;