patchwarden 1.5.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (597) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +194 -26
  3. package/README.md +160 -22
  4. package/dist/assessments/agentAssessor.d.ts +1 -1
  5. package/dist/assessments/agentAssessor.js +24 -15
  6. package/dist/assessments/assessmentStore.d.ts +1 -1
  7. package/dist/assessments/assessmentStore.js +44 -36
  8. package/dist/assessments/confirmCli.js +5 -4
  9. package/dist/config.d.ts +22 -0
  10. package/dist/config.js +98 -11
  11. package/dist/control/fileManager.d.ts +2 -0
  12. package/dist/control/fileManager.js +39 -0
  13. package/dist/control/middleware/auth.d.ts +16 -0
  14. package/dist/control/middleware/auth.js +17 -0
  15. package/dist/control/middleware/static.d.ts +3 -0
  16. package/dist/control/middleware/static.js +78 -0
  17. package/dist/control/routeTable.d.ts +24 -0
  18. package/dist/control/routeTable.js +263 -0
  19. package/dist/control/routes/audit.d.ts +15 -0
  20. package/dist/control/routes/audit.js +287 -0
  21. package/dist/control/routes/evidence.d.ts +4 -0
  22. package/dist/control/routes/evidence.js +96 -0
  23. package/dist/control/routes/lineage.d.ts +3 -0
  24. package/dist/control/routes/lineage.js +71 -0
  25. package/dist/control/routes/policy.d.ts +3 -0
  26. package/dist/control/routes/policy.js +81 -0
  27. package/dist/control/routes/process.d.ts +12 -0
  28. package/dist/control/routes/process.js +487 -0
  29. package/dist/control/routes/sessions.d.ts +30 -0
  30. package/dist/control/routes/sessions.js +287 -0
  31. package/dist/control/routes/status.d.ts +51 -0
  32. package/dist/control/routes/status.js +307 -0
  33. package/dist/control/routes/taskActions.d.ts +21 -0
  34. package/dist/control/routes/taskActions.js +197 -0
  35. package/dist/control/routes/tasks.d.ts +24 -0
  36. package/dist/control/routes/tasks.js +309 -0
  37. package/dist/control/routes/workspace.d.ts +15 -0
  38. package/dist/control/routes/workspace.js +220 -0
  39. package/dist/control/runtime.d.ts +92 -0
  40. package/dist/control/runtime.js +425 -0
  41. package/dist/control/server.d.ts +13 -0
  42. package/dist/control/server.js +150 -0
  43. package/dist/control/shared.d.ts +59 -0
  44. package/dist/control/shared.js +341 -0
  45. package/dist/controlCenter.d.ts +6 -0
  46. package/dist/controlCenter.js +7 -2197
  47. package/dist/direct/directAudit.js +234 -225
  48. package/dist/direct/directGuards.d.ts +2 -1
  49. package/dist/direct/directGuards.js +72 -37
  50. package/dist/direct/directPatch.d.ts +6 -1
  51. package/dist/direct/directPatch.js +55 -8
  52. package/dist/direct/directSessionStore.d.ts +16 -6
  53. package/dist/direct/directSessionStore.js +127 -35
  54. package/dist/direct/directVerification.js +8 -16
  55. package/dist/doctor.d.ts +18 -1
  56. package/dist/doctor.js +614 -373
  57. package/dist/goal/acceptanceEngine.d.ts +1 -1
  58. package/dist/goal/goalProgress.js +75 -69
  59. package/dist/goal/goalReport.d.ts +54 -0
  60. package/dist/goal/goalReport.js +197 -0
  61. package/dist/goal/goalStatus.d.ts +8 -0
  62. package/dist/goal/goalStatus.js +10 -3
  63. package/dist/goal/goalStore.d.ts +9 -2
  64. package/dist/goal/goalStore.js +152 -45
  65. package/dist/goal/handoffExport.js +6 -6
  66. package/dist/goal/specKitImport.d.ts +63 -0
  67. package/dist/goal/specKitImport.js +221 -0
  68. package/dist/goal/subgoalSync.js +13 -10
  69. package/dist/goal/worktreeManager.d.ts +2 -0
  70. package/dist/goal/worktreeManager.js +209 -136
  71. package/dist/httpServer.js +126 -44
  72. package/dist/index.js +7 -4
  73. package/dist/logging.d.ts +7 -1
  74. package/dist/logging.js +13 -3
  75. package/dist/policy/projectPolicy.js +5 -2
  76. package/dist/release/releaseGate.d.ts +1 -1
  77. package/dist/release/releaseGate.js +78 -25
  78. package/dist/runner/agentInvocation.d.ts +14 -0
  79. package/dist/runner/agentInvocation.js +96 -4
  80. package/dist/runner/changeCapture.d.ts +14 -6
  81. package/dist/runner/changeCapture.js +287 -81
  82. package/dist/runner/cli.js +7 -6
  83. package/dist/runner/postTaskCleanup.js +60 -7
  84. package/dist/runner/processSecurity.d.ts +56 -0
  85. package/dist/runner/processSecurity.js +373 -0
  86. package/dist/runner/runTask.d.ts +1 -1
  87. package/dist/runner/runTask.js +339 -287
  88. package/dist/runner/simpleProcess.d.ts +3 -0
  89. package/dist/runner/simpleProcess.js +52 -38
  90. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  91. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  92. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
  93. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  94. package/dist/runner/taskStatusStore.d.ts +11 -0
  95. package/dist/runner/taskStatusStore.js +24 -0
  96. package/dist/runner/watch.d.ts +14 -1
  97. package/dist/runner/watch.js +265 -54
  98. package/dist/security/contentRedaction.d.ts +6 -0
  99. package/dist/security/contentRedaction.js +24 -2
  100. package/dist/security/discoveryTokenStore.d.ts +3 -2
  101. package/dist/security/discoveryTokenStore.js +17 -2
  102. package/dist/security/loopbackHost.d.ts +2 -0
  103. package/dist/security/loopbackHost.js +16 -0
  104. package/dist/security/pathGuard.js +32 -17
  105. package/dist/security/planGuard.js +2 -1
  106. package/dist/security/runtimeGuard.js +9 -6
  107. package/dist/security/secretComparison.d.ts +3 -0
  108. package/dist/security/secretComparison.js +11 -0
  109. package/dist/security/sensitiveGuard.d.ts +5 -0
  110. package/dist/security/sensitiveGuard.js +27 -8
  111. package/dist/security/toolInvocationGuard.d.ts +2 -2
  112. package/dist/security/toolInvocationGuard.js +1 -1
  113. package/dist/security/workspaceRootGuard.d.ts +8 -0
  114. package/dist/security/workspaceRootGuard.js +47 -0
  115. package/dist/smoke-test.js +280 -273
  116. package/dist/tools/catalog/index.d.ts +4 -0
  117. package/dist/tools/catalog/index.js +4 -0
  118. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  119. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  120. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +28 -15
  121. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  122. package/dist/tools/definitions/toolDefs.js +1177 -0
  123. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  124. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  125. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +8 -63
  126. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +111 -37
  127. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
  128. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +10 -10
  129. package/dist/tools/diagnostics/index.d.ts +6 -0
  130. package/dist/tools/diagnostics/index.js +6 -0
  131. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  132. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  133. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +14 -10
  134. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +25 -15
  135. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  136. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  137. package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +1 -1
  138. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +8 -8
  139. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +2 -2
  140. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  141. package/dist/tools/direct/index.d.ts +3 -0
  142. package/dist/tools/direct/index.js +3 -0
  143. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  144. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  145. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  146. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  147. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  148. package/dist/tools/discovery/index.d.ts +3 -0
  149. package/dist/tools/discovery/index.js +3 -0
  150. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  151. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  152. package/dist/tools/dispatch/coreDispatch.d.ts +9 -0
  153. package/dist/tools/dispatch/coreDispatch.js +283 -0
  154. package/dist/tools/dispatch/diagnosticDispatch.d.ts +12 -0
  155. package/dist/tools/dispatch/diagnosticDispatch.js +80 -0
  156. package/dist/tools/dispatch/directDispatch.d.ts +8 -0
  157. package/dist/tools/dispatch/directDispatch.js +116 -0
  158. package/dist/tools/dispatch/goalDispatch.d.ts +8 -0
  159. package/dist/tools/dispatch/goalDispatch.js +92 -0
  160. package/dist/tools/dispatch/releaseDispatch.d.ts +8 -0
  161. package/dist/tools/dispatch/releaseDispatch.js +46 -0
  162. package/dist/tools/dispatch/types.d.ts +23 -0
  163. package/dist/tools/dispatch/types.js +15 -0
  164. package/dist/tools/dispatch/validation.d.ts +7 -0
  165. package/dist/tools/dispatch/validation.js +71 -0
  166. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  167. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +2 -2
  168. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  169. package/dist/tools/goals/index.d.ts +3 -0
  170. package/dist/tools/goals/index.js +3 -0
  171. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  172. package/dist/tools/registry.d.ts +10 -14
  173. package/dist/tools/registry.js +47 -1651
  174. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  175. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  176. package/dist/tools/release/index.d.ts +2 -0
  177. package/dist/tools/release/index.js +2 -0
  178. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  179. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  180. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  181. package/dist/tools/tasks/cancelTask.js +85 -0
  182. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
  183. package/dist/tools/{createTask.js → tasks/createTask.js} +46 -31
  184. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  185. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
  186. package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +6 -0
  187. package/dist/tools/tasks/evidencePack.js +387 -0
  188. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  189. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  190. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
  191. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  192. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
  193. package/dist/tools/tasks/index.d.ts +18 -0
  194. package/dist/tools/tasks/index.js +18 -0
  195. package/dist/tools/tasks/killTask.d.ts +1 -0
  196. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  197. package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
  198. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
  199. package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
  200. package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +2 -2
  201. package/dist/tools/tasks/retryTask.js +60 -0
  202. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  203. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +59 -31
  204. package/dist/tools/tasks/runVerification.js +38 -0
  205. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  206. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  207. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  208. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  209. package/dist/tools/workspace/applyPatch.js +53 -0
  210. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  211. package/dist/tools/workspace/index.d.ts +10 -0
  212. package/dist/tools/workspace/index.js +10 -0
  213. package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
  214. package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
  215. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  216. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  217. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  218. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  219. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  220. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  221. package/dist/tools/workspace/syncFile.js +134 -0
  222. package/dist/utils/atomicFile.d.ts +7 -0
  223. package/dist/utils/atomicFile.js +48 -0
  224. package/dist/utils/boundedFile.d.ts +8 -0
  225. package/dist/utils/boundedFile.js +93 -0
  226. package/dist/utils/lockedJsonFile.d.ts +15 -0
  227. package/dist/utils/lockedJsonFile.js +224 -0
  228. package/dist/utils/platform.d.ts +11 -0
  229. package/dist/utils/platform.js +26 -0
  230. package/dist/utils/stableJson.d.ts +2 -0
  231. package/dist/utils/stableJson.js +12 -0
  232. package/dist/version.d.ts +3 -2
  233. package/dist/version.js +3 -2
  234. package/dist/watcherStatus.d.ts +2 -2
  235. package/dist/watcherStatus.js +18 -0
  236. package/docs/CODE_WIKI.md +1648 -0
  237. package/docs/agentseal-integration.md +150 -0
  238. package/docs/architecture.md +23 -0
  239. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  240. package/docs/assets/patchwarden-oss-demo.gif +0 -0
  241. package/docs/control-center/README.md +4 -4
  242. package/docs/dashboard-overview.md +86 -0
  243. package/docs/demo.md +8 -0
  244. package/docs/desktop-app.md +175 -0
  245. package/docs/direct-session-workflow.md +98 -0
  246. package/docs/evidence-pack-schema.md +215 -0
  247. package/docs/lineage-evidence-pack-workflow.md +127 -0
  248. package/docs/mcp-inspector-testing.md +200 -0
  249. package/docs/open-source-application.md +168 -0
  250. package/docs/opencode-worker.md +151 -0
  251. package/docs/openhands-worker.md +181 -0
  252. package/docs/release-checklist.md +1 -1
  253. package/docs/release-evidence.md +82 -0
  254. package/docs/spec-kit-integration.md +131 -0
  255. package/docs/task-safe-review-workflow.md +98 -0
  256. package/docs/threat-model.md +97 -0
  257. package/docs/user-feedback.md +40 -0
  258. package/docs/why-patchwarden.md +110 -0
  259. package/examples/config.example.json +4 -2
  260. package/package.json +28 -11
  261. package/scripts/README.md +15 -6
  262. package/scripts/build.js +11 -0
  263. package/scripts/checks/brand-check.js +3 -0
  264. package/scripts/checks/build-output-check.js +37 -0
  265. package/scripts/checks/control-center-smoke.js +447 -6
  266. package/scripts/checks/control-smoke.js +19 -8
  267. package/scripts/checks/http-mcp-smoke.js +82 -4
  268. package/scripts/checks/lifecycle-smoke.js +39 -33
  269. package/scripts/checks/mcp-manifest-check.js +1 -1
  270. package/scripts/checks/mcp-smoke.js +30 -4
  271. package/scripts/checks/package-manifest-check.js +20 -7
  272. package/scripts/checks/unit-tests.js +3 -5
  273. package/scripts/checks/watcher-supervisor-smoke.js +9 -0
  274. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  275. package/scripts/control/manage-patchwarden.ps1 +69 -4
  276. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  277. package/scripts/control/run-background-supervisor.ps1 +41 -0
  278. package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
  279. package/scripts/generate-demo-gif.py +320 -0
  280. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  281. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  282. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  283. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  284. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  285. package/scripts/lib/clean-generated-output.js +11 -0
  286. package/scripts/lib/file-discovery.js +12 -0
  287. package/scripts/release/desktop-preflight.js +211 -0
  288. package/scripts/release/pack-clean.js +59 -14
  289. package/src/assessments/agentAssessor.ts +29 -15
  290. package/src/assessments/assessmentStore.ts +55 -43
  291. package/src/assessments/confirmCli.ts +5 -4
  292. package/src/config.ts +105 -11
  293. package/src/control/fileManager.ts +40 -0
  294. package/src/control/middleware/auth.ts +35 -0
  295. package/src/control/middleware/static.ts +84 -0
  296. package/src/control/routeTable.ts +301 -0
  297. package/src/control/routes/audit.ts +329 -0
  298. package/src/control/routes/evidence.ts +107 -0
  299. package/src/control/routes/lineage.ts +92 -0
  300. package/src/control/routes/policy.ts +81 -0
  301. package/src/control/routes/process.ts +505 -0
  302. package/src/control/routes/sessions.ts +314 -0
  303. package/src/control/routes/status.ts +405 -0
  304. package/src/control/routes/taskActions.ts +214 -0
  305. package/src/control/routes/tasks.ts +331 -0
  306. package/src/control/routes/workspace.ts +234 -0
  307. package/src/control/runtime.ts +508 -0
  308. package/src/control/server.ts +165 -0
  309. package/src/control/shared.ts +360 -0
  310. package/src/controlCenter.ts +7 -2347
  311. package/src/direct/directAudit.ts +17 -9
  312. package/src/direct/directGuards.ts +100 -51
  313. package/src/direct/directPatch.ts +95 -8
  314. package/src/direct/directSessionStore.ts +211 -55
  315. package/src/direct/directVerification.ts +9 -23
  316. package/src/doctor.ts +766 -492
  317. package/src/goal/acceptanceEngine.ts +1 -1
  318. package/src/goal/goalProgress.ts +17 -13
  319. package/src/goal/goalReport.ts +263 -0
  320. package/src/goal/goalStatus.ts +25 -3
  321. package/src/goal/goalStore.ts +241 -54
  322. package/src/goal/handoffExport.ts +6 -6
  323. package/src/goal/specKitImport.ts +355 -0
  324. package/src/goal/subgoalSync.ts +15 -10
  325. package/src/goal/worktreeManager.ts +139 -22
  326. package/src/httpServer.ts +130 -44
  327. package/src/index.ts +7 -4
  328. package/src/logging.ts +20 -4
  329. package/src/policy/projectPolicy.ts +5 -2
  330. package/src/release/releaseGate.ts +74 -27
  331. package/src/runner/agentInvocation.ts +126 -4
  332. package/src/runner/changeCapture.ts +342 -80
  333. package/src/runner/cli.ts +7 -6
  334. package/src/runner/postTaskCleanup.ts +55 -7
  335. package/src/runner/processSecurity.ts +433 -0
  336. package/src/runner/runTask.ts +441 -292
  337. package/src/runner/simpleProcess.ts +68 -33
  338. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  339. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
  340. package/src/runner/taskStatusStore.ts +54 -0
  341. package/src/runner/watch.ts +290 -57
  342. package/src/security/contentRedaction.ts +31 -2
  343. package/src/security/discoveryTokenStore.ts +18 -4
  344. package/src/security/loopbackHost.ts +19 -0
  345. package/src/security/pathGuard.ts +35 -16
  346. package/src/security/planGuard.ts +2 -1
  347. package/src/security/runtimeGuard.ts +9 -6
  348. package/src/security/secretComparison.ts +13 -0
  349. package/src/security/sensitiveGuard.ts +27 -8
  350. package/src/security/toolInvocationGuard.ts +2 -2
  351. package/src/security/workspaceRootGuard.ts +53 -0
  352. package/src/smoke-test.ts +276 -273
  353. package/src/tools/catalog/index.ts +4 -0
  354. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  355. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +29 -16
  356. package/src/tools/definitions/toolDefs.ts +1264 -0
  357. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  358. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +196 -89
  359. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +11 -11
  360. package/src/tools/diagnostics/index.ts +6 -0
  361. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  362. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +29 -19
  363. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  364. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +9 -9
  365. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +11 -9
  366. package/src/tools/direct/index.ts +3 -0
  367. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  368. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  369. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  370. package/src/tools/discovery/index.ts +3 -0
  371. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  372. package/src/tools/dispatch/coreDispatch.ts +379 -0
  373. package/src/tools/dispatch/diagnosticDispatch.ts +106 -0
  374. package/src/tools/dispatch/directDispatch.ts +168 -0
  375. package/src/tools/dispatch/goalDispatch.ts +128 -0
  376. package/src/tools/dispatch/releaseDispatch.ts +66 -0
  377. package/src/tools/dispatch/types.ts +24 -0
  378. package/src/tools/dispatch/validation.ts +78 -0
  379. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  380. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +43 -28
  381. package/src/tools/goals/index.ts +3 -0
  382. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  383. package/src/tools/registry.ts +54 -1858
  384. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  385. package/src/tools/release/index.ts +2 -0
  386. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  387. package/src/tools/tasks/cancelTask.ts +109 -0
  388. package/src/tools/{createTask.ts → tasks/createTask.ts} +56 -33
  389. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
  390. package/src/tools/tasks/evidencePack.ts +504 -0
  391. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  392. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
  393. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  394. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
  395. package/src/tools/tasks/index.ts +18 -0
  396. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
  397. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
  398. package/src/tools/tasks/retryTask.ts +69 -0
  399. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +70 -35
  400. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  401. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  402. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  403. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  404. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  405. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  406. package/src/tools/workspace/index.ts +10 -0
  407. package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
  408. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  409. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  410. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  411. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  412. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  413. package/src/tools/workspace/syncFile.ts +292 -0
  414. package/src/utils/atomicFile.ts +58 -0
  415. package/src/utils/boundedFile.ts +116 -0
  416. package/src/utils/lockedJsonFile.ts +251 -0
  417. package/src/utils/platform.ts +31 -0
  418. package/src/utils/stableJson.ts +12 -0
  419. package/src/version.ts +3 -2
  420. package/src/watcherStatus.ts +19 -1
  421. package/ui/desktop-bootstrap.js +11 -0
  422. package/ui/desktop-bridge.js +36 -0
  423. package/ui/desktop.css +160 -0
  424. package/ui/getting-started.js +93 -0
  425. package/ui/i18n.js +484 -0
  426. package/ui/log-parser.js +30 -0
  427. package/ui/pages/audit.html +204 -3
  428. package/ui/pages/dashboard.html +937 -79
  429. package/ui/pages/direct-sessions.html +609 -60
  430. package/ui/pages/getting-started.html +43 -0
  431. package/ui/pages/logs.html +105 -22
  432. package/ui/pages/settings.html +101 -0
  433. package/ui/pages/task-detail.html +468 -438
  434. package/ui/pages/tasks.html +648 -65
  435. package/ui/pages/workspace.html +38 -1
  436. package/ui/settings.js +245 -0
  437. package/PatchWarden-Control.cmd +0 -6
  438. package/Restart-PatchWarden-Control.cmd +0 -6
  439. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  440. package/dist/test/unit/acceptance-engine.test.js +0 -228
  441. package/dist/test/unit/agent-router.test.d.ts +0 -1
  442. package/dist/test/unit/agent-router.test.js +0 -287
  443. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  444. package/dist/test/unit/android-doctor.test.js +0 -118
  445. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  446. package/dist/test/unit/audit-checks.test.js +0 -350
  447. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  448. package/dist/test/unit/chinese-path.test.js +0 -91
  449. package/dist/test/unit/command-guard.test.d.ts +0 -1
  450. package/dist/test/unit/command-guard.test.js +0 -160
  451. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  452. package/dist/test/unit/diagnose-task.test.js +0 -457
  453. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  454. package/dist/test/unit/direct-guards.test.js +0 -213
  455. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  456. package/dist/test/unit/discovery-token-store.test.js +0 -139
  457. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  458. package/dist/test/unit/evidence-pack.test.js +0 -130
  459. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  460. package/dist/test/unit/goal-graph.test.js +0 -298
  461. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  462. package/dist/test/unit/goal-progress.test.js +0 -381
  463. package/dist/test/unit/goal-status.test.d.ts +0 -1
  464. package/dist/test/unit/goal-status.test.js +0 -215
  465. package/dist/test/unit/goal-store.test.d.ts +0 -1
  466. package/dist/test/unit/goal-store.test.js +0 -253
  467. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  468. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  469. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  470. package/dist/test/unit/goal-tools-registry.test.js +0 -190
  471. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  472. package/dist/test/unit/handoff-export.test.js +0 -263
  473. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  474. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  475. package/dist/test/unit/logging.test.d.ts +0 -1
  476. package/dist/test/unit/logging.test.js +0 -397
  477. package/dist/test/unit/path-guard.test.d.ts +0 -1
  478. package/dist/test/unit/path-guard.test.js +0 -109
  479. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  480. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  481. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  482. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  483. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  484. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  485. package/dist/test/unit/release-gate.test.d.ts +0 -1
  486. package/dist/test/unit/release-gate.test.js +0 -242
  487. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  488. package/dist/test/unit/run-task-loop.test.js +0 -380
  489. package/dist/test/unit/safe-status.test.d.ts +0 -1
  490. package/dist/test/unit/safe-status.test.js +0 -165
  491. package/dist/test/unit/safe-views.test.d.ts +0 -1
  492. package/dist/test/unit/safe-views.test.js +0 -171
  493. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  494. package/dist/test/unit/schema-drift-check.test.js +0 -176
  495. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  496. package/dist/test/unit/sensitive-guard.test.js +0 -104
  497. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  498. package/dist/test/unit/subgoal-sync.test.js +0 -183
  499. package/dist/test/unit/sync-file.test.d.ts +0 -1
  500. package/dist/test/unit/sync-file.test.js +0 -154
  501. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  502. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  503. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  504. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  505. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  506. package/dist/test/unit/toolSearch.test.js +0 -571
  507. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  508. package/dist/test/unit/watcher-status.test.js +0 -169
  509. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  510. package/dist/test/unit/worktree-manager.test.js +0 -176
  511. package/dist/tools/applyPatch.js +0 -41
  512. package/dist/tools/cancelTask.d.ts +0 -30
  513. package/dist/tools/cancelTask.js +0 -64
  514. package/dist/tools/evidencePack.js +0 -168
  515. package/dist/tools/finalizeDirectSession.js +0 -84
  516. package/dist/tools/goalSubgoalTask.js +0 -110
  517. package/dist/tools/killTask.d.ts +0 -15
  518. package/dist/tools/retryTask.js +0 -45
  519. package/dist/tools/runVerification.js +0 -36
  520. package/dist/tools/syncFile.js +0 -65
  521. package/docs/control-center/control-center-daily-driver.md +0 -211
  522. package/docs/control-center/control-center-mvp.md +0 -205
  523. package/docs/control-center/control-center-phase2.md +0 -159
  524. package/docs/release-v0.6.0.md +0 -71
  525. package/docs/release-v0.6.1.md +0 -75
  526. package/docs/release-v0.6.4.md +0 -45
  527. package/src/test/unit/acceptance-engine.test.ts +0 -261
  528. package/src/test/unit/agent-router.test.ts +0 -342
  529. package/src/test/unit/android-doctor.test.ts +0 -158
  530. package/src/test/unit/audit-checks.test.ts +0 -567
  531. package/src/test/unit/chinese-path.test.ts +0 -106
  532. package/src/test/unit/command-guard.test.ts +0 -221
  533. package/src/test/unit/diagnose-task.test.ts +0 -544
  534. package/src/test/unit/direct-guards.test.ts +0 -297
  535. package/src/test/unit/discovery-token-store.test.ts +0 -181
  536. package/src/test/unit/evidence-pack.test.ts +0 -142
  537. package/src/test/unit/goal-graph.test.ts +0 -347
  538. package/src/test/unit/goal-progress.test.ts +0 -538
  539. package/src/test/unit/goal-status.test.ts +0 -270
  540. package/src/test/unit/goal-store.test.ts +0 -318
  541. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  542. package/src/test/unit/goal-tools-registry.test.ts +0 -243
  543. package/src/test/unit/handoff-export.test.ts +0 -295
  544. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  545. package/src/test/unit/logging.test.ts +0 -497
  546. package/src/test/unit/path-guard.test.ts +0 -150
  547. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  548. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  549. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  550. package/src/test/unit/release-gate.test.ts +0 -314
  551. package/src/test/unit/run-task-loop.test.ts +0 -425
  552. package/src/test/unit/safe-status.test.ts +0 -187
  553. package/src/test/unit/safe-views.test.ts +0 -184
  554. package/src/test/unit/schema-drift-check.test.ts +0 -260
  555. package/src/test/unit/sensitive-guard.test.ts +0 -124
  556. package/src/test/unit/subgoal-sync.test.ts +0 -236
  557. package/src/test/unit/sync-file.test.ts +0 -231
  558. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  559. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  560. package/src/test/unit/toolSearch.test.ts +0 -756
  561. package/src/test/unit/watcher-status.test.ts +0 -190
  562. package/src/test/unit/worktree-manager.test.ts +0 -247
  563. package/src/tools/cancelTask.ts +0 -70
  564. package/src/tools/evidencePack.ts +0 -205
  565. package/src/tools/retryTask.ts +0 -53
  566. package/src/tools/syncFile.ts +0 -122
  567. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  568. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  569. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  570. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  571. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  572. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  573. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  574. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  575. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  576. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  577. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  578. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  579. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  580. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  581. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  582. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  583. /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
  584. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  585. /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
  586. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  587. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  588. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  589. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  590. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  591. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  592. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  593. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  594. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  595. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  596. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
  597. /package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +0 -0
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from "node:child_process";
3
+ import http from "node:http";
3
4
  import {
4
5
  existsSync,
5
6
  mkdirSync,
6
7
  mkdtempSync,
8
+ readFileSync,
7
9
  rmSync,
8
10
  writeFileSync,
9
11
  } from "node:fs";
