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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchwarden",
3
- "version": "1.5.1",
3
+ "version": "1.6.2",
4
4
  "description": "Secure local MCP bridge for ChatGPT and local agents, with workspace-scoped task loops, safe summaries, audits, and Direct editing.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,9 +12,12 @@
12
12
  },
13
13
  "files": [
14
14
  "dist/",
15
+ "!dist/test/",
15
16
  "docs/",
17
+ "!docs/archive/",
16
18
  "!docs/optimization-proposal.md",
17
19
  "src/",
20
+ "!src/test/",
18
21
  "examples/",
19
22
  "scripts/",
20
23
  "!scripts/control/kill-patchwarden.ps1",
@@ -22,18 +25,14 @@
22
25
  "!.local/",
23
26
  "ui/",
24
27
  "PatchWarden.cmd",
25
- "PatchWarden-Control.cmd",
26
- "PatchWarden-Control-Tray.cmd",
27
- "PatchWarden-Desktop.cmd",
28
- "Restart-PatchWarden-Control.cmd",
29
- "Stop-PatchWarden.cmd",
30
28
  "README.md",
31
29
  "README.en.md",
32
30
  "LICENSE",
33
31
  "tsconfig.json"
34
32
  ],
35
33
  "scripts": {
36
- "build": "node ./node_modules/typescript/bin/tsc",
34
+ "build": "node scripts/build.js",
35
+ "check:build-output": "node scripts/checks/build-output-check.js",
37
36
  "start": "node dist/index.js",
38
37
  "start:http": "node dist/httpServer.js",
39
38
  "start:control": "node dist/controlCenter.js",
@@ -43,7 +42,7 @@
43
42
  "doctor": "node dist/doctor.js",
44
43
  "doctor:ci": "node dist/doctor.js --allow-default-config",
45
44
  "test:unit": "node scripts/checks/unit-tests.js",
46
- "test": "node dist/smoke-test.js && node scripts/checks/unit-tests.js && node scripts/checks/lifecycle-smoke.js && node scripts/checks/doctor-smoke.js && node scripts/checks/tunnel-supervisor-smoke.js && node scripts/checks/watcher-supervisor-smoke.js && node scripts/checks/control-smoke.js && node scripts/checks/mcp-manifest-check.js && node scripts/checks/brand-check.js && node scripts/checks/control-center-smoke.js",
45
+ "test": "node scripts/checks/build-output-check.js && node dist/smoke-test.js && node scripts/checks/unit-tests.js && node scripts/checks/lifecycle-smoke.js && node scripts/checks/doctor-smoke.js && node scripts/checks/tunnel-supervisor-smoke.js && node scripts/checks/watcher-supervisor-smoke.js && node scripts/checks/control-smoke.js && node scripts/checks/mcp-manifest-check.js && node scripts/checks/brand-check.js && node scripts/checks/control-center-smoke.js",
47
46
  "test:lifecycle": "npm run build && node scripts/checks/lifecycle-smoke.js",
48
47
  "test:doctor": "npm run build && node scripts/checks/doctor-smoke.js",
49
48
  "test:tunnel-supervisor": "npm run build && node scripts/checks/tunnel-supervisor-smoke.js",
@@ -53,18 +52,36 @@
53
52
  "check:search-tool-manifest": "npm run build && node scripts/checks/mcp-manifest-check.js --profile chatgpt_search",
54
53
  "check:brand": "node scripts/checks/brand-check.js",
55
54
  "test:mcp": "npm run build && node scripts/checks/mcp-smoke.js",
55
+ "test:mcp:built": "node scripts/checks/mcp-smoke.js",
56
56
  "test:http-mcp": "npm run build && node scripts/checks/http-mcp-smoke.js",
57
+ "test:http-mcp:built": "node scripts/checks/http-mcp-smoke.js",
57
58
  "pretest": "npm run build",
58
59
  "pack:clean": "npm run build && node scripts/release/pack-clean.js",
59
- "verify:package": "npm run build && node scripts/checks/package-manifest-check.js"
60
+ "pack:clean:built": "node scripts/release/pack-clean.js",
61
+ "verify:package": "npm run build && node scripts/checks/package-manifest-check.js",
62
+ "verify:package:built": "node scripts/checks/package-manifest-check.js",
63
+ "desktop:install": "npm.cmd --prefix desktop install --cache ..\\.npm-cache",
64
+ "desktop:test": "npm.cmd --prefix desktop test",
65
+ "desktop:smoke:unpacked": "npm.cmd --prefix desktop run smoke:unpacked --",
66
+ "desktop:stage": "npm run build && npm.cmd --prefix desktop run stage",
67
+ "desktop:dev": "npm run build && npm.cmd --prefix desktop run dev",
68
+ "desktop:package:dir": "npm run build && npm.cmd --prefix desktop run build:dir",
69
+ "desktop:package": "npm run build && npm.cmd --prefix desktop run build",
70
+ "desktop:preflight": "node scripts/release/desktop-preflight.js",
71
+ "desktop:preflight:release": "node scripts/release/desktop-preflight.js --require-clean"
60
72
  },
