patchwarden 1.5.1 → 1.6.2

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 (591) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +158 -26
  3. package/README.md +124 -24
  4. package/dist/assessments/agentAssessor.js +21 -12
  5. package/dist/assessments/assessmentDiagnostics.d.ts +29 -0
  6. package/dist/assessments/assessmentDiagnostics.js +132 -0
  7. package/dist/assessments/assessmentStore.d.ts +39 -0
  8. package/dist/assessments/assessmentStore.js +207 -45
  9. package/dist/assessments/securitySnapshot.d.ts +50 -0
  10. package/dist/assessments/securitySnapshot.js +158 -0
  11. package/dist/config.d.ts +15 -0
  12. package/dist/config.js +66 -13
  13. package/dist/control/fileManager.d.ts +2 -0
  14. package/dist/control/fileManager.js +39 -0
  15. package/dist/control/middleware/auth.d.ts +6 -0
  16. package/dist/control/middleware/auth.js +14 -5
  17. package/dist/control/middleware/static.js +16 -3
  18. package/dist/control/routeTable.d.ts +24 -0
  19. package/dist/control/routeTable.js +263 -0
  20. package/dist/control/routes/audit.js +17 -7
  21. package/dist/control/routes/evidence.js +1 -1
  22. package/dist/control/routes/lineage.js +1 -1
  23. package/dist/control/routes/process.d.ts +7 -0
  24. package/dist/control/routes/process.js +329 -42
  25. package/dist/control/routes/sessions.d.ts +9 -1
  26. package/dist/control/routes/sessions.js +84 -21
  27. package/dist/control/routes/status.d.ts +60 -1
  28. package/dist/control/routes/status.js +94 -15
  29. package/dist/control/routes/taskActions.js +24 -60
  30. package/dist/control/routes/tasks.d.ts +4 -0
  31. package/dist/control/routes/tasks.js +61 -62
  32. package/dist/control/routes/workspace.js +50 -23
  33. package/dist/control/runtime.d.ts +3 -2
  34. package/dist/control/runtime.js +65 -40
  35. package/dist/control/server.js +37 -365
  36. package/dist/control/shared.d.ts +25 -1
  37. package/dist/control/shared.js +64 -11
  38. package/dist/diagnostics/allowedTestCommandSafety.d.ts +1 -0
  39. package/dist/diagnostics/allowedTestCommandSafety.js +11 -0
  40. package/dist/direct/directAudit.js +234 -225
  41. package/dist/direct/directGuards.d.ts +2 -1
  42. package/dist/direct/directGuards.js +42 -29
  43. package/dist/direct/directPatch.d.ts +6 -1
  44. package/dist/direct/directPatch.js +55 -8
  45. package/dist/direct/directSessionStore.d.ts +16 -6
  46. package/dist/direct/directSessionStore.js +128 -35
  47. package/dist/direct/directVerification.js +8 -16
  48. package/dist/doctor.js +65 -57
  49. package/dist/goal/acceptanceEngine.d.ts +1 -1
  50. package/dist/goal/goalProgress.js +75 -69
  51. package/dist/goal/goalReport.js +5 -12
  52. package/dist/goal/goalStatus.d.ts +2 -0
  53. package/dist/goal/goalStatus.js +10 -3
  54. package/dist/goal/goalStore.d.ts +9 -2
  55. package/dist/goal/goalStore.js +152 -45
  56. package/dist/goal/handoffExport.js +6 -6
  57. package/dist/goal/specKitImport.js +97 -96
  58. package/dist/goal/subgoalSync.js +11 -9
  59. package/dist/goal/worktreeManager.d.ts +2 -0
  60. package/dist/goal/worktreeManager.js +209 -136
  61. package/dist/httpServer.js +111 -32
  62. package/dist/logging.js +5 -3
  63. package/dist/policy/projectPolicy.js +5 -2
  64. package/dist/release/releaseGate.d.ts +1 -1
  65. package/dist/release/releaseGate.js +78 -25
  66. package/dist/runner/agentInvocation.d.ts +14 -0
  67. package/dist/runner/agentInvocation.js +96 -4
  68. package/dist/runner/changeCapture.d.ts +13 -5
  69. package/dist/runner/changeCapture.js +243 -61
  70. package/dist/runner/postTaskCleanup.js +48 -19
  71. package/dist/runner/processSecurity.d.ts +56 -0
  72. package/dist/runner/processSecurity.js +373 -0
  73. package/dist/runner/runTask.d.ts +2 -1
  74. package/dist/runner/runTask.js +162 -93
  75. package/dist/runner/simpleProcess.d.ts +3 -0
  76. package/dist/runner/simpleProcess.js +50 -36
  77. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  78. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  79. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +3 -1
  80. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  81. package/dist/runner/taskStatusStore.d.ts +11 -0
  82. package/dist/runner/taskStatusStore.js +35 -0
  83. package/dist/runner/watch.d.ts +14 -1
  84. package/dist/runner/watch.js +320 -49
  85. package/dist/security/contentRedaction.js +2 -2
  86. package/dist/security/discoveryTokenStore.d.ts +3 -2
  87. package/dist/security/discoveryTokenStore.js +17 -2
  88. package/dist/security/loopbackHost.d.ts +2 -0
  89. package/dist/security/loopbackHost.js +16 -0
  90. package/dist/security/pathGuard.js +32 -17
  91. package/dist/security/planGuard.js +2 -1
  92. package/dist/security/runtimeGuard.js +9 -6
  93. package/dist/security/secretComparison.d.ts +3 -0
  94. package/dist/security/secretComparison.js +11 -0
  95. package/dist/security/sensitiveGuard.d.ts +5 -0
  96. package/dist/security/sensitiveGuard.js +27 -8
  97. package/dist/security/toolInvocationGuard.d.ts +2 -2
  98. package/dist/security/toolInvocationGuard.js +1 -1
  99. package/dist/security/workspaceRootGuard.d.ts +8 -0
  100. package/dist/security/workspaceRootGuard.js +47 -0
  101. package/dist/smoke-test.js +27 -26
  102. package/dist/tools/catalog/index.d.ts +4 -0
  103. package/dist/tools/catalog/index.js +4 -0
  104. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  105. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  106. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +6 -15
  107. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  108. package/dist/tools/definitions/toolDefs.js +1177 -0
  109. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  110. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  111. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +2 -1
  112. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +134 -36
  113. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +18 -6
  114. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +36 -8
  115. package/dist/tools/diagnostics/index.d.ts +6 -0
  116. package/dist/tools/diagnostics/index.js +6 -0
  117. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  118. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  119. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +12 -8
  120. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +23 -13
  121. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  122. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  123. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +6 -6
  124. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +1 -1
  125. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  126. package/dist/tools/direct/index.d.ts +3 -0
  127. package/dist/tools/direct/index.js +3 -0
  128. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  129. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  130. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  131. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  132. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  133. package/dist/tools/discovery/index.d.ts +3 -0
  134. package/dist/tools/discovery/index.js +3 -0
  135. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  136. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  137. package/dist/tools/dispatch/coreDispatch.js +32 -31
  138. package/dist/tools/dispatch/diagnosticDispatch.d.ts +3 -5
  139. package/dist/tools/dispatch/diagnosticDispatch.js +70 -68
  140. package/dist/tools/dispatch/directDispatch.js +11 -10
  141. package/dist/tools/dispatch/goalDispatch.js +5 -4
  142. package/dist/tools/dispatch/releaseDispatch.js +3 -2
  143. package/dist/tools/dispatch/validation.d.ts +7 -0
  144. package/dist/tools/dispatch/validation.js +71 -0
  145. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  146. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +1 -1
  147. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  148. package/dist/tools/goals/index.d.ts +3 -0
  149. package/dist/tools/goals/index.js +3 -0
  150. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  151. package/dist/tools/registry.d.ts +7 -11
  152. package/dist/tools/registry.js +17 -1178
  153. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  154. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  155. package/dist/tools/release/index.d.ts +2 -0
  156. package/dist/tools/release/index.js +2 -0
  157. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  158. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  159. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  160. package/dist/tools/tasks/cancelTask.js +86 -0
  161. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
  162. package/dist/tools/{createTask.js → tasks/createTask.js} +110 -34
  163. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  164. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +12 -16
  165. package/dist/tools/{evidencePack.js → tasks/evidencePack.js} +34 -22
  166. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  167. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  168. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +11 -6
  169. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  170. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +35 -36
  171. package/dist/tools/tasks/index.d.ts +18 -0
  172. package/dist/tools/tasks/index.js +18 -0
  173. package/dist/tools/tasks/killTask.d.ts +1 -0
  174. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  175. package/dist/tools/{listTasks.js → tasks/listTasks.js} +5 -4
  176. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +3 -2
  177. package/dist/tools/tasks/reconcileTasks.js +496 -0
  178. package/dist/tools/tasks/retryTask.js +60 -0
  179. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  180. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +63 -41
  181. package/dist/tools/tasks/runVerification.js +38 -0
  182. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  183. package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +2 -2
  184. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  185. package/dist/tools/tasks/taskStates.d.ts +6 -0
  186. package/dist/tools/tasks/taskStates.js +52 -0
  187. package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +3 -11
  188. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  189. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  190. package/dist/tools/workspace/applyPatch.js +53 -0
  191. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  192. package/dist/tools/workspace/index.d.ts +10 -0
  193. package/dist/tools/workspace/index.js +10 -0
  194. package/dist/tools/workspace/listAgents.d.ts +23 -0
  195. package/dist/tools/workspace/listAgents.js +56 -0
  196. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  197. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  198. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  199. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  200. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  201. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  202. package/dist/tools/workspace/syncFile.js +134 -0
  203. package/dist/utils/atomicFile.d.ts +7 -0
  204. package/dist/utils/atomicFile.js +67 -0
  205. package/dist/utils/boundedFile.d.ts +8 -0
  206. package/dist/utils/boundedFile.js +93 -0
  207. package/dist/utils/lockedJsonFile.d.ts +15 -0
  208. package/dist/utils/lockedJsonFile.js +224 -0
  209. package/dist/utils/platform.d.ts +11 -0
  210. package/dist/utils/platform.js +26 -0
  211. package/dist/utils/stableJson.d.ts +2 -0
  212. package/dist/utils/stableJson.js +12 -0
  213. package/dist/version.d.ts +3 -2
  214. package/dist/version.js +3 -2
  215. package/dist/watcherStatus.d.ts +2 -2
  216. package/dist/watcherStatus.js +18 -0
  217. package/docs/CODE_WIKI.md +808 -143
  218. package/docs/architecture.md +15 -55
  219. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  220. package/docs/control-center/README.md +4 -4
  221. package/docs/dashboard-overview.md +4 -4
  222. package/docs/desktop-app.md +175 -0
  223. package/docs/evidence-pack-schema.md +3 -3
  224. package/docs/open-source-application.md +16 -11
  225. package/docs/release-checklist.md +1 -1
  226. package/docs/release-evidence.md +35 -39
  227. package/docs/task-safe-review-workflow.md +5 -5
  228. package/docs/threat-model.md +19 -1
  229. package/examples/config.example.json +4 -2
  230. package/package.json +27 -10
  231. package/scripts/README.md +15 -6
  232. package/scripts/build.js +11 -0
  233. package/scripts/checks/brand-check.js +3 -0
  234. package/scripts/checks/build-output-check.js +37 -0
  235. package/scripts/checks/control-center-smoke.js +102 -10
  236. package/scripts/checks/control-smoke.js +19 -8
  237. package/scripts/checks/http-mcp-smoke.js +82 -4
  238. package/scripts/checks/lifecycle-smoke.js +15 -12
  239. package/scripts/checks/mcp-manifest-check.js +1 -1
  240. package/scripts/checks/mcp-smoke.js +58 -4
  241. package/scripts/checks/package-manifest-check.js +20 -7
  242. package/scripts/checks/unit-tests.js +3 -5
  243. package/scripts/checks/watcher-supervisor-smoke.js +38 -1
  244. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  245. package/scripts/control/manage-patchwarden.ps1 +69 -4
  246. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  247. package/scripts/control/run-background-supervisor.ps1 +41 -0
  248. package/scripts/control/start-patchwarden-tunnel.ps1 +191 -33
  249. package/scripts/e2e/demo-runtime.mjs +153 -0
  250. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  251. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  252. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  253. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  254. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  255. package/scripts/lib/clean-generated-output.js +11 -0
  256. package/scripts/lib/file-discovery.js +12 -0
  257. package/scripts/release/desktop-preflight.js +211 -0
  258. package/scripts/release/pack-clean.js +59 -14
  259. package/src/assessments/agentAssessor.ts +26 -12
  260. package/src/assessments/assessmentDiagnostics.ts +172 -0
  261. package/src/assessments/assessmentStore.ts +288 -51
  262. package/src/assessments/securitySnapshot.ts +231 -0
  263. package/src/config.ts +70 -13
  264. package/src/control/fileManager.ts +40 -0
  265. package/src/control/middleware/auth.ts +22 -4
  266. package/src/control/middleware/static.ts +16 -3
  267. package/src/control/routeTable.ts +301 -0
  268. package/src/control/routes/audit.ts +14 -6
  269. package/src/control/routes/evidence.ts +1 -1
  270. package/src/control/routes/lineage.ts +1 -1
  271. package/src/control/routes/process.ts +341 -40
  272. package/src/control/routes/sessions.ts +88 -25
  273. package/src/control/routes/status.ts +134 -17
  274. package/src/control/routes/taskActions.ts +24 -58
  275. package/src/control/routes/tasks.ts +66 -58
  276. package/src/control/routes/workspace.ts +56 -25
  277. package/src/control/runtime.ts +71 -43
  278. package/src/control/server.ts +37 -342
  279. package/src/control/shared.ts +77 -11
  280. package/src/diagnostics/allowedTestCommandSafety.ts +12 -0
  281. package/src/direct/directAudit.ts +17 -9
  282. package/src/direct/directGuards.ts +72 -44
  283. package/src/direct/directPatch.ts +95 -8
  284. package/src/direct/directSessionStore.ts +213 -55
  285. package/src/direct/directVerification.ts +9 -23
  286. package/src/doctor.ts +76 -64
  287. package/src/goal/acceptanceEngine.ts +1 -1
  288. package/src/goal/goalProgress.ts +17 -13
  289. package/src/goal/goalReport.ts +5 -13
  290. package/src/goal/goalStatus.ts +19 -3
  291. package/src/goal/goalStore.ts +241 -54
  292. package/src/goal/handoffExport.ts +6 -6
  293. package/src/goal/specKitImport.ts +12 -12
  294. package/src/goal/subgoalSync.ts +11 -8
  295. package/src/goal/worktreeManager.ts +139 -22
  296. package/src/httpServer.ts +113 -30
  297. package/src/logging.ts +10 -3
  298. package/src/policy/projectPolicy.ts +5 -2
  299. package/src/release/releaseGate.ts +74 -27
  300. package/src/runner/agentInvocation.ts +126 -4
  301. package/src/runner/changeCapture.ts +291 -57
  302. package/src/runner/postTaskCleanup.ts +41 -17
  303. package/src/runner/processSecurity.ts +433 -0
  304. package/src/runner/runTask.ts +197 -97
  305. package/src/runner/simpleProcess.ts +66 -31
  306. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  307. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +6 -3
  308. package/src/runner/taskStatusStore.ts +69 -0
  309. package/src/runner/watch.ts +348 -52
  310. package/src/security/contentRedaction.ts +2 -2
  311. package/src/security/discoveryTokenStore.ts +18 -4
  312. package/src/security/loopbackHost.ts +19 -0
  313. package/src/security/pathGuard.ts +35 -16
  314. package/src/security/planGuard.ts +2 -1
  315. package/src/security/runtimeGuard.ts +9 -6
  316. package/src/security/secretComparison.ts +13 -0
  317. package/src/security/sensitiveGuard.ts +27 -8
  318. package/src/security/toolInvocationGuard.ts +2 -2
  319. package/src/security/workspaceRootGuard.ts +53 -0
  320. package/src/smoke-test.ts +27 -26
  321. package/src/tools/catalog/index.ts +4 -0
  322. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  323. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +7 -16
  324. package/src/tools/definitions/toolDefs.ts +1264 -0
  325. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  326. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +136 -39
  327. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +37 -9
  328. package/src/tools/diagnostics/index.ts +6 -0
  329. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  330. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +27 -17
  331. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  332. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +6 -6
  333. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +7 -5
  334. package/src/tools/direct/index.ts +3 -0
  335. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  336. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  337. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  338. package/src/tools/discovery/index.ts +3 -0
  339. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  340. package/src/tools/dispatch/coreDispatch.ts +36 -31
  341. package/src/tools/dispatch/diagnosticDispatch.ts +26 -21
  342. package/src/tools/dispatch/directDispatch.ts +11 -10
  343. package/src/tools/dispatch/goalDispatch.ts +5 -4
  344. package/src/tools/dispatch/releaseDispatch.ts +3 -2
  345. package/src/tools/dispatch/validation.ts +78 -0
  346. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  347. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +41 -26
  348. package/src/tools/goals/index.ts +3 -0
  349. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  350. package/src/tools/registry.ts +17 -1261
  351. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  352. package/src/tools/release/index.ts +2 -0
  353. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  354. package/src/tools/tasks/cancelTask.ts +110 -0
  355. package/src/tools/{createTask.ts → tasks/createTask.ts} +161 -33
  356. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +12 -16
  357. package/src/tools/{evidencePack.ts → tasks/evidencePack.ts} +36 -22
  358. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  359. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +13 -6
  360. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  361. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +46 -44
  362. package/src/tools/tasks/index.ts +18 -0
  363. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +6 -5
  364. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +228 -77
  365. package/src/tools/tasks/retryTask.ts +69 -0
  366. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +73 -46
  367. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  368. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  369. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  370. package/src/tools/tasks/taskStates.ts +54 -0
  371. package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +3 -12
  372. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  373. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  374. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  375. package/src/tools/workspace/index.ts +10 -0
  376. package/src/tools/workspace/listAgents.ts +75 -0
  377. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  378. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  379. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  380. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  381. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  382. package/src/tools/workspace/syncFile.ts +292 -0
  383. package/src/utils/atomicFile.ts +74 -0
  384. package/src/utils/boundedFile.ts +116 -0
  385. package/src/utils/lockedJsonFile.ts +251 -0
  386. package/src/utils/platform.ts +31 -0
  387. package/src/utils/stableJson.ts +12 -0
  388. package/src/version.ts +3 -2
  389. package/src/watcherStatus.ts +19 -1
  390. package/ui/desktop-bootstrap.js +11 -0
  391. package/ui/desktop-bridge.js +36 -0
  392. package/ui/desktop.css +160 -0
  393. package/ui/getting-started.js +93 -0
  394. package/ui/i18n.js +484 -0
  395. package/ui/log-parser.js +30 -0
  396. package/ui/pages/audit.html +12 -0
  397. package/ui/pages/dashboard.html +94 -147
  398. package/ui/pages/direct-sessions.html +123 -26
  399. package/ui/pages/getting-started.html +43 -0
  400. package/ui/pages/logs.html +134 -22
  401. package/ui/pages/settings.html +101 -0
  402. package/ui/pages/task-detail.html +12 -0
  403. package/ui/pages/tasks.html +54 -8
  404. package/ui/pages/workspace.html +38 -1
  405. package/ui/settings.js +254 -0
  406. package/PatchWarden-Control.cmd +0 -6
  407. package/Restart-PatchWarden-Control.cmd +0 -6
  408. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  409. package/dist/test/unit/acceptance-engine.test.js +0 -228
  410. package/dist/test/unit/agent-router.test.d.ts +0 -1
  411. package/dist/test/unit/agent-router.test.js +0 -287
  412. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  413. package/dist/test/unit/android-doctor.test.js +0 -118
  414. package/dist/test/unit/apply-patch.test.d.ts +0 -1
  415. package/dist/test/unit/apply-patch.test.js +0 -225
  416. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  417. package/dist/test/unit/audit-checks.test.js +0 -350
  418. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  419. package/dist/test/unit/chinese-path.test.js +0 -91
  420. package/dist/test/unit/command-guard.test.d.ts +0 -1
  421. package/dist/test/unit/command-guard.test.js +0 -160
  422. package/dist/test/unit/create-task.test.d.ts +0 -1
  423. package/dist/test/unit/create-task.test.js +0 -197
  424. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  425. package/dist/test/unit/diagnose-task.test.js +0 -457
  426. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  427. package/dist/test/unit/direct-guards.test.js +0 -328
  428. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  429. package/dist/test/unit/discovery-token-store.test.js +0 -139
  430. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  431. package/dist/test/unit/evidence-pack.test.js +0 -224
  432. package/dist/test/unit/get-task-status.test.d.ts +0 -1
  433. package/dist/test/unit/get-task-status.test.js +0 -174
  434. package/dist/test/unit/get-task-summary.test.d.ts +0 -1
  435. package/dist/test/unit/get-task-summary.test.js +0 -146
  436. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  437. package/dist/test/unit/goal-graph.test.js +0 -298
  438. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  439. package/dist/test/unit/goal-progress.test.js +0 -381
  440. package/dist/test/unit/goal-report.test.d.ts +0 -1
  441. package/dist/test/unit/goal-report.test.js +0 -159
  442. package/dist/test/unit/goal-status.test.d.ts +0 -1
  443. package/dist/test/unit/goal-status.test.js +0 -215
  444. package/dist/test/unit/goal-store.test.d.ts +0 -1
  445. package/dist/test/unit/goal-store.test.js +0 -253
  446. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  447. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  448. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  449. package/dist/test/unit/goal-tools-registry.test.js +0 -192
  450. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  451. package/dist/test/unit/handoff-export.test.js +0 -263
  452. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  453. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  454. package/dist/test/unit/logging.test.d.ts +0 -1
  455. package/dist/test/unit/logging.test.js +0 -397
  456. package/dist/test/unit/path-guard.test.d.ts +0 -1
  457. package/dist/test/unit/path-guard.test.js +0 -133
  458. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  459. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  460. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  461. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  462. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  463. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  464. package/dist/test/unit/release-gate.test.d.ts +0 -1
  465. package/dist/test/unit/release-gate.test.js +0 -242
  466. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  467. package/dist/test/unit/run-task-loop.test.js +0 -380
  468. package/dist/test/unit/safe-status.test.d.ts +0 -1
  469. package/dist/test/unit/safe-status.test.js +0 -165
  470. package/dist/test/unit/safe-views.test.d.ts +0 -1
  471. package/dist/test/unit/safe-views.test.js +0 -171
  472. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  473. package/dist/test/unit/schema-drift-check.test.js +0 -176
  474. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  475. package/dist/test/unit/sensitive-guard.test.js +0 -104
  476. package/dist/test/unit/spec-kit-import.test.d.ts +0 -1
  477. package/dist/test/unit/spec-kit-import.test.js +0 -341
  478. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  479. package/dist/test/unit/subgoal-sync.test.js +0 -183
  480. package/dist/test/unit/sync-file.test.d.ts +0 -1
  481. package/dist/test/unit/sync-file.test.js +0 -154
  482. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  483. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  484. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  485. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  486. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  487. package/dist/test/unit/toolSearch.test.js +0 -571
  488. package/dist/test/unit/wait-for-task.test.d.ts +0 -1
  489. package/dist/test/unit/wait-for-task.test.js +0 -144
  490. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  491. package/dist/test/unit/watcher-status.test.js +0 -169
  492. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  493. package/dist/test/unit/worktree-manager.test.js +0 -176
  494. package/dist/tools/applyPatch.js +0 -41
  495. package/dist/tools/cancelTask.d.ts +0 -30
  496. package/dist/tools/cancelTask.js +0 -64
  497. package/dist/tools/finalizeDirectSession.js +0 -84
  498. package/dist/tools/goalSubgoalTask.js +0 -110
  499. package/dist/tools/killTask.d.ts +0 -15
  500. package/dist/tools/listAgents.d.ts +0 -12
  501. package/dist/tools/listAgents.js +0 -42
  502. package/dist/tools/reconcileTasks.js +0 -352
  503. package/dist/tools/retryTask.js +0 -45
  504. package/dist/tools/runVerification.js +0 -36
  505. package/dist/tools/syncFile.js +0 -65
  506. package/docs/control-center/control-center-daily-driver.md +0 -211
  507. package/docs/control-center/control-center-mvp.md +0 -205
  508. package/docs/control-center/control-center-phase2.md +0 -159
  509. package/docs/execution-plan-2026-07-09.md +0 -315
  510. package/docs/release-v0.6.0.md +0 -71
  511. package/docs/release-v0.6.1.md +0 -75
  512. package/docs/release-v0.6.4.md +0 -45
  513. package/docs/roadmap-execution-and-acceptance.md +0 -365
  514. package/src/test/unit/acceptance-engine.test.ts +0 -261
  515. package/src/test/unit/agent-router.test.ts +0 -342
  516. package/src/test/unit/android-doctor.test.ts +0 -158
  517. package/src/test/unit/apply-patch.test.ts +0 -293
  518. package/src/test/unit/audit-checks.test.ts +0 -567
  519. package/src/test/unit/chinese-path.test.ts +0 -106
  520. package/src/test/unit/command-guard.test.ts +0 -221
  521. package/src/test/unit/create-task.test.ts +0 -255
  522. package/src/test/unit/diagnose-task.test.ts +0 -544
  523. package/src/test/unit/direct-guards.test.ts +0 -467
  524. package/src/test/unit/discovery-token-store.test.ts +0 -181
  525. package/src/test/unit/evidence-pack.test.ts +0 -251
  526. package/src/test/unit/get-task-status.test.ts +0 -203
  527. package/src/test/unit/get-task-summary.test.ts +0 -173
  528. package/src/test/unit/goal-graph.test.ts +0 -347
  529. package/src/test/unit/goal-progress.test.ts +0 -538
  530. package/src/test/unit/goal-report.test.ts +0 -189
  531. package/src/test/unit/goal-status.test.ts +0 -270
  532. package/src/test/unit/goal-store.test.ts +0 -318
  533. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  534. package/src/test/unit/goal-tools-registry.test.ts +0 -245
  535. package/src/test/unit/handoff-export.test.ts +0 -295
  536. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  537. package/src/test/unit/logging.test.ts +0 -497
  538. package/src/test/unit/path-guard.test.ts +0 -180
  539. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  540. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  541. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  542. package/src/test/unit/release-gate.test.ts +0 -314
  543. package/src/test/unit/run-task-loop.test.ts +0 -425
  544. package/src/test/unit/safe-status.test.ts +0 -187
  545. package/src/test/unit/safe-views.test.ts +0 -184
  546. package/src/test/unit/schema-drift-check.test.ts +0 -260
  547. package/src/test/unit/sensitive-guard.test.ts +0 -124
  548. package/src/test/unit/spec-kit-import.test.ts +0 -429
  549. package/src/test/unit/subgoal-sync.test.ts +0 -236
  550. package/src/test/unit/sync-file.test.ts +0 -231
  551. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  552. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  553. package/src/test/unit/toolSearch.test.ts +0 -756
  554. package/src/test/unit/wait-for-task.test.ts +0 -176
  555. package/src/test/unit/watcher-status.test.ts +0 -190
  556. package/src/test/unit/worktree-manager.test.ts +0 -247
  557. package/src/tools/cancelTask.ts +0 -70
  558. package/src/tools/listAgents.ts +0 -54
  559. package/src/tools/retryTask.ts +0 -53
  560. package/src/tools/syncFile.ts +0 -122
  561. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  562. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  563. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  564. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  565. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  566. /package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +0 -0
  567. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  568. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  569. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  570. /package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +0 -0
  571. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  572. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  573. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  574. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  575. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  576. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  577. /package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +0 -0
  578. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  579. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  580. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  581. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  582. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  583. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  584. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  585. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  586. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  587. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  588. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  589. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  590. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  591. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