@@ -11,7 +13,7 @@ import { tmpdir } from "node:os";
11
13
  import { dirname, join, resolve } from "node:path";
12
14
  import { fileURLToPath } from "node:url";
13
15
  import { setTimeout as sleep } from "node:timers/promises";
14
- import { CHATGPT_CORE_TOOL_NAMES } from "../../dist/tools/toolCatalog.js";
16
+ import { CHATGPT_CORE_TOOL_NAMES } from "../../dist/tools/catalog/toolCatalog.js";
15
17
 
16
18
  const __dirname = dirname(fileURLToPath(import.meta.url));
17
19
  const root = resolve(__dirname, "..", "..");
@@ -96,6 +98,25 @@ function toolJson(result) {
96
98
  return JSON.parse(toolText(result));
97
99
  }
98
100
 
101
+ function requestWithHost(path, hostHeader) {
102
+ return new Promise((resolvePromise, rejectPromise) => {
103
+ const request = http.request({
104
+ hostname: host,
105
+ port,
106
+ path,
107
+ method: "GET",
108
+ headers: { Host: hostHeader },
109
+ }, (response) => {
110
+ let body = "";
111
+ response.setEncoding("utf-8");
112
+ response.on("data", (chunk) => { body += chunk; });
113
+ response.on("end", () => resolvePromise({ status: response.statusCode, body }));
114
+ });
115
+ request.on("error", rejectPromise);
116
+ request.end();
117
+ });
118
+ }
119
+
99
120
  console.log("\n=== PatchWarden HTTP MCP Smoke Tests ===\n");
