patchwarden 1.5.1 → 1.6.1

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