@@ -12,26 +12,26 @@
12
12
  import { createServer } from "node:http";
13
13
  import { PATCHWARDEN_VERSION, TOOL_SCHEMA_EPOCH } from "../version.js";
14
14
  import { logger } from "../logging.js";
15
- import { config, controlToken, errorMessage, host, PAGE_ALIASES, port, readBody, resolveTailParam, sendJson, } from "./shared.js";
15
+ import { config, controlToken, errorMessage, host, PAGE_ALIASES, port, readBody, sendJson, } from "./shared.js";
16
16
  import { recordEvent, removeStatusFile, writeStatusFile } from "./runtime.js";
17
- import { checkControlToken } from "./middleware/auth.js";
17
+ import { checkControlToken, isTrustedControlHost } from "./middleware/auth.js";
18
18
  import { serveFavicon, serveStatic } from "./middleware/static.js";
19
- import { handleTasks, handleStaleTasks, handleTaskDetail, handleTaskSafeResult, handleTaskSafeAudit, handleTaskSafeTestSummary, handleTaskSafeDiffSummary } from "./routes/tasks.js";
20
- import { handleReconcile, handleTaskAudit, handleOpenTaskFolder, handleHideStale } from "./routes/taskActions.js";
21
- import { handleDirectSessions, handleDirectSessionDetail, handleDirectSessionSafeSummary, handleDirectSessionFinalize, handleDirectSessionAudit, handleDirectSessionHide } from "./routes/sessions.js";
22
- import { handleLineages, handleLineageDetail } from "./routes/lineage.js";
23
- import { handleEvidencePacks, handleEvidencePackDetail, handleEvidencePackExport } from "./routes/evidence.js";
24
- import { handleProjectPolicy, handleReleaseStatus } from "./routes/policy.js";
25
- import { handleWorkspace, handleWorkspaceRepos, handleWorkspaceRepoStatus } from "./routes/workspace.js";
26
- import { handleManageAction, handleOpenLogsFolder } from "./routes/process.js";
27
- import { handleStatus, handleControlCenterStatus, handleEvents, handleTunnelUiUrl, handleDiagnostics } from "./routes/status.js";
28
- import { handleLogs, handleAudit, handleWarnings } from "./routes/audit.js";
19
+ import { buildRoutes } from "./routeTable.js";
29
20
  // ── Request router ────────────────────────────────────────────────