100
121
 
101
122
  try {
@@ -126,7 +147,7 @@ try {
126
147
  );
127
148
 
128
149
  console.log(`Starting HTTP MCP server on ${mcpUrl}...`);
129
- serverProcess = spawn("node", [serverPath], {
150
+ serverProcess = spawn(process.execPath, [serverPath], {
130
151
  cwd: root,
131
152
  env: {
132
153
  ...process.env,
@@ -163,6 +184,13 @@ try {
163
184
  }
164
185
  });
165
186
 
187
+ await test("HTTP MCP rejects DNS-rebinding Host headers", async () => {
188
+ const response = await requestWithHost("/healthz", "patchwarden.attacker.invalid");
189
+ if (response.status !== 421 || !response.body.includes("Untrusted Host")) {
190
+ throw new Error(`Unexpected hostile Host response: ${JSON.stringify(response)}`);
191
+ }
192
+ });
193
+
166
194
  await test("wrong endpoint returns structured 404 guidance", async () => {
167
195
  const response = await fetch(`http://${host}:${port}/wrong-path`);
168
196
  const data = await response.json();
@@ -262,7 +290,7 @@ try {
262
290
 
263
291
  // Start server WITH owner token
264
292
  console.log("\n Starting server with owner token...");
265
- serverProcess = spawn("node", [serverPath], {
293
+ serverProcess = spawn(process.execPath, [serverPath], {
266
294
  cwd: root,
267
295
  env: {
268
296
  ...process.env,
@@ -278,7 +306,7 @@ try {
278
306
  // Wait for server to be ready
279
307
  for (let i = 0; i < 10; i++) {
280
308
  try {
281
- const r = await fetch(`${mcpUrl}/healthz`);
309
+ const r = await fetch(`http://${host}:${port}/healthz`);
282
310
  if (r.status === 200) break;
283
311
  } catch {}
284
312
  await sleep(500);
@@ -328,6 +356,56 @@ try {
328
356
  }
329
357
  });
330
358
 
359
+ const acceptanceTaskId = "task_http_acceptance";
360
+ const acceptanceTaskDir = join(workspaceRoot, ".patchwarden", "tasks", acceptanceTaskId);
361
+ mkdirSync(acceptanceTaskDir, { recursive: true });
362
+ writeFileSync(join(acceptanceTaskDir, "status.json"), JSON.stringify({
363
+ task_id: acceptanceTaskId,
364
+ status: "done_by_agent",
365
+ acceptance_status: "pending",
366
+ }), "utf-8");
367
+
368
+ await test("admin acceptance requires the owner token", async () => {
369
+ const response = await fetch(`http://${host}:${port}/admin/tasks/${acceptanceTaskId}/accept`, {
370
+ method: "POST",
371
+ headers: { "content-type": "application/json" },
372
+ body: JSON.stringify({ notes: "reviewed" }),
373
+ });
374
+ if (response.status !== 401) throw new Error(`Expected 401, got ${response.status}`);
375
+ });
376
+
377
+ await test("admin acceptance writes bounded atomic evidence", async () => {
378
+ const secret = `ghp_${"a".repeat(24)}`;
379
+ const response = await fetch(`http://${host}:${port}/admin/tasks/${acceptanceTaskId}/accept`, {
380
+ method: "POST",
381
+ headers: {
382
+ "content-type": "application/json",
383
+ Authorization: `Bearer ${OWNER_TOKEN}`,
384
+ },
385
+ body: JSON.stringify({ notes: `reviewed ${secret}` }),
386
+ });
387
+ if (response.status !== 200) throw new Error(`Expected 200, got ${response.status}: ${await response.text()}`);
388
+ const acceptance = JSON.parse(readFileSync(join(acceptanceTaskDir, "acceptance.json"), "utf-8"));
389
+ const status = JSON.parse(readFileSync(join(acceptanceTaskDir, "status.json"), "utf-8"));
390
+ if (acceptance.status !== "accepted" || status.acceptance_status !== "accepted") {
391
+ throw new Error("Acceptance state was not persisted consistently");
392
+ }
393
+ if (JSON.stringify(acceptance).includes(secret)) throw new Error("Acceptance notes persisted a token");
394
+ if (existsSync(join(acceptanceTaskDir, "status.json.lock"))) throw new Error("Status lock was not released");
395
+ });
396
+
397
+ await test("admin acceptance rejects oversized request bodies", async () => {
398
+ const response = await fetch(`http://${host}:${port}/admin/tasks/${acceptanceTaskId}/reject`, {
399
+ method: "POST",
400
+ headers: {
401
+ "content-type": "application/json",
402
+ Authorization: `Bearer ${OWNER_TOKEN}`,
403
+ },
404
+ body: JSON.stringify({ reason: "x".repeat(70 * 1024) }),
405
+ });
406
+ if (response.status !== 413) throw new Error(`Expected 413, got ${response.status}`);
407
+ });
408
+
331
409
  } catch (error) {
332
410
  fail("HTTP MCP smoke setup", error);
333
411
  } finally {
@@ -152,16 +152,16 @@ try {
152
152
  );
153
153
  process.env.PATCHWARDEN_CONFIG = configPath;
154
154
 
155
- const { savePlan } = await import("../../dist/tools/savePlan.js");
156
- const { createTask } = await import("../../dist/tools/createTask.js");
157
- const { getTaskStatus } = await import("../../dist/tools/getTaskStatus.js");
158
- const { cancelTask } = await import("../../dist/tools/cancelTask.js");
159
- const { killTask } = await import("../../dist/tools/killTask.js");
160
- const { getDiff } = await import("../../dist/tools/taskOutputs.js");
161
- const { listAgents } = await import("../../dist/tools/listAgents.js");
162
- const { getTaskSummary } = await import("../../dist/tools/getTaskSummary.js");
163
- const { auditTask } = await import("../../dist/tools/auditTask.js");
164
- const { waitForTask } = await import("../../dist/tools/waitForTask.js");
155
+ const { savePlan } = await import("../../dist/tools/goals/savePlan.js");
156
+ const { createTask } = await import("../../dist/tools/tasks/createTask.js");
157
+ const { getTaskStatus } = await import("../../dist/tools/tasks/getTaskStatus.js");
158
+ const { cancelTask } = await import("../../dist/tools/tasks/cancelTask.js");
159
+ const { killTask } = await import("../../dist/tools/tasks/killTask.js");
160
+ const { getDiff } = await import("../../dist/tools/tasks/taskOutputs.js");
161
+ const { listAgents } = await import("../../dist/tools/workspace/listAgents.js");
162
+ const { getTaskSummary } = await import("../../dist/tools/tasks/getTaskSummary.js");
163
+ const { auditTask } = await import("../../dist/tools/diagnostics/auditTask.js");
164
+ const { waitForTask } = await import("../../dist/tools/tasks/waitForTask.js");
165
165
  const { runTask } = await import("../../dist/runner/runTask.js");
166
166
  const { reloadConfig } = await import("../../dist/config.js");
167
167
 
@@ -175,7 +175,7 @@ try {
175
175
  await test("git diff captures tracked and untracked task changes", async () => {
176
176
  writeFileSync(join(repoPath, "preexisting-user-file.txt"), "preexisting and untouched\n", "utf-8");
177
177
  const plan = savePlan({ title: "Change capture", content: "Modify fixture files." });
178
- const task = createTask({
178
+ const task = await createTask({
179
179
  plan_id: plan.plan_id,
180
180
  agent: "writer",
181
181
  repo_path: "repo",
@@ -231,7 +231,7 @@ try {
231
231
  });
232
232
 
233
233
  await test("inspect_only template fails when the agent changes repository files", async () => {
234
- const task = createTask({
234
+ const task = await createTask({
235
235
  template: "inspect_only",
236
236
  goal: "Inspect the fixture without modifying files",
237
237
  agent: "writer",
@@ -249,7 +249,7 @@ try {
249
249
 
250
250
  await test("legacy test_command becomes one independent verification command", async () => {
251
251
  const plan = savePlan({ title: "Legacy verification", content: "No changes." });
252
- const task = createTask({
252
+ const task = await createTask({
253
253
  plan_id: plan.plan_id,
254
254
  agent: "noop",
255
255
  repo_path: "repo",
@@ -267,7 +267,7 @@ try {
267
267
 
268
268
  await test("no-change task returns an explicit empty diff result", async () => {
269
269
  const plan = savePlan({ title: "No diff", content: "Do not change files." });
270
- const task = createTask({ plan_id: plan.plan_id, agent: "noop", repo_path: "repo" });
270
+ const task = await createTask({ plan_id: plan.plan_id, agent: "noop", repo_path: "repo" });
271
271
  const result = await runTask(task.task_id);
272
272
  if (!isDoneStatus(result.status)) throw new Error(`No-op task failed: ${JSON.stringify(result)}`);
273
273
  const diff = getDiff(task.task_id);
@@ -288,7 +288,7 @@ try {
288
288
 
289
289
  await test("large diff response is truncated while diff.patch remains complete", async () => {
290
290
  const plan = savePlan({ title: "Large diff", content: "Create a large text file." });
291
- const task = createTask({ plan_id: plan.plan_id, agent: "largewriter", repo_path: "repo" });
291
+ const task = await createTask({ plan_id: plan.plan_id, agent: "largewriter", repo_path: "repo" });
292
292
  const result = await runTask(task.task_id);
293
293
  if (!isDoneStatus(result.status)) throw new Error(`Large task failed: ${JSON.stringify(result)}`);
294
294
  const diff = getDiff(task.task_id);
@@ -300,7 +300,7 @@ try {
300
300
 
301
301
  await test("non-Git repositories return hash-only evidence with a reason", async () => {
302
302
  const plan = savePlan({ title: "Non-Git evidence", content: "Modify files in a non-Git repository." });
303
- const task = createTask({ plan_id: plan.plan_id, agent: "writer", repo_path: "plain-repo" });
303
+ const task = await createTask({ plan_id: plan.plan_id, agent: "writer", repo_path: "plain-repo" });
304
304
  const result = await runTask(task.task_id);
305
305
  if (!isDoneStatus(result.status)) throw new Error(`Non-Git task failed: ${JSON.stringify(result)}`);
306
306
  const diff = getDiff(task.task_id);
@@ -315,7 +315,7 @@ try {
315
315
 
316
316
  await test("binary Git changes remain reviewable as a textual Git binary patch", async () => {
317
317
  const plan = savePlan({ title: "Binary evidence", content: "Create a binary fixture." });
318
- const task = createTask({ plan_id: plan.plan_id, agent: "binarywriter", repo_path: "repo" });
318
+ const task = await createTask({ plan_id: plan.plan_id, agent: "binarywriter", repo_path: "repo" });
319
319
  const result = await runTask(task.task_id);
320
320
  if (!isDoneStatus(result.status)) throw new Error(`Binary task failed: ${JSON.stringify(result)}`);
321
321
  const diff = getDiff(task.task_id);
@@ -326,7 +326,7 @@ try {
326
326
 
327
327
  await test("artifact hygiene separates source, ignored output, runtime data, and suspicious changes", async () => {
328
328
  const plan = savePlan({ title: "Artifact hygiene", content: "Generate representative task outputs." });
329
- const task = createTask({ plan_id: plan.plan_id, agent: "artifactwriter", repo_path: "repo" });
329
+ const task = await createTask({ plan_id: plan.plan_id, agent: "artifactwriter", repo_path: "repo" });
330
330
  const result = await runTask(task.task_id);
331
331
  if (!isDoneStatus(result.status)) throw new Error(`Artifact task failed: ${JSON.stringify(result)}`);
332
332
  const standard = getTaskSummary(task.task_id);
@@ -355,7 +355,7 @@ try {
355
355
 
356
356
  await test("deleted tracked files are identified with file stats", async () => {
357
357
  const plan = savePlan({ title: "Delete fixture", content: "Delete the designated fixture file." });
358
- const task = createTask({ plan_id: plan.plan_id, agent: "deleter", repo_path: "repo" });
358
+ const task = await createTask({ plan_id: plan.plan_id, agent: "deleter", repo_path: "repo" });
359
359
  const result = await runTask(task.task_id);
360
360
  if (!isDoneStatus(result.status)) throw new Error(`Delete task failed: ${JSON.stringify(result)}`);
361
361
  const diff = getDiff(task.task_id);
@@ -366,7 +366,7 @@ try {
366
366
 
367
367
  await test("wait_for_task stays in the tool loop and returns terminal acceptance", async () => {
368
368
  const plan = savePlan({ title: "Wait loop", content: "Finish normally." });
369
- const task = createTask({ plan_id: plan.plan_id, agent: "writer", repo_path: "repo" });
369
+ const task = await createTask({ plan_id: plan.plan_id, agent: "writer", repo_path: "repo" });
370
370
  const running = runTask(task.task_id);
371
371
  const waited = await waitForTask(task.task_id, 5);
372
372
  await raceWithTimeout(running, 15000, "wait_for_task loop did not terminate within 15s");
@@ -380,7 +380,7 @@ try {
380
380
 
381
381
  await test("wait_for_task explicitly requires another call when the task is not terminal", async () => {
382
382
  const plan = savePlan({ title: "Pending wait", content: "Remain queued for this check." });
383
- const task = createTask({ plan_id: plan.plan_id, agent: "slow", repo_path: "repo" });
383
+ const task = await createTask({ plan_id: plan.plan_id, agent: "slow", repo_path: "repo" });
384
384
  const waited = await waitForTask(task.task_id, 1);
385
385
  if (waited.terminal || !waited.timed_out || !waited.continuation_required) {
386
386
  throw new Error(`Expected continuation response: ${JSON.stringify(waited)}`);
@@ -390,11 +390,17 @@ try {
390
390
 
391
391
  await test("running task summary includes heartbeat, phase, command, and elapsed time", async () => {
392
392
  const plan = savePlan({ title: "Running summary", content: "Wait." });
393
- const task = createTask({ plan_id: plan.plan_id, agent: "slow", repo_path: "repo", timeout_seconds: 30 });
393
+ const task = await createTask({ plan_id: plan.plan_id, agent: "slow", repo_path: "repo", timeout_seconds: 30 });
394
394
  const running = runTask(task.task_id);
395
395
  await waitForRunning(getTaskStatus, task.task_id);
396
- await sleep(50);
397
- const summary = getTaskSummary(task.task_id);
396
+ // Wait for the task to transition from "preparing" to actually running a command.
397
+ // Async snapshot may delay the preparing phase, so poll until current_command appears.
398
+ let summary;
399
+ for (let attempt = 0; attempt < 100; attempt++) {
400
+ await sleep(50);
401
+ summary = getTaskSummary(task.task_id);
402
+ if (summary.current_command) break;
403
+ }
398
404
  if (summary.terminal || !summary.last_heartbeat_at || !summary.phase || !summary.current_command || summary.elapsed_ms < 0) {
399
405
  throw new Error(`Running summary incomplete: ${JSON.stringify(summary)}`);
400
406
  }
@@ -404,7 +410,7 @@ try {
404
410
 
405
411
  await test("verification failure produces failed_verification and structured evidence", async () => {
406
412
  const plan = savePlan({ title: "Verify failure", content: "Finish normally." });
407
- const task = createTask({
413
+ const task = await createTask({
408
414
  plan_id: plan.plan_id,
409
415
  agent: "writer",
410
416
  repo_path: "repo",
@@ -434,7 +440,7 @@ try {
434
440
 
435
441
  await test("out-of-scope workspace changes fail the task and generate a rollback plan", async () => {
436
442
  const plan = savePlan({ title: "Scope violation", content: "Do not leave the repository." });
437
- const task = createTask({
443
+ const task = await createTask({
438
444
  plan_id: plan.plan_id,
439
445
  agent: "scopebreaker",
440
446
  repo_path: "repo",
@@ -505,7 +511,7 @@ try {
505
511
  reloadConfig(cfg2);
506
512
 
507
513
  const plan = savePlan({ title: "Noop with external dirty", content: "Do nothing." });
508
- const task = createTask({ plan_id: plan.plan_id, agent: "noop", repo_path: "repo", verify_commands: ["node --check main.js"] });
514
+ const task = await createTask({ plan_id: plan.plan_id, agent: "noop", repo_path: "repo", verify_commands: ["node --check main.js"] });
509
515
  const result = await runTask(task.task_id);
510
516
 
511
517
  if (!isDoneStatus(result.status)) {
@@ -590,7 +596,7 @@ try {
590
596
  reloadConfig(cfg2);
591
597
 
592
598
  const plan = savePlan({ title: "External dirty modifier", content: "Modify external tracked file." });
593
- const task = createTask({ plan_id: plan.plan_id, agent: "extmod", repo_path: "repo", verify_commands: ["node --check main.js"] });
599
+ const task = await createTask({ plan_id: plan.plan_id, agent: "extmod", repo_path: "repo", verify_commands: ["node --check main.js"] });
594
600
  const result = await runTask(task.task_id);
595
601
 
596
602
  if (result.status !== "failed_scope_violation") {
@@ -675,7 +681,7 @@ try {
675
681
  reloadConfig(cfg2);
676
682
 
677
683
  const plan = savePlan({ title: "Clean external modifier", content: "Modify clean external tracked file." });
678
- const task = createTask({ plan_id: plan.plan_id, agent: "extmod", repo_path: "repo", verify_commands: ["node --check main.js"] });
684
+ const task = await createTask({ plan_id: plan.plan_id, agent: "extmod", repo_path: "repo", verify_commands: ["node --check main.js"] });
679
685
  const result = await runTask(task.task_id);
680
686
 
681
687
  if (result.status !== "failed_scope_violation") {
@@ -752,7 +758,7 @@ try {
752
758
  reloadConfig(cfg2);
753
759
 
754
760
  const plan = savePlan({ title: "External rename", content: "Rename external tracked file." });
755
- const task = createTask({ plan_id: plan.plan_id, agent: "extrenamer", repo_path: "repo", verify_commands: ["node --check main.js"] });
761
+ const task = await createTask({ plan_id: plan.plan_id, agent: "extrenamer", repo_path: "repo", verify_commands: ["node --check main.js"] });
756
762
  const result = await runTask(task.task_id);
757
763
 
758
764
  if (result.status !== "failed_scope_violation") {
@@ -794,7 +800,7 @@ try {
794
800
 
795
801
  await test("timeout terminates a long-running agent", async () => {
796
802
  const plan = savePlan({ title: "Timeout", content: "Wait." });
797
- const task = createTask({ plan_id: plan.plan_id, agent: "slow", repo_path: "repo", timeout_seconds: 1 });
803
+ const task = await createTask({ plan_id: plan.plan_id, agent: "slow", repo_path: "repo", timeout_seconds: 1 });
798
804
  const started = Date.now();
799
805
  const result = await runTask(task.task_id);
800
806
  if (result.status !== "failed" || !result.error?.includes("timed out")) {
@@ -810,7 +816,7 @@ try {
810
816
 
811
817
  await test("cancel_task safely stops a running agent", async () => {
812
818
  const plan = savePlan({ title: "Cancel", content: "Wait." });
813
- const task = createTask({ plan_id: plan.plan_id, agent: "slow", repo_path: "repo", timeout_seconds: 30 });
819
+ const task = await createTask({ plan_id: plan.plan_id, agent: "slow", repo_path: "repo", timeout_seconds: 30 });
814
820
  const running = runTask(task.task_id);
815
821
  await waitForRunning(getTaskStatus, task.task_id);
816
822
  const request = cancelTask(task.task_id);
@@ -823,7 +829,7 @@ try {
823
829
 
824
830
  await test("kill_task immediately stops a running agent", async () => {
825
831
  const plan = savePlan({ title: "Kill", content: "Wait." });
826
- const task = createTask({ plan_id: plan.plan_id, agent: "slow", repo_path: "repo", timeout_seconds: 30 });
832
+ const task = await createTask({ plan_id: plan.plan_id, agent: "slow", repo_path: "repo", timeout_seconds: 30 });
827
833
  const running = runTask(task.task_id);
828
834
  await waitForRunning(getTaskStatus, task.task_id);
829
835
  const request = killTask(task.task_id);
@@ -4,7 +4,7 @@ import { resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
6
6
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
7
- import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES, CHATGPT_SEARCH_TOOL_NAMES } from "../../dist/tools/toolCatalog.js";
7
+ import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES, CHATGPT_SEARCH_TOOL_NAMES } from "../../dist/tools/catalog/toolCatalog.js";
8
8
 
9
9
  const root = resolve(fileURLToPath(new URL("../..", import.meta.url)));
10
10
  const jsonOnly = process.argv.includes("--json");
@@ -54,6 +54,8 @@ try {
54
54
  }), "utf-8");
55
55
  writeFileSync(join(workspaceRoot, "hello.txt"), "hello from mcp smoke\n", "utf-8");
56
56
  writeFileSync(join(workspaceRoot, ".env"), "SECRET=blocked\n", "utf-8");
57
+ writeFileSync(join(workspaceRoot, "npm.cmd"), "@echo repo npm hijack executed\r\n@exit /b 99\r\n", "utf-8");
58
+ writeFileSync(join(workspaceRoot, "git.cmd"), "@echo repo git hijack executed\r\n@exit /b 98\r\n", "utf-8");
57
59
  writeFileSync(
58
60
  join(workspaceRoot, "package.json"),
59
61
  JSON.stringify({
@@ -117,6 +119,7 @@ try {
117
119
  "discard_worktree",
118
120
  "discover_tools",
119
121
  "explain_tool",
122
+ "export_goal_report",
120
123
  "export_handoff",
121
124
  "export_task_evidence_pack",
122
125
  "finalize_direct_session",
@@ -133,6 +136,7 @@ try {
133
136
  "get_task_summary",
134
137
  "get_test_log",
135
138
  "health_check",
139
+ "import_speckit_tasks",
136
140
  "invoke_discovered_tool",
137
141
  "kill_task",
138
142
  "list_agents",
@@ -177,7 +181,7 @@ try {
177
181
  if (!tools._meta || typeof tools._meta.tool_manifest_sha256 !== "string" || tools._meta.tool_manifest_sha256.length !== 64) {
178
182
  throw new Error(`tools/list _meta missing manifest hash: ${JSON.stringify(tools._meta || null)}`);
179
183
  }
180
- if (tools._meta.tool_profile !== "full" || tools._meta.tool_count !== 64) {
184
+ if (tools._meta.tool_profile !== "full" || tools._meta.tool_count !== 66) {
181
185
  throw new Error(`tools/list _meta profile/count mismatch: ${JSON.stringify(tools._meta)}`);
182
186
  }
183
187
  if (typeof tools._meta.schema_epoch !== "string" || typeof tools._meta.server_version !== "string") {
@@ -318,7 +322,11 @@ try {
318
322
  );
319
323
  ok("sensitive file and path escape checks reject access");
320
324
 
321
- const runner = spawnSync("node", ["dist/runner/cli.js", task.task_id], {
325
+ // The runner executes with the temporary workspace as cwd; use an absolute
326
+ // package path so the smoke test does not depend on that workspace having a
327
+ // copied `dist/` tree.
328
+ const runnerPath = resolve(root, "dist", "runner", "cli.js");
329
+ const runner = spawnSync("node", [runnerPath, task.task_id], {
322
330
  cwd: root,
323
331
  env: { ...process.env, PATCHWARDEN_CONFIG: configPath },
324
332
  encoding: "utf-8",
@@ -435,7 +443,9 @@ try {
435
443
  writeFileSync(join(directRepo, "package.json"), JSON.stringify({
436
444
  name: "direct-fixture",
437
445
  private: true,
438
- scripts: { test: 'node -e "console.log(\'ok\')"' },
446
+ // Exercise the npm-script execution path without relying on a temporary
447
+ // script file or platform-specific quoting.
448
+ scripts: { test: "node --version" },
439
449
  }, null, 2), "utf-8");
440
450
 
441
451
  writeFileSync(
@@ -515,7 +525,23 @@ try {
515
525
  })).content?.[0]?.text || "{}"
516
526
  );
517
527
  if (bundleResult.status !== "passed" || bundleResult.command_count !== 1 || bundleResult.passed_commands !== 1) {
518
- throw new Error(`run_direct_verification_bundle failed: ${JSON.stringify(bundleResult)}`);
528
+ // This repository and command are generated entirely by this smoke test,
529
+ // so a bounded log excerpt is safe and makes platform-only CI failures
530
+ // actionable without weakening the production tool's safe summary.
531
+ const sessionDir = join(workspaceRoot, ".patchwarden", "direct-sessions", sessionResult.session_id);
532
+ const verificationPath = join(sessionDir, "verification.json");
533
+ const logPath = join(sessionDir, "verification.log");
534
+ const verification = existsSync(verificationPath)
535
+ ? readFileSync(verificationPath, "utf-8").slice(-2000)
536
+ : "missing verification.json";
537
+ const fixtureLog = existsSync(logPath)
538
+ ? readFileSync(logPath, "utf-8").slice(-2000)
539
+ : "missing verification.log";
540
+ throw new Error(
541
+ `run_direct_verification_bundle failed: ${JSON.stringify(bundleResult)}; ` +
542
+ `synthetic_fixture_verification=${JSON.stringify(verification)}; ` +
543
+ `synthetic_fixture_log=${JSON.stringify(fixtureLog)}`
544
+ );
519
545
  }
520
546
  const bundleText = JSON.stringify(bundleResult).toLowerCase();
521
547
  if (bundleText.includes("stdout_tail") || bundleText.includes("stderr_tail") || bundleText.includes("verification.log")) {
@@ -1,15 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from "node:child_process";
3
3
  import { readFileSync } from "node:fs";
4
+ import { resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import {
7
+ buildChildEnvironment,
8
+ resolvePackageManagerInvocation,
9
+ } from "../../dist/runner/processSecurity.js";
4
10
 
5
- const isWindows = process.platform === "win32";
6
- const command = isWindows ? (process.env.ComSpec || "cmd.exe") : "npm";
7
- const args = isWindows
8
- ? ["/d", "/s", "/c", "npm.cmd pack --dry-run --json"]
9
- : ["pack", "--dry-run", "--json"];
10
- const result = spawnSync(command, args, {
11
+ const root = resolve(fileURLToPath(new URL("../..", import.meta.url)));
12
+ const env = buildChildEnvironment({ cwd: root });
13
+ const invocation = resolvePackageManagerInvocation(process.platform === "win32" ? "npm.cmd" : "npm", root, {
14
+ pathValue: env.PATH,
15
+ });
16
+ const result = spawnSync(invocation.command, [...invocation.argsPrefix, "pack", "--dry-run", "--json"], {
17
+ cwd: root,
11
18
  encoding: "utf8",
19
+ env,
12
20
  shell: false,
21
+ windowsHide: true,
13
22
  });
14
23
 
15
24
  if (result.status !== 0) {
@@ -35,6 +44,8 @@ const forbidden = [
35
44
  /\.dpapi$/i,
36
45
  /^docs\/optimization-proposal\.md$/i,
37
46
  /(^|\/)kill-patchwarden\.(cmd|ps1)$/i,
47
+ /^(?:dist|src)\/test\//i,
48
+ /^docs\/archive\//i,
38
49
  ];
39
50
  const leaked = files.filter((file) => forbidden.some((pattern) => pattern.test(file)));
40
51
  if (leaked.length > 0) {
@@ -44,8 +55,10 @@ if (leaked.length > 0) {
44
55
  }
45
56
 
46
57
  const required = [
58
+ "dist/index.js",
59
+ "dist/index.d.ts",
47
60
  "PatchWarden.cmd",
48
- "PatchWarden-Desktop.cmd",
61
+ "scripts/launchers/PatchWarden-Desktop.cmd",
49
62
  "scripts/control/manage-patchwarden.ps1",
50
63
  "scripts/launchers/Start-PatchWarden-Tunnel.cmd",
51
64
  "scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd",
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, readdirSync } from "node:fs";
2
+ import { existsSync } from "node:fs";
3
3
  import { spawnSync } from "node:child_process";
4
4
  import { resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
+ import { collectMatchingFiles } from "../lib/file-discovery.js";
6
7
 
7
8
  const scriptDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
8
9
  const root = resolve(scriptDir, "..", "..");
@@ -14,10 +15,7 @@ if (!existsSync(unitDir)) {
14
15
  process.exit(1);
15
16
  }
16
17
 
17
- const testFiles = readdirSync(unitDir)
18
- .filter((name) => name.endsWith(".test.js"))
19
- .sort()
20
- .map((name) => resolve(unitDir, name));
18
+ const testFiles = collectMatchingFiles(unitDir, (name) => name.endsWith(".test.js"));
21
19
 
22
20
  if (testFiles.length === 0) {
23
21
  console.error(`[unit-tests] No compiled unit tests found in ${unitDir}`);
@@ -60,6 +60,8 @@ async function runScenario(label, mode, maxRestarts, lifetimeMs, assertState) {
60
60
  async function runExternalScenario() {
61
61
  const fixture = createFixture("external");
62
62
  const env = fixtureEnv(fixture, "healthy", 7_000);
63
+ delete env.CONTROL_PLANE_API_KEY;
64
+ delete env.PATCHWARDEN_OWNER_TOKEN;
63
65
  const external = spawn(process.execPath, [fixture.watcherPath], {
64
66
  cwd: fixture.project,
65
67
  env: {
@@ -168,6 +170,9 @@ function fixtureEnv(fixture, mode, lifetimeMs) {
168
170
  APPDATA: fixture.appData,
169
171
  LOCALAPPDATA: fixture.localAppData,
170
172
  CONTROL_PLANE_API_KEY: "test-key",
173
+ PATCHWARDEN_OWNER_TOKEN: "test-owner-token",
174
+ PATCHWARDEN_AGENT_ENV_ALLOWLIST: "PATCHWARDEN_TEST_PROVIDER_KEY",
175
+ PATCHWARDEN_TEST_PROVIDER_KEY: "provider-key-canary",
171
176
  WATCHER_FIXTURE_MODE: mode,
172
177
  TUNNEL_FIXTURE_LIFETIME_MS: String(lifetimeMs),
173
178
  };
@@ -177,6 +182,9 @@ function watcherFixtureSource(attemptPath) {
177
182
  return `
178
183
  const fs=require('fs');const path=require('path');
179
184
  if(!process.env.XDG_CONFIG_HOME){console.error('watcher did not receive XDG_CONFIG_HOME');process.exit(12)}
185
+ if(process.env.CONTROL_PLANE_API_KEY){console.error('watcher received tunnel owner credential');process.exit(13)}
186
+ if(process.env.PATCHWARDEN_OWNER_TOKEN){console.error('watcher received HTTP owner credential');process.exit(14)}
187
+ if(process.env.PATCHWARDEN_TEST_PROVIDER_KEY!=='provider-key-canary'){console.error('watcher did not receive allow-listed provider credential');process.exit(15)}
180
188
  let attempt=0;try{attempt=Number(fs.readFileSync(${JSON.stringify(attemptPath)},'utf8'))||0}catch{}
181
189
  attempt++;fs.writeFileSync(${JSON.stringify(attemptPath)},String(attempt));
182
190
  const cfg=JSON.parse(fs.readFileSync(process.env.PATCHWARDEN_CONFIG,'utf8'));
@@ -192,6 +200,7 @@ else setInterval(write,250);
192
200
  function tunnelFixtureSource() {
193
201
  return `
194
202
  const fs=require('fs');const path=require('path');const args=process.argv.slice(2);const command=args[0]||'';
203
+ if(process.env.PATCHWARDEN_TEST_PROVIDER_KEY){console.error('allow-listed provider credential leaked into tunnel-client');process.exit(22)}
195
204
  const flag=(name)=>{const i=args.indexOf(name);return i>=0?args[i+1]:''};
196
205
  if(command==='init'){const profile=flag('--profile')||'patchwarden';const mcpCommand=flag('--mcp-command')||'';const yamlDir=path.join(process.env.APPDATA,'tunnel-client');const yamlPath=path.join(yamlDir,profile+'.yaml');fs.mkdirSync(yamlDir,{recursive:true});fs.writeFileSync(yamlPath,'mcp:\\n commands:\\n - channel: main\\n command: \"'+mcpCommand.replace(/\\\\/g,'/')+'\"\\n\\nhealth:\\n listen_addr: \"127.0.0.1:8080\"\\n');console.log('{}');process.exit(0)}
197
206
  if(command==='doctor'){console.log('{}');process.exit(0)}
@@ -6,7 +6,7 @@ $RuntimeDirectory = Join-Path $env:LOCALAPPDATA "patchwarden\runtime"
6
6
  $HealthUrlFile = Join-Path $RuntimeDirectory "tunnel-health-url.txt"
7
7
 
8
8
  $env:PATCHWARDEN_CONFIG = $ConfigPath
9
- $healthJson = node --input-type=module -e "Promise.all([import('./dist/tools/healthCheck.js'),import('./dist/tools/registry.js')]).then(([h,r])=>console.log(JSON.stringify(h.healthCheck(r.getToolCatalogSnapshot()))))" 2>$null
9
+ $healthJson = node --input-type=module -e "Promise.all([import('./dist/tools/diagnostics/healthCheck.js'),import('./dist/tools/registry.js')]).then(([h,r])=>console.log(JSON.stringify(h.healthCheck(r.getToolCatalogSnapshot()))))" 2>$null
10
10
  if ($LASTEXITCODE -ne 0 -or -not $healthJson) {
11
11
  throw "Could not load PatchWarden health information. Run npm.cmd run build first."
12
12
  }