61
73
  "dependencies": {
62
- "@modelcontextprotocol/sdk": "^1.0.0"
74
+ "@modelcontextprotocol/sdk": "^1.29.0"
63
75
  },
64
76
  "devDependencies": {
65
77
  "@types/node": "^26.1.0",
66
78
  "typescript": "^6.0.3"
67
79
  },
80
+ "overrides": {
81
+ "@hono/node-server": "2.0.11",
82
+ "hono": "4.12.31",
83
+ "fast-uri": "4.1.1"
84
+ },
68
85
  "engines": {
69
86
  "node": ">=18.0.0"
70
87
  },
package/scripts/README.md CHANGED
@@ -4,13 +4,18 @@ This directory keeps implementation scripts out of the root folder. Normal
4
4
  desktop use should start from the root entrypoints:
5
5
 
6
6
  ```powershell
7
- .\PatchWarden-Desktop.cmd
8
- .\PatchWarden-Control.cmd
9
- .\PatchWarden-Control-Tray.cmd --foreground
10
- .\Stop-PatchWarden.cmd
7
+ .\scripts\launchers\PatchWarden-Desktop.cmd
8
+ .\scripts\launchers\PatchWarden-Control.cmd
9
+ .\scripts\launchers\PatchWarden-Control-Tray.cmd --foreground
10
+ .\scripts\launchers\Stop-PatchWarden.cmd
11
11
  .\PatchWarden.cmd status all
12
12
  ```
13
13
 
14
+ The installable Windows desktop shell is built separately with
15
+ `npm.cmd run desktop:package`. It preserves these source/npm entrypoints and
16
+ uses the same Control Center API rather than adding another process-management
17
+ surface. See `docs/desktop-app.md`.
18
+
14
19
  ## Control Scripts
15
20
 
16
21
  - `control/manage-patchwarden.ps1`: backing implementation for `PatchWarden.cmd`.
@@ -34,11 +39,15 @@ desktop use should start from the root entrypoints:
34
39
  - `checks/mcp-manifest-check.js`: validates MCP manifest expectations.
35
40
  - `brand-check.js`: checks public brand strings.
36
41
  - `checks/package-manifest-check.js`: verifies package contents.
42
+ - `checks/build-output-check.js`: verifies clean-output confinement and exact recursive unit-test compilation.
43
+ - `release/desktop-preflight.js`: builds an isolated Windows directory package and writes a bounded preflight receipt.
37
44
 
38
45
  ## Release Helpers
39
46
 
40
- - `release/pack-clean.js`: rebuilds `release/`, `patchwarden-release.tar.gz`, and the
41
- versioned `PatchWarden-v*.zip` artifact.
47
+ - `release/pack-clean.js`: rebuilds the isolated `release/package/` staging
48
+ directory, `patchwarden-release.tar.gz`, and the versioned
49
+ `PatchWarden-v*.zip` artifact. Desktop and preflight siblings under
50
+ `release/` are preserved.
42
51
 
43
52
  ## Compatibility Launchers