30
21
  async function handleRequest(req, res) {
31
- const parsedUrl = new URL(req.url || "/", `http://${req.headers.host || host}`);
22
+ res.setHeader("Content-Security-Policy", "frame-ancestors 'none'");
23
+ res.setHeader("Cross-Origin-Resource-Policy", "same-origin");
24
+ res.setHeader("Referrer-Policy", "no-referrer");
25
+ res.setHeader("X-Content-Type-Options", "nosniff");
26
+ res.setHeader("X-Frame-Options", "DENY");
27
+ if (!isTrustedControlHost(req, port)) {
28
+ sendJson(res, 421, { error: "Untrusted Host header" });
29
+ return;
30
+ }
31
+ const parsedUrl = new URL(req.url || "/", `http://${host}:${port}`);
32
32
  const pathname = parsedUrl.pathname;
33
33
  const method = (req.method || "GET").toUpperCase();
34
- // Static routes
34
+ // ── Static resource priority branches (not in routeTable.ts) ──
35
35
  if (method === "GET" && pathname === "/") {
36
36
  serveStatic(res, "pages/dashboard.html");
37
37
  return;
@@ -51,368 +51,40 @@ async function handleRequest(req, res) {
51
51
  }
52
52
  if (method === "GET" &&
53
53
  (pathname === "/colors_and_type.css" ||
54
+ pathname === "/desktop.css" ||
55
+ pathname === "/desktop-bridge.js" ||
56
+ pathname === "/log-parser.js" ||
57
+ pathname === "/i18n.js" ||
58
+ pathname === "/getting-started.js" ||
59
+ pathname === "/settings.js" ||
54
60
  pathname.startsWith("/pages/") ||
55
61
  pathname.startsWith("/partials/") ||
56
62
  pathname.startsWith("/vendor/"))) {
57
63
  serveStatic(res, pathname);
58
64
  return;
59
65
  }
60
- // GET API routes
61
- if (method === "GET" && pathname === "/api/status") {
62
- await handleStatus(res);
63
- return;
64
- }
65
- if (method === "GET" && pathname === "/api/tasks") {
66
- handleTasks(res, {
67
- repo_path: parsedUrl.searchParams.get("repo_path") || undefined,
68
- status: parsedUrl.searchParams.get("status") || undefined,
69
- acceptance_status: parsedUrl.searchParams.get("acceptance_status") || undefined,
70
- agent: parsedUrl.searchParams.get("agent") || undefined,
71
- warning_type: parsedUrl.searchParams.get("warning_type") || undefined,
72
- });
73
- return;
74
- }
75
- if (method === "GET" && pathname === "/api/tasks/stale") {
76
- handleStaleTasks(res);
77
- return;
78
- }
79
- if (method === "GET" && pathname === "/api/lineages") {
80
- handleLineages(res);
81
- return;
82
- }
83
- const lineageMatch = pathname.match(/^\/api\/lineages\/([^/]+)$/);
84
- if (method === "GET" && lineageMatch) {
85
- let lineageId;
86
- try {
87
- lineageId = decodeURIComponent(lineageMatch[1]);
88
- }
89
- catch {
90
- lineageId = lineageMatch[1];
91
- }
92
- handleLineageDetail(res, lineageId);
93
- return;
94
- }
95
- if (method === "GET" && pathname === "/api/project-policy") {
96
- handleProjectPolicy(res, parsedUrl.searchParams.get("repo_path") || ".");
97
- return;
98
- }
99
- if (method === "GET" && pathname === "/api/release/status") {
100
- handleReleaseStatus(res, parsedUrl.searchParams.get("repo_path") || ".");
101
- return;
102
- }
103
- if (method === "GET" && pathname === "/api/evidence-packs") {
104
- handleEvidencePacks(res);
105
- return;
106
- }
107
- const evidencePackMatch = pathname.match(/^\/api\/evidence-packs\/([^/]+)$/);
108
- if (method === "GET" && evidencePackMatch) {
109
- let lineageId;
110
- try {
111
- lineageId = decodeURIComponent(evidencePackMatch[1]);
112
- }
113
- catch {
114
- lineageId = evidencePackMatch[1];
115
- }
116
- handleEvidencePackDetail(res, lineageId);
117
- return;
118
- }
119
- const taskMatch = pathname.match(/^\/api\/tasks\/([^/]+)$/);
120
- if (method === "GET" && taskMatch) {
121
- let taskId;
122
- try {
123
- taskId = decodeURIComponent(taskMatch[1]);
124
- }
125
- catch {
126
- taskId = taskMatch[1];
127
- }
128
- if (taskId === "." ||
129
- taskId === ".." ||
130
- taskId.includes("/") ||
131
- taskId.includes("\\") ||
132
- taskId.includes("\0")) {
133
- sendJson(res, 400, { error: "Invalid task id" });
134
- return;
135
- }
136
- handleTaskDetail(res, taskId);
137
- return;
138
- }
139
- // Safe, bounded views for task artifacts (no full stdout/stderr/diff).
140
- const taskSafeResultMatch = pathname.match(/^\/api\/tasks\/([^/]+)\/safe-result$/);
141
- if (method === "GET" && taskSafeResultMatch) {
142
- let taskId;
143
- try {
144
- taskId = decodeURIComponent(taskSafeResultMatch[1]);
145
- }
146
- catch {
147
- taskId = taskSafeResultMatch[1];
148
- }
149
- handleTaskSafeResult(res, taskId);
150
- return;
151
- }
152
- const taskSafeAuditMatch = pathname.match(/^\/api\/tasks\/([^/]+)\/safe-audit$/);
153
- if (method === "GET" && taskSafeAuditMatch) {
154
- let taskId;
155
- try {
156
- taskId = decodeURIComponent(taskSafeAuditMatch[1]);
157
- }
158
- catch {
159
- taskId = taskSafeAuditMatch[1];
160
- }
161
- handleTaskSafeAudit(res, taskId);
162
- return;
163
- }
164
- const taskSafeTestSummaryMatch = pathname.match(/^\/api\/tasks\/([^/]+)\/safe-test-summary$/);
165
- if (method === "GET" && taskSafeTestSummaryMatch) {
166
- let taskId;
167
- try {
168
- taskId = decodeURIComponent(taskSafeTestSummaryMatch[1]);
169
- }
170
- catch {
171
- taskId = taskSafeTestSummaryMatch[1];
172
- }
173
- handleTaskSafeTestSummary(res, taskId);
174
- return;
175
- }
176
- const taskSafeDiffSummaryMatch = pathname.match(/^\/api\/tasks\/([^/]+)\/safe-diff-summary$/);
177
- if (method === "GET" && taskSafeDiffSummaryMatch) {
178
- let taskId;
179
- try {
180
- taskId = decodeURIComponent(taskSafeDiffSummaryMatch[1]);
181
- }
182
- catch {
183
- taskId = taskSafeDiffSummaryMatch[1];
184
- }
185
- handleTaskSafeDiffSummary(res, taskId);
186
- return;
187
- }
188
- // Logs: /api/logs/<category>?tail=<100|300|1000>
189
- const logsMatch = pathname.match(/^\/api\/logs\/([a-z-]+)$/);
190
- if (method === "GET" && logsMatch) {
191
- const rawCat = logsMatch[1];
192
- const category = rawCat === "core" || rawCat === "direct" || rawCat === "watcher" || rawCat === "control-center"
193
- ? rawCat
194
- : null;
195
- if (!category) {
196
- sendJson(res, 404, { error: "Unknown log category" });
197
- return;
198
- }
199
- const tail = resolveTailParam(parsedUrl.searchParams.get("tail"));
200
- handleLogs(res, category, tail);
201
- return;
202
- }
203
- if (method === "GET" && pathname === "/api/workspace") {
204
- handleWorkspace(res);
205
- return;
206
- }
207
- if (method === "GET" && pathname === "/api/workspace/repos") {
208
- handleWorkspaceRepos(res);
209
- return;
210
- }
211
- // On-demand git status for a single repo (path-traversal safe).
212
- // The repo segment is URL-decoded; traversal is rejected by guardWorkspacePath.
213
- const workspaceRepoMatch = pathname.match(/^\/api\/workspace\/([^/]+(?:\/[^/]+)*)\/status$/);
214
- if (method === "GET" && workspaceRepoMatch) {
215
- let repoParam;
216
- try {
217
- repoParam = decodeURIComponent(workspaceRepoMatch[1]);
218
- }
219
- catch {
220
- sendJson(res, 400, { error: "Invalid repo path encoding" });
221
- return;
222
- }
223
- handleWorkspaceRepoStatus(res, repoParam);
224
- return;
225
- }
226
- if (method === "GET" && pathname === "/api/direct-sessions") {
227
- handleDirectSessions(res);
228
- return;
229
- }
230
- const directSessionSummaryMatch = pathname.match(/^\/api\/direct-sessions\/([^/]+)\/summary$/);
231
- if (method === "GET" && directSessionSummaryMatch) {
232
- let sessionId;
233
- try {
234
- sessionId = decodeURIComponent(directSessionSummaryMatch[1]);
235
- }
236
- catch {
237
- sessionId = directSessionSummaryMatch[1];
238
- }
239
- handleDirectSessionSafeSummary(res, sessionId);
240
- return;
241
- }
242
- const directSessionMatch = pathname.match(/^\/api\/direct-sessions\/([^/]+)$/);
243
- if (method === "GET" && directSessionMatch) {
244
- let sessionId;
245
- try {
246
- sessionId = decodeURIComponent(directSessionMatch[1]);
247
- }
248
- catch {
249
- sessionId = directSessionMatch[1];
250
- }
251
- handleDirectSessionDetail(res, sessionId);
252
- return;
253
- }
254
- if (method === "GET" && pathname === "/api/audit") {
255
- handleAudit(res);
256
- return;
257
- }
258
- if (method === "GET" && pathname === "/api/warnings") {
259
- handleWarnings(res);
260
- return;
261
- }
262
- if (method === "GET" && pathname === "/api/diagnostics") {
263
- handleDiagnostics(res);
264
- return;
265
- }
266
- if (method === "GET" && pathname === "/api/tunnel-ui-url") {
267
- handleTunnelUiUrl(res);
268
- return;
269
- }
270
- if (method === "GET" && pathname === "/api/events") {
271
- const limitParam = parsedUrl.searchParams.get("limit");
272
- let limit = 100;
273
- if (limitParam !== null) {
274
- const n = parseInt(limitParam, 10);
275
- if (Number.isFinite(n) && n > 0 && n <= 1000)
276
- limit = n;
277
- }
278
- handleEvents(res, limit);
279
- return;
280
- }
281
- if (method === "GET" && pathname === "/api/control-center-status") {
282
- handleControlCenterStatus(res);
283
- return;
284
- }
285
- // POST API routes (all require control token)
66
+ // ── POST routes: drain body + verify control token before dispatch ──
67
+ // All POST routes in the table declare `requiresToken: true`; the body is
68
+ // drained up-front so HTTP keep-alive connections stay clean even when the
69
+ // route is unknown (preserving the original handleRequest behavior).
286
70
  if (method === "POST") {
287
- await readBody(req); // drain optional body
71
+ await readBody(req);
288
72
  if (!checkControlToken(req)) {
289
73
  sendJson(res, 403, { error: "Missing or invalid control token" });
290
74
  return;
291
75
  }
292
- if (pathname === "/api/start-all")
293
- return handleManageAction(res, "start", "all");
294
- if (pathname === "/api/stop-all")
295
- return handleManageAction(res, "stop", "all");
296
- if (pathname === "/api/restart-all")
297
- return handleManageAction(res, "restart", "all");
298
- if (pathname === "/api/core/start")
299
- return handleManageAction(res, "start", "core");
300
- if (pathname === "/api/core/stop")
301
- return handleManageAction(res, "stop", "core");
302
- if (pathname === "/api/direct/start")
303
- return handleManageAction(res, "start", "direct");
304
- if (pathname === "/api/direct/stop")
305
- return handleManageAction(res, "stop", "direct");
306
- if (pathname === "/api/open-logs-folder") {
307
- handleOpenLogsFolder(res);
308
- return;
309
- }
310
- // POST /api/direct-sessions/:sessionId/finalize — finalize a direct session
311
- // (must be matched BEFORE any generic /api/direct-sessions/:sessionId pattern)
312
- const finalizeDirectMatch = pathname.match(/^\/api\/direct-sessions\/([^/]+)\/finalize$/);
313
- if (finalizeDirectMatch) {
314
- let sessionId;
315
- try {
316
- sessionId = decodeURIComponent(finalizeDirectMatch[1]);
317
- }
318
- catch {
319
- sessionId = finalizeDirectMatch[1];
320
- }
321
- await handleDirectSessionFinalize(res, sessionId);
322
- return;
323
- }
324
- // POST /api/direct-sessions/:sessionId/audit — audit a direct session
325
- const auditDirectMatch = pathname.match(/^\/api\/direct-sessions\/([^/]+)\/audit$/);
326
- if (auditDirectMatch) {
327
- let sessionId;
328
- try {
329
- sessionId = decodeURIComponent(auditDirectMatch[1]);
330
- }
331
- catch {
332
- sessionId = auditDirectMatch[1];
333
- }
334
- handleDirectSessionAudit(res, sessionId);
335
- return;
336
- }
337
- // POST /api/direct-sessions/:sessionId/hide — hide a direct session from the list
338
- const hideDirectMatch = pathname.match(/^\/api\/direct-sessions\/([^/]+)\/hide$/);
339
- if (hideDirectMatch) {
340
- let sessionId;
341
- try {
342
- sessionId = decodeURIComponent(hideDirectMatch[1]);
343
- }
344
- catch {
345
- sessionId = hideDirectMatch[1];
346
- }
347
- handleDirectSessionHide(res, sessionId);
348
- return;
349
- }
350
- // POST /api/tasks/:taskId/reconcile (token already validated above)
351
- const reconcileMatch = pathname.match(/^\/api\/tasks\/([^/]+)\/reconcile$/);
352
- if (reconcileMatch) {
353
- let taskId;
354
- try {
355
- taskId = decodeURIComponent(reconcileMatch[1]);
356
- }
357
- catch {
358
- taskId = reconcileMatch[1];
359
- }
360
- handleReconcile(res, taskId);
361
- return;
362
- }
363
- // POST /api/tasks/:taskId/audit — run audit_task safely
364
- const auditMatch = pathname.match(/^\/api\/tasks\/([^/]+)\/audit$/);
365
- if (auditMatch) {
366
- let taskId;
367
- try {
368
- taskId = decodeURIComponent(auditMatch[1]);
369
- }
370
- catch {
371
- taskId = auditMatch[1];
372
- }
373
- handleTaskAudit(res, taskId);
374
- return;
375
- }
376
- // POST /api/tasks/:taskId/open-folder — open task folder in file explorer
377
- const openFolderMatch = pathname.match(/^\/api\/tasks\/([^/]+)\/open-folder$/);
378
- if (openFolderMatch) {
379
- let taskId;
380
- try {
381
- taskId = decodeURIComponent(openFolderMatch[1]);
382
- }
383
- catch {
384
- taskId = openFolderMatch[1];
385
- }
386
- handleOpenTaskFolder(res, taskId);
387
- return;
388
- }
389
- // POST /api/tasks/:taskId/hide-stale — hide a stale task from the dashboard
390
- const hideStaleMatch = pathname.match(/^\/api\/tasks\/([^/]+)\/hide-stale$/);
391
- if (hideStaleMatch) {
392
- let taskId;
393
- try {
394
- taskId = decodeURIComponent(hideStaleMatch[1]);
395
- }
396
- catch {
397
- taskId = hideStaleMatch[1];
398
- }
399
- handleHideStale(res, taskId);
400
- return;
401
- }
402
- // POST /api/evidence-packs/:lineageId/export — export an evidence pack for a lineage
403
- const exportPackMatch = pathname.match(/^\/api\/evidence-packs\/([^/]+)\/export$/);
404
- if (exportPackMatch) {
405
- let lineageId;
406
- try {
407
- lineageId = decodeURIComponent(exportPackMatch[1]);
408
- }
409
- catch {
410
- lineageId = exportPackMatch[1];
411
- }
412
- handleEvidencePackExport(res, lineageId);
413
- return;
414
- }
415
- sendJson(res, 404, { error: "Not found" });
76
+ }
77
+ // ── Declarative route table dispatch ──
78
+ // Filter by method → match pattern (in declaration order, so specific
79
+ // patterns win over generic `:id`) → call handler with capture-group slice.
80
+ const routes = buildRoutes(parsedUrl);
81
+ for (const route of routes) {
82
+ if (route.method !== method)
83
+ continue;
84
+ const match = pathname.match(route.pattern);
85
+ if (!match)
86
+ continue;
87
+ await route.handler(res, match.slice(1));
416
88
  return;
417
89
  }
418
90
  sendJson(res, 404, { error: "Not found" });
@@ -4,6 +4,8 @@ import { type PatchWardenConfig } from "../config.js";
4
4
  export declare const projectRoot: string;
5
5
  export declare const uiRoot: string;
6
6
  export declare const manageScriptPath: string;
7
+ export declare const activeConfigPath: string;
8
+ export declare const configIdentitySha256: string;
7
9
  export declare const PAGE_ALIASES: Record<string, string>;
8
10
  export declare function createFallbackConfig(): PatchWardenConfig;
9
11
  export declare let config: PatchWardenConfig;
@@ -13,7 +15,6 @@ export declare const port: number;
13
15
  export declare const host = "127.0.0.1";
14
16
  export declare const CORE_BASE_URL: string;
15
17
  export declare const DIRECT_BASE_URL: string;
16
- export declare const DEFAULT_TUNNEL_CLIENT_EXE = "D:\\ai_agent\\tunnel-client-v0.0.9--context-conduit-topaz-windows-amd64\\tunnel-client.exe";
17
18
  export declare const CONTROL_CENTER_FAVICON = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\">\n <rect width=\"64\" height=\"64\" rx=\"14\" fill=\"#0a0e14\"/>\n <path d=\"M32 52s17-8 17-27V14L32 8 15 14v11c0 19 17 27 17 27z\" fill=\"#111820\" stroke=\"#2dd4a8\" stroke-width=\"4\" stroke-linejoin=\"round\"/>\n <path d=\"M24 32l6 6 12-14\" fill=\"none\" stroke=\"#2dd4a8\" stroke-width=\"5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>";
18
19
  export declare function errorMessage(err: unknown): string;
19
20
  export declare function getRuntimeRoot(direct: boolean): string;
@@ -30,6 +31,29 @@ export declare function readJsonFileSafe<T = unknown>(filePath: string): T | nul
30
31
  export declare function isPathInside(root: string, target: string): boolean;
31
32
  export declare function readJsonFileSafeUnder<T = unknown>(root: string, relPath: string): T | null;
32
33
  export declare function readTextFileSafe(filePath: string): string | null;
34
+ /** Resolve a Control Center artifact path and reject symlink/junction escapes. */
35
+ export declare function guardControlPath(path: string, allowedPrefix: string): string | null;
33
36
  export declare function readFileTail(filePath: string, lines?: number): string;
34
37
  export declare function findLatestLog(dir: string, pattern: RegExp): string | null;
38
+ /**
39
+ * Declarative route descriptor consumed by `routeTable.ts` and dispatched by
40
+ * `server.ts`'s `handleRequest`. The handler receives the slice of path
41
+ * capture groups `[match[1], match[2], ...]` so wrappers can decode them via
42
+ * `decodeParam`. POST routes default to `requiresToken: true`.
43
+ */
44
+ export interface Route {
45
+ method: "GET" | "POST";
46
+ pattern: RegExp;
47
+ handler: (res: ServerResponse, params: string[]) => Promise<void> | void;
48
+ requiresToken?: boolean;
49
+ }
50
+ /**
51
+ * Safely decode a path capture group, falling back to the raw value if the
52
+ * percent-encoding is malformed. Accepts the raw `RegExpMatchArray` or the
53
+ * sliced `string[]` params passed to a Route handler — both are readable by
54
+ * numeric index. Note: under `lib: es2024+` `RegExpMatchArray` is a tuple
55
+ * `[string, ...string[]]`, so we widen the parameter to `readonly string[]` to
56
+ * accept both shapes without forcing callers to cast.
57
+ */
58
+ export declare function decodeParam(match: readonly string[] | RegExpMatchArray | null, index: number): string;
35
59
  export { delimiter, extname, join, resolve, isAbsolute, relative };
@@ -10,15 +10,33 @@
10
10
  import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
11
11
  import { delimiter, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
- import { randomUUID } from "node:crypto";
13
+ import { createHash, randomUUID } from "node:crypto";
14
14
  import { homedir } from "node:os";
15
15
  import { loadConfig } from "../config.js";
16
16
  import { logger } from "../logging.js";
17
+ import { readTextFilePrefixSync, readTextFileTailLinesSync } from "../utils/boundedFile.js";
18
+ import { redactSensitiveContent, redactSensitiveValue } from "../security/contentRedaction.js";
19
+ import { guardPath } from "../security/pathGuard.js";
17
20
  // ── Paths ─────────────────────────────────────────────────────────
18
21
  export const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
19
22
  export const uiRoot = join(projectRoot, "ui");
20
23
  export const manageScriptPath = join(projectRoot, "scripts", "control", "manage-patchwarden.ps1");
24
+ function resolveActiveConfigPath() {
25
+ if (process.env.PATCHWARDEN_CONFIG)
26
+ return resolve(process.env.PATCHWARDEN_CONFIG);
27
+ for (const name of ["patchwarden.config.json", ".patchwarden.json"]) {
28
+ const candidate = resolve(process.cwd(), name);
29
+ if (existsSync(candidate))
30
+ return candidate;
31
+ }
32
+ return resolve(process.cwd(), "patchwarden.config.json");
33
+ }
34
+ export const activeConfigPath = resolveActiveConfigPath();
35
+ export const configIdentitySha256 = createHash("sha256")
36
+ .update(process.platform === "win32" ? activeConfigPath.toLowerCase() : activeConfigPath)
37
+ .digest("hex");
21
38
  export const PAGE_ALIASES = {
39
+ "/getting-started.html": "pages/getting-started.html",
22
40
  "/dashboard.html": "pages/dashboard.html",
23
41
  "/tasks.html": "pages/tasks.html",
24
42
  "/workspace.html": "pages/workspace.html",
@@ -26,11 +44,15 @@ export const PAGE_ALIASES = {
26
44
  "/task-detail.html": "pages/task-detail.html",
27
45
  "/direct-sessions.html": "pages/direct-sessions.html",
28
46
  "/logs.html": "pages/logs.html",
47
+ "/settings.html": "pages/settings.html",
29
48
  };
30
49
  // ── Config (fault-tolerant bootstrap) ─────────────────────────────
31
50
  export function createFallbackConfig() {
32
51
  return {
33
- workspaceRoot: process.cwd(),
52
+ // Fault-tolerant dashboard mode must not widen a rejected workspace to the
53
+ // caller's cwd (which could be a home or drive root). The package root is
54
+ // bounded and all execution capabilities remain disabled below.
55
+ workspaceRoot: projectRoot,
34
56
  plansDir: ".patchwarden/plans",
35
57
  tasksDir: ".patchwarden/tasks",
36
58
  assessmentsDir: ".patchwarden/assessments",
@@ -90,7 +112,6 @@ export const host = "127.0.0.1";
90
112
  // not depend on the real 8080/8081 ports being free on the host.
91
113
  export const CORE_BASE_URL = process.env.PATCHWARDEN_CORE_URL || "http://127.0.0.1:8080";
92
114
  export const DIRECT_BASE_URL = process.env.PATCHWARDEN_DIRECT_URL || "http://127.0.0.1:8081";
93
- export const DEFAULT_TUNNEL_CLIENT_EXE = "D:\\ai_agent\\tunnel-client-v0.0.9--context-conduit-topaz-windows-amd64\\tunnel-client.exe";
94
115
  export const CONTROL_CENTER_FAVICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
95
116
  <rect width="64" height="64" rx="14" fill="#0a0e14"/>
96
117
  <path d="M32 52s17-8 17-27V14L32 8 15 14v11c0 19 17 27 17 27z" fill="#111820" stroke="#2dd4a8" stroke-width="4" stroke-linejoin="round"/>
@@ -207,8 +228,12 @@ export function readJsonFileSafe(filePath) {
207
228
  if (!existsSync(filePath))
208
229
  return null;
209
230
  try {
231
+ const size = statSync(filePath).size;
232
+ if (size > Math.max(1, config.maxReadFileBytes))
233
+ return null;
210
234
  const raw = readFileSync(filePath, "utf-8");
211
- return JSON.parse(raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw);
235
+ const parsed = JSON.parse(raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw);
236
+ return redactSensitiveValue(parsed).value;
212
237
  }
213
238
  catch {
214
239
  return null;
@@ -238,20 +263,29 @@ export function readTextFileSafe(filePath) {
238
263
  if (!existsSync(filePath))
239
264
  return null;
240
265
  try {
241
- return readFileSync(filePath, "utf-8");
266
+ const bounded = readTextFilePrefixSync(filePath, Math.max(1, config.maxReadFileBytes));
267
+ return redactSensitiveContent(bounded.content).content;
268
+ }
269
+ catch {
270
+ return null;
271
+ }
272
+ }
273
+ /** Resolve a Control Center artifact path and reject symlink/junction escapes. */
274
+ export function guardControlPath(path, allowedPrefix) {
275
+ try {
276
+ return guardPath(path, config.workspaceRoot, allowedPrefix);
242
277
  }
243
278
  catch {
244
279
  return null;
245
280
  }
246
281
  }
247
282
  export function readFileTail(filePath, lines = 100) {
248
- if (!existsSync(filePath))
283
+ try {
284
+ return readTextFileTailLinesSync(filePath, lines);
285
+ }
286
+ catch {
249
287
  return "";
250
- const content = readFileSync(filePath, "utf-8");
251
- const allLines = content.split(/\r?\n/);
252
- if (allLines.length > 0 && allLines[allLines.length - 1] === "")
253
- allLines.pop();
254
- return allLines.slice(-lines).join("\n");
288
+ }
255
289
  }
256
290
  export function findLatestLog(dir, pattern) {
257
291
  if (!existsSync(dir))
@@ -282,6 +316,25 @@ export function findLatestLog(dir, pattern) {
282
316
  }
283
317
  return join(dir, latestName);
284
318
  }
319
+ /**
320
+ * Safely decode a path capture group, falling back to the raw value if the
321
+ * percent-encoding is malformed. Accepts the raw `RegExpMatchArray` or the
322
+ * sliced `string[]` params passed to a Route handler — both are readable by
323
+ * numeric index. Note: under `lib: es2024+` `RegExpMatchArray` is a tuple
324
+ * `[string, ...string[]]`, so we widen the parameter to `readonly string[]` to
325
+ * accept both shapes without forcing callers to cast.
326
+ */
327
+ export function decodeParam(match, index) {
328
+ if (!match)
329
+ return "";
330
+ const raw = match[index] || "";
331
+ try {
332
+ return decodeURIComponent(raw);
333
+ }
334
+ catch {
335
+ return raw;
336
+ }
337
+ }
285
338
  // Re-export widely-used node primitives so route modules have a single import
286
339
  // surface for path/delimiter helpers that are also needed alongside the shared
287
340
  // helpers above.
@@ -0,0 +1 @@
1
+ export declare function isDangerousAllowedTestCommand(command: string): boolean;
@@ -0,0 +1,11 @@
1
+ const DANGEROUS_ALLOWED_TEST_COMMAND_PATTERNS = [
2
+ /(^|\s)rm\s+-rf(?:\s|$)/i,
3
+ /(^|\s)del\s+\/s(?:\s|$)/i,
4
+ /(^|\s)format(?:\.exe)?(?:\s|$)/i,
5
+ /(^|\s)shutdown(?:\.exe)?(?:\s|$)/i,
6
+ /curl\s+[^\r\n|]*\|/i,
7
+ /wget\s+[^\r\n|]*\|/i,
8
+ ];
9
+ export function isDangerousAllowedTestCommand(command) {
10
+ return DANGEROUS_ALLOWED_TEST_COMMAND_PATTERNS.some((pattern) => pattern.test(command));
11
+ }