44
53
 
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { cleanGeneratedOutput } from "./lib/clean-generated-output.js";
5
+
6
+ const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
7
+ const dist = resolve(root, "dist");
8
+
9
+ cleanGeneratedOutput(root, dist);
10
+ process.chdir(root);
11
+ await import("../node_modules/typescript/lib/tsc.js");
@@ -12,6 +12,9 @@ const allowedLegacyFiles = new Set([
12
12
  "docs/release-v0.3.0.md",
13
13
  "docs/release-v0.4.0.md",
14
14
  "docs/release-v0.6.0.md",
15
+ "docs/archive/releases/release-v0.6.0.md",
16
+ "docs/archive/releases/release-v0.6.1.md",
17
+ "docs/archive/releases/release-v0.6.4.md",
15
18
  "scripts/checks/brand-check.js",
16
19
  "scripts/release/pack-clean.js",
17
20
  ]);
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ import assert from "node:assert/strict";
3
+ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { relative, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { cleanGeneratedOutput } from "../lib/clean-generated-output.js";
8
+ import { collectMatchingFiles } from "../lib/file-discovery.js";
9
+
10
+ const root = resolve(fileURLToPath(new URL("../..", import.meta.url)));
11
+ const sourceRoot = resolve(root, "src", "test", "unit");
12
+ const compiledRoot = resolve(root, "dist", "test", "unit");
13
+
14
+ const sourceTests = collectMatchingFiles(sourceRoot, (name) => name.endsWith(".test.ts"))
15
+ .map((path) => relative(sourceRoot, path).replace(/\\/g, "/").replace(/\.ts$/, ".js"));
16
+ const compiledTests = collectMatchingFiles(compiledRoot, (name) => name.endsWith(".test.js"))
17
+ .map((path) => relative(compiledRoot, path).replace(/\\/g, "/"));
18
+
19
+ assert.deepEqual(compiledTests, sourceTests, "compiled unit tests must exactly match source test paths");
20
+ assert.ok(compiledTests.some((path) => path.includes("/")), "unit discovery must include nested tests");
21
+
22
+ const fixtureRoot = mkdtempSync(resolve(tmpdir(), "patchwarden-clean-build-"));
23
+ try {
24
+ const fixtureDist = resolve(fixtureRoot, "dist");
25
+ const outside = resolve(fixtureRoot, "keep.txt");
26
+ mkdirSync(fixtureDist);
27
+ writeFileSync(resolve(fixtureDist, "stale.js"), "stale\n", "utf8");
28
+ writeFileSync(outside, "keep\n", "utf8");
29
+ cleanGeneratedOutput(fixtureRoot, fixtureDist);
30
+ assert.equal(existsSync(fixtureDist), false, "clean build must remove stale dist contents");
31
+ assert.equal(existsSync(outside), true, "clean build must preserve files outside dist");
32
+ assert.throws(() => cleanGeneratedOutput(fixtureRoot, resolve(fixtureRoot, "release")), /Refusing/);
33
+ } finally {
34
+ rmSync(fixtureRoot, { recursive: true, force: true });
35
+ }
36
+
37
+ console.log(`[build-output-check] OK: ${sourceTests.length} source tests match ${compiledTests.length} compiled tests.`);
@@ -121,9 +121,9 @@ process.on("SIGTERM", () => {
121
121
  process.exit(143);
122
122
  });
123
123
 
124
- function httpGet(url) {
124
+ function httpGet(url, headers = {}) {
125
125
  return new Promise((resolve, reject) => {
126
- const req = http.get(url, (res) => {
126
+ const req = http.get(url, { headers }, (res) => {
127
127
  let data = "";
128
128
  res.on("data", (chunk) => (data += chunk));
129
129
  res.on("end", () => resolve({ status: res.statusCode, headers: res.headers, body: data }));
@@ -133,6 +133,34 @@ function httpGet(url) {
133
133
  });
134
134
  }
135
135
 
136
+ async function testLoopbackHostBoundary() {
137
+ const name = "Control Center rejects DNS-rebinding Host headers and cannot be framed";
138
+ try {
139
+ const hostileToken = await httpGet(`${BASE_URL}/control-token.json`, {
140
+ Host: "patchwarden.attacker.invalid",
141
+ });
142
+ const hostilePage = await httpGet(`${BASE_URL}/`, {
143
+ Host: "127.0.0.1.attacker.invalid",
144
+ });
145
+ const localPage = await httpGet(`${BASE_URL}/`);
146
+ const problems = [];
147
+ if (hostileToken.status !== 421 || hostileToken.body.includes("token")) {
148
+ problems.push(`hostile token request returned ${hostileToken.status}: ${hostileToken.body.slice(0, 120)}`);
149
+ }
150
+ if (hostilePage.status !== 421) problems.push(`hostile page request returned ${hostilePage.status}`);
151
+ if (localPage.status !== 200) problems.push(`loopback page returned ${localPage.status}`);
152
+ if (String(localPage.headers["x-frame-options"] || "").toUpperCase() !== "DENY") {
153
+ problems.push("X-Frame-Options is not DENY");
154
+ }
155
+ if (!String(localPage.headers["content-security-policy"] || "").includes("frame-ancestors 'none'")) {
156
+ problems.push("CSP does not deny frame ancestors");
157
+ }
158
+ record(name, problems.length === 0, problems.join("; "));
159
+ } catch (err) {
160
+ record(name, false, `error: ${err.message}`);
161
+ }
162
+ }
163
+
136
164
  function httpPost(url, headers = {}) {
137
165
  return new Promise((resolve, reject) => {
138
166
  const parsed = new URL(url);
@@ -221,6 +249,13 @@ async function testStaticFiles() {
221
249
  const root = await httpGet(`${BASE_URL}/`);
222
250
  const vendor = await httpGet(`${BASE_URL}/vendor/tailwindcss-browser.js`);
223
251
  const css = await httpGet(`${BASE_URL}/colors_and_type.css`);
252
+ const desktopCss = await httpGet(`${BASE_URL}/desktop.css`);
253
+ const desktopBridge = await httpGet(`${BASE_URL}/desktop-bridge.js`);
254
+ const logParser = await httpGet(`${BASE_URL}/log-parser.js`);
255
+ const i18n = await httpGet(`${BASE_URL}/i18n.js`);
256
+ const gettingStarted = await httpGet(`${BASE_URL}/pages/getting-started.html`);
257
+ const gettingStartedJs = await httpGet(`${BASE_URL}/getting-started.js`);
258
+ const settings = await httpGet(`${BASE_URL}/pages/settings.html`);
224
259
  const favicon = await httpGet(`${BASE_URL}/favicon.ico`);
225
260
 
226
261
  const checks = [];
@@ -228,7 +263,8 @@ async function testStaticFiles() {
228
263
  const rootCt = root.headers["content-type"] || "";
229
264
  if (!rootCt.includes("text/html")) checks.push(`GET / -> Content-Type ${rootCt} (expected text/html)`);
230
265
  if (!root.body.includes("setup-checklist-card")) checks.push("dashboard missing setup checklist card");
231
- if (!root.body.includes("Show Core / Direct log tails")) checks.push("dashboard activity log is not collapsed behind a summary");
266
+ if (!root.body.includes("查看 Core / Direct 本次日志尾部")) checks.push("dashboard activity log is not collapsed behind a summary");
267
+ if (!root.body.includes("pw-dashboard-toolbar")) checks.push("dashboard missing stable desktop toolbar layout");
232
268
  if (!root.body.includes("evidence-pack-card")) checks.push("dashboard missing v1.5 evidence pack card");
233
269
 
234
270
  if (vendor.status !== 200) checks.push(`GET /vendor/tailwindcss-browser.js -> status ${vendor.status}`);
@@ -239,6 +275,34 @@ async function testStaticFiles() {
239
275
  const cssCt = css.headers["content-type"] || "";
240
276
  if (!cssCt.includes("text/css")) checks.push(`GET /colors_and_type.css -> Content-Type ${cssCt}`);
241
277
 
278
+ if (desktopCss.status !== 200 || !(desktopCss.headers["content-type"] || "").includes("text/css")) {
279
+ checks.push("desktop refinement stylesheet is not served as CSS");
280
+ }
281
+ if (desktopBridge.status !== 200 || !(desktopBridge.headers["content-type"] || "").includes("javascript")) {
282
+ checks.push("desktop bridge is not served as JavaScript");
283
+ }
284
+ if (logParser.status !== 200 || !(logParser.headers["content-type"] || "").includes("javascript") || !logParser.body.includes("PatchWardenLogParser")) {
285
+ checks.push("log parser is not served as JavaScript");
286
+ }
287
+ if (i18n.status !== 200 || !i18n.body.includes("applyTranslations") || !i18n.body.includes('"zh-CN"') || !i18n.body.includes("en:")) {
288
+ checks.push("shared Chinese/English translation dictionary is unavailable");
289
+ }
290
+ if (gettingStarted.status !== 200 || (gettingStarted.body.match(/class="readiness-item"/g) || []).length !== 4 || !gettingStarted.body.includes("home.manual")) {
291
+ checks.push("desktop getting-started page is missing its four readiness checks or manual ChatGPT state");
292
+ }
293
+ if (gettingStartedJs.status !== 200 || !gettingStartedJs.body.includes('fetchJson("/api/status")') || !gettingStartedJs.body.includes("health_check")) {
294
+ checks.push("getting-started controller is missing bounded Tunnel status or health_check guidance");
295
+ }
296
+ if (settings.status !== 200 || !settings.body.includes("/settings.js")) {
297
+ checks.push("desktop settings page is unavailable or missing its bounded bridge");
298
+ }
299
+ for (const marker of ["detectTunnel", "chooseTunnel", "enableDirectProfile", "proxyScope", "saveRuntime", "tunnelId", "runtimeKey", "provisionTunnel", "forgetCredential", "language"]) {
300
+ if (!settings.body.includes(marker)) checks.push(`desktop settings missing runtime control: ${marker}`);
301
+ }
302
+ if (!desktopBridge.body.includes("patchwardenApplyTheme") || !desktopBridge.body.includes("请求超过 10 秒")) {
303
+ checks.push("desktop bridge is missing renderer theme propagation or bounded loading timeout");
304
+ }
305
+
242
306
  if (favicon.status !== 200) checks.push(`GET /favicon.ico -> status ${favicon.status}`);
243
307
  const faviconCt = favicon.headers["content-type"] || "";
244
308
  if (!faviconCt.includes("image/svg+xml")) checks.push(`GET /favicon.ico -> Content-Type ${faviconCt}`);
@@ -268,6 +332,9 @@ async function testPageNavigationRoutes() {
268
332
  "/pages/workspace.html",
269
333
  "/pages/audit.html",
270
334
  "/pages/task-detail.html?id=smoke",
335
+ "/settings.html",
336
+ "/pages/settings.html",
337
+ "/pages/getting-started.html",
271
338
  ];
272
339
 
273
340
  for (const route of routes) {
@@ -411,6 +478,13 @@ async function testTasksJson() {
411
478
  if (!Array.isArray(json.tasks)) problems.push("'tasks' is not an array");
412
479
  if (typeof json.total !== "number") problems.push("'total' is not a number");
413
480
  if (typeof json.returned !== "number") problems.push("'returned' is not a number");
481
+ if (!(json.nextCursor === null || typeof json.nextCursor === "string")) problems.push("'nextCursor' is neither null nor string");
482
+ if (!isObject(json.filters) || !isObject(json.filters.applied) || !isObject(json.filters.options)) problems.push("'filters' contract is missing");
483
+
484
+ const traversal = await httpGet(`${BASE_URL}/api/tasks/..%2Foutside`);
485
+ if (traversal.status !== 400) {
486
+ problems.push(`encoded task traversal returned ${traversal.status} (expected 400)`);
487
+ }
414
488
 
415
489
  if (problems.length === 0) {
416
490
  record(name, true);
@@ -823,7 +897,7 @@ async function testStaleTasksApi() {
823
897
 
824
898
  // ── Test 12: GET /api/workspace/<traversal>/status -> 400 ───────
825
899
  async function testWorkspaceRepoPathTraversal() {
826
- const name = "Test 12: /api/workspace/<traversal>/status rejected with 400";
900
+ const name = "Test 12: workspace status rejects traversal without rejecting dotted names";
827
901
  // Use percent-encoded dots to bypass URL-parser segment normalization;
828
902
  // after decode the repoParam is "../secret" which contains ".." and is
829
903
  // rejected by the server's traversal guard.
@@ -833,7 +907,12 @@ async function testWorkspaceRepoPathTraversal() {
833
907
  if (res.status === 400) {
834
908
  const json = tryJson(res.body);
835
909
  if (json && typeof json.error === "string" && /traversal/i.test(json.error)) {
836
- record(name, true);
910
+ const dotted = await httpGet(`${BASE_URL}/api/workspace/release..candidate/status`);
911
+ if (dotted.status === 404) {
912
+ record(name, true);
913
+ } else {
914
+ record(name, false, `valid dotted repo name returned ${dotted.status} (expected 404 for missing directory)`);
915
+ }
837
916
  } else {
838
917
  record(name, false, `status 400 but error field missing/invalid: ${res.body.slice(0, 200)}`);
839
918
  }
@@ -865,9 +944,9 @@ async function testReconcileNoToken() {
865
944
  }
866
945
  }
867
946
 
868
- // ── Test 14: GET /api/direct-sessions returns empty list (not 500)
947
+ // ── Test 14: GET /api/direct-sessions returns a bounded list (not 500)
869
948
  async function testDirectSessionsEmptyList() {
870
- const name = "Test 14: /api/direct-sessions returns empty list (not 500) when dir missing";
949
+ const name = "Test 14: /api/direct-sessions returns a bounded list (not 500)";
871
950
  try {
872
951
  const res = await httpGet(`${BASE_URL}/api/direct-sessions`);
873
952
  if (res.status !== 200) {
@@ -882,10 +961,20 @@ async function testDirectSessionsEmptyList() {
882
961
  const problems = [];
883
962
  if (!Array.isArray(json.sessions)) problems.push("'sessions' is not an array");
884
963
  if (typeof json.total !== "number") problems.push("'total' is not a number");
964
+ if (!(json.nextCursor === null || typeof json.nextCursor === "string")) problems.push("'nextCursor' is neither null nor string");
965
+ if (!isObject(json.filters) || !isObject(json.filters.options)) problems.push("'filters' contract is missing");
966
+ if (typeof json.direct_profile_enabled !== "boolean") problems.push("'direct_profile_enabled' is not a boolean");
885
967
  if (json.reason !== null && typeof json.reason !== "string") problems.push("'reason' is neither null nor string");
886
- if (Array.isArray(json.sessions) && json.sessions.length !== json.total) {
887
- problems.push(`sessions.length (${json.sessions.length}) != total (${json.total})`);
968
+ if (Array.isArray(json.sessions) && json.sessions.length > json.total) {
969
+ problems.push(`sessions.length (${json.sessions.length}) exceeds total (${json.total})`);
970
+ }
971
+ if (Array.isArray(json.sessions) && json.sessions.length < json.total && typeof json.nextCursor !== "string") {
972
+ problems.push("paginated direct sessions response is missing nextCursor");
888
973
  }
974
+ const invalidDetail = await httpGet(`${BASE_URL}/api/direct-sessions/invalid.id`);
975
+ const invalidSummary = await httpGet(`${BASE_URL}/api/direct-sessions/invalid.id/summary`);
976
+ if (invalidDetail.status !== 400) problems.push(`invalid detail id returned ${invalidDetail.status} (expected 400)`);
977
+ if (invalidSummary.status !== 400) problems.push(`invalid summary id returned ${invalidSummary.status} (expected 400)`);
889
978
  if (problems.length === 0) {
890
979
  record(name, true);
891
980
  } else {
@@ -915,6 +1004,8 @@ async function testLogsTailParam() {
915
1004
  if (typeof json.stderr !== "string") problems.push("'stderr' is not a string");
916
1005
  if (json.tail !== 300) problems.push(`'tail' is ${json.tail} (expected 300)`);
917
1006
  if (json.category !== "core") problems.push(`'category' is ${json.category} (expected "core")`);
1007
+ if (json.historical_snapshot !== true) problems.push("'historical_snapshot' is not true");
1008
+ if (json.redacted !== true) problems.push("'redacted' is not true");
918
1009
  if (problems.length === 0) {
919
1010
  record(name, true);
920
1011
  } else {
@@ -1440,7 +1531,7 @@ async function main() {
1440
1531
  process.exit(1);
1441
1532
  }
1442
1533
 
1443
- child = spawn("node", [serverPath], {
1534
+ child = spawn(process.execPath, [serverPath], {
1444
1535
  cwd: projectRoot,
1445
1536
  env: {
1446
1537
  ...process.env,
@@ -1480,6 +1571,7 @@ async function main() {
1480
1571
  console.log(`[control-center-smoke] server ready at ${BASE_URL}`);
1481
1572
 
1482
1573
  await testStaticFiles();
1574
+ await testLoopbackHostBoundary();
1483
1575
  await testPageNavigationRoutes();
1484
1576
  await testStatusJson();
1485
1577
  await testTasksJson();
@@ -139,12 +139,13 @@ try {
139
139
 
140
140
  const expectedFiles = [
141
141
  "PatchWarden.cmd",
142
- "PatchWarden-Control.cmd",
143
- "PatchWarden-Control-Tray.cmd",
144
- "PatchWarden-Desktop.cmd",
145
- "Restart-PatchWarden-Control.cmd",
146
- "Stop-PatchWarden.cmd",
142
+ "scripts/launchers/PatchWarden-Control.cmd",
143
+ "scripts/launchers/PatchWarden-Control-Tray.cmd",
144
+ "scripts/launchers/PatchWarden-Desktop.cmd",
145
+ "scripts/launchers/Restart-PatchWarden-Control.cmd",
146
+ "scripts/launchers/Stop-PatchWarden.cmd",
147
147
  "scripts/control/manage-patchwarden.ps1",
148
+ "scripts/control/run-background-supervisor.ps1",
148
149
  "scripts/control/stop-patchwarden.ps1",
149
150
  "scripts/launchers/Start-PatchWarden-Tunnel.cmd",
150
151
  "scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd",
@@ -158,14 +159,24 @@ try {
158
159
  if (!rootEntry.includes("manage-patchwarden.ps1")) {
159
160
  throw new Error("PatchWarden.cmd does not invoke the consolidated manager");
160
161
  }
161
- const stopEntry = readFileSync(join(root, "Stop-PatchWarden.cmd"), "utf8");
162
- if (!stopEntry.includes("scripts\\control\\stop-patchwarden.ps1")) {
162
+ const stopEntry = readFileSync(join(root, "scripts", "launchers", "Stop-PatchWarden.cmd"), "utf8");
163
+ if (!stopEntry.includes("control\\stop-patchwarden.ps1")) {
163
164
  throw new Error("Stop-PatchWarden.cmd does not invoke the one-click shutdown script");
164
165
  }
165
- const desktopEntry = readFileSync(join(root, "PatchWarden-Desktop.cmd"), "utf8");
166
+ const desktopEntry = readFileSync(join(root, "scripts", "launchers", "PatchWarden-Desktop.cmd"), "utf8");
166
167
  if (!desktopEntry.includes("control-center-tray.ps1") || !desktopEntry.includes("WindowStyle Hidden")) {
167
168
  throw new Error("PatchWarden-Desktop.cmd must launch the tray hidden as the daily desktop entry");
168
169
  }
170
+ const managerSource = readFileSync(manager, "utf8");
171
+ const wrapperSource = readFileSync(join(root, "scripts", "control", "run-background-supervisor.ps1"), "utf8");
172
+ for (const expected of ["supervisor-status.json", "supervisor.stdout.log", "supervisor.stderr.log", "run-background-supervisor.ps1", "Write-BackgroundStartingState"]) {
173
+ if (!managerSource.includes(expected)) {
174
+ throw new Error(`background manager is missing supervisor observability: ${expected}`);
175
+ }
176
+ }
177
+ if (!wrapperSource.includes("1> $stdout 2> $stderr") || wrapperSource.includes("CONTROL_PLANE_API_KEY")) {
178
+ throw new Error("background wrapper must redirect supervisor logs without credential arguments");
179
+ }
169
180
  console.log("ok - control handles orphan cleanup, scoped kill, port conflicts, health fallback, and Core/Direct lifecycle actions");
170
181
  } finally {
171
182
  if (fakeTunnel?.exitCode === null) fakeTunnel.kill();
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from "node:child_process";
3
+ import http from "node:http";
3
4
  import {
4
5
  existsSync,
5
6
  mkdirSync,
6
7
  mkdtempSync,
8
+ readFileSync,
7
9
  rmSync,
8
10
  writeFileSync,
9
11
  } from "node:fs";
@@ -11,7 +13,7 @@ import { tmpdir } from "node:os";
11
13
  import { dirname, join, resolve } from "node:path";
12
14
  import { fileURLToPath } from "node:url";
13
15
  import { setTimeout as sleep } from "node:timers/promises";
14
- import { CHATGPT_CORE_TOOL_NAMES } from "../../dist/tools/toolCatalog.js";
16
+ import { CHATGPT_CORE_TOOL_NAMES } from "../../dist/tools/catalog/toolCatalog.js";
15
17
 
16
18
  const __dirname = dirname(fileURLToPath(import.meta.url));
17
19
  const root = resolve(__dirname, "..", "..");
@@ -96,6 +98,25 @@ function toolJson(result) {
96
98
  return JSON.parse(toolText(result));
97
99
  }
98
100
 
101
+ function requestWithHost(path, hostHeader) {
102
+ return new Promise((resolvePromise, rejectPromise) => {
103
+ const request = http.request({
104
+ hostname: host,
105
+ port,
106
+ path,
107
+ method: "GET",
108
+ headers: { Host: hostHeader },
109
+ }, (response) => {
110
+ let body = "";
111
+ response.setEncoding("utf-8");
112
+ response.on("data", (chunk) => { body += chunk; });
113
+ response.on("end", () => resolvePromise({ status: response.statusCode, body }));
114
+ });
115
+ request.on("error", rejectPromise);
116
+ request.end();
117
+ });
118
+ }
119
+
99
120
  console.log("\n=== PatchWarden HTTP MCP Smoke Tests ===\n");
100
121
 
101
122
  try {
@@ -126,7 +147,7 @@ try {
126
147
  );
127
148
 
128
149
  console.log(`Starting HTTP MCP server on ${mcpUrl}...`);
129
- serverProcess = spawn("node", [serverPath], {
150
+ serverProcess = spawn(process.execPath, [serverPath], {
130
151
  cwd: root,
131
152
  env: {
132
153
  ...process.env,
@@ -163,6 +184,13 @@ try {
163
184
  }
164
185
  });
165
186
 
187
+ await test("HTTP MCP rejects DNS-rebinding Host headers", async () => {
188
+ const response = await requestWithHost("/healthz", "patchwarden.attacker.invalid");
189
+ if (response.status !== 421 || !response.body.includes("Untrusted Host")) {
190
+ throw new Error(`Unexpected hostile Host response: ${JSON.stringify(response)}`);
191
+ }
192
+ });
193
+
166
194
  await test("wrong endpoint returns structured 404 guidance", async () => {
167
195
  const response = await fetch(`http://${host}:${port}/wrong-path`);
168
196
  const data = await response.json();
@@ -262,7 +290,7 @@ try {
262
290
 
263
291
  // Start server WITH owner token
264
292
  console.log("\n Starting server with owner token...");
265
- serverProcess = spawn("node", [serverPath], {
293
+ serverProcess = spawn(process.execPath, [serverPath], {
266
294
  cwd: root,
267
295
  env: {
268
296
  ...process.env,
@@ -278,7 +306,7 @@ try {
278
306
  // Wait for server to be ready
279
307
  for (let i = 0; i < 10; i++) {
280
308
  try {
281
- const r = await fetch(`${mcpUrl}/healthz`);
309
+ const r = await fetch(`http://${host}:${port}/healthz`);
282
310
  if (r.status === 200) break;
283
311
  } catch {}
284
312
  await sleep(500);
@@ -328,6 +356,56 @@ try {
328
356
  }
329
357
  });
330
358
 
359
+ const acceptanceTaskId = "task_http_acceptance";
360
+ const acceptanceTaskDir = join(workspaceRoot, ".patchwarden", "tasks", acceptanceTaskId);
361
+ mkdirSync(acceptanceTaskDir, { recursive: true });
362
+ writeFileSync(join(acceptanceTaskDir, "status.json"), JSON.stringify({
363
+ task_id: acceptanceTaskId,
364
+ status: "done_by_agent",
365
+ acceptance_status: "pending",
366
+ }), "utf-8");
367
+
368
+ await test("admin acceptance requires the owner token", async () => {
369
+ const response = await fetch(`http://${host}:${port}/admin/tasks/${acceptanceTaskId}/accept`, {
370
+ method: "POST",
371
+ headers: { "content-type": "application/json" },
372
+ body: JSON.stringify({ notes: "reviewed" }),
373
+ });
374
+ if (response.status !== 401) throw new Error(`Expected 401, got ${response.status}`);
375
+ });
376
+
377
+ await test("admin acceptance writes bounded atomic evidence", async () => {
378
+ const secret = `ghp_${"a".repeat(24)}`;
379
+ const response = await fetch(`http://${host}:${port}/admin/tasks/${acceptanceTaskId}/accept`, {
380
+ method: "POST",
381
+ headers: {
382
+ "content-type": "application/json",
383
+ Authorization: `Bearer ${OWNER_TOKEN}`,
384
+ },
385
+ body: JSON.stringify({ notes: `reviewed ${secret}` }),
386
+ });
387
+ if (response.status !== 200) throw new Error(`Expected 200, got ${response.status}: ${await response.text()}`);
388
+ const acceptance = JSON.parse(readFileSync(join(acceptanceTaskDir, "acceptance.json"), "utf-8"));
389
+ const status = JSON.parse(readFileSync(join(acceptanceTaskDir, "status.json"), "utf-8"));
390
+ if (acceptance.status !== "accepted" || status.acceptance_status !== "accepted") {
391
+ throw new Error("Acceptance state was not persisted consistently");
392
+ }
393
+ if (JSON.stringify(acceptance).includes(secret)) throw new Error("Acceptance notes persisted a token");
394
+ if (existsSync(join(acceptanceTaskDir, "status.json.lock"))) throw new Error("Status lock was not released");
395
+ });
396
+
397
+ await test("admin acceptance rejects oversized request bodies", async () => {
398
+ const response = await fetch(`http://${host}:${port}/admin/tasks/${acceptanceTaskId}/reject`, {
399
+ method: "POST",
400
+ headers: {
401
+ "content-type": "application/json",
402
+ Authorization: `Bearer ${OWNER_TOKEN}`,
403
+ },
404
+ body: JSON.stringify({ reason: "x".repeat(70 * 1024) }),
405
+ });
406
+ if (response.status !== 413) throw new Error(`Expected 413, got ${response.status}`);
407
+ });
408
+
331
409
  } catch (error) {
332
410
  fail("HTTP MCP smoke setup", error);
333
411
  } finally {
@@ -152,16 +152,16 @@ try {
152
152
  );
153
153
  process.env.PATCHWARDEN_CONFIG = configPath;
154
154
 
155
- const { savePlan } = await import("../../dist/tools/savePlan.js");
156
- const { createTask } = await import("../../dist/tools/createTask.js");
157
- const { getTaskStatus } = await import("../../dist/tools/getTaskStatus.js");
158
- const { cancelTask } = await import("../../dist/tools/cancelTask.js");
159
- const { killTask } = await import("../../dist/tools/killTask.js");
160
- const { getDiff } = await import("../../dist/tools/taskOutputs.js");
161
- const { listAgents } = await import("../../dist/tools/listAgents.js");
162
- const { getTaskSummary } = await import("../../dist/tools/getTaskSummary.js");
163
- const { auditTask } = await import("../../dist/tools/auditTask.js");
164
- const { waitForTask } = await import("../../dist/tools/waitForTask.js");
155
+ const { savePlan } = await import("../../dist/tools/goals/savePlan.js");
156
+ const { createTask } = await import("../../dist/tools/tasks/createTask.js");
157
+ const { getTaskStatus } = await import("../../dist/tools/tasks/getTaskStatus.js");
158
+ const { cancelTask } = await import("../../dist/tools/tasks/cancelTask.js");
159
+ const { killTask } = await import("../../dist/tools/tasks/killTask.js");
160
+ const { getDiff } = await import("../../dist/tools/tasks/taskOutputs.js");
161
+ const { listAgents } = await import("../../dist/tools/workspace/listAgents.js");
162
+ const { getTaskSummary } = await import("../../dist/tools/tasks/getTaskSummary.js");
163
+ const { auditTask } = await import("../../dist/tools/diagnostics/auditTask.js");
164
+ const { waitForTask } = await import("../../dist/tools/tasks/waitForTask.js");
165
165
  const { runTask } = await import("../../dist/runner/runTask.js");
166
166
  const { reloadConfig } = await import("../../dist/config.js");
167
167
 
@@ -170,6 +170,9 @@ try {
170
170
  if (result.total !== 8 || result.agents.some((agent) => !agent.available)) {
171
171
  throw new Error(`Unexpected agent availability: ${JSON.stringify(result)}`);
172
172
  }
173
+ if (result.agents.some((agent) => agent.availability_scope !== "executable_only" || agent.provider_status !== "not_checked")) {
174
+ throw new Error(`Agent readiness scope was not explicit: ${JSON.stringify(result)}`);
175
+ }
173
176
  });
174
177
 
175
178
  await test("git diff captures tracked and untracked task changes", async () => {
@@ -803,12 +806,12 @@ try {
803
806
  const task = await createTask({ plan_id: plan.plan_id, agent: "slow", repo_path: "repo", timeout_seconds: 1 });
804
807
  const started = Date.now();
805
808
  const result = await runTask(task.task_id);
806
- if (result.status !== "failed" || !result.error?.includes("timed out")) {
809
+ if (result.status !== "timeout" || !result.error?.includes("timed out")) {
807
810
  throw new Error(`Expected timeout failure, got ${JSON.stringify(result)}`);
808
811
  }
809
812
  if (Date.now() - started > 10000) throw new Error("Timeout took too long to stop the process");
810
813
  const status = getTaskStatus(task.task_id);
811
- if (status.phase !== "failed" || !status.last_heartbeat_at) {
814
+ if (status.phase !== "timeout" || status.termination_reason !== "timeout" || !status.last_heartbeat_at) {
812
815
  throw new Error(`Missing timeout phase/heartbeat: ${JSON.stringify(status)}`);
813
816
  }
814
817
  if (!existsSync(join(task.path, "progress.md"))) throw new Error("progress.md missing");