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
@@ -10,9 +10,8 @@
10
10
  import { existsSync, statSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
  import { type ServerResponse } from "node:http";
13
- import { listTasks, type TaskEntry } from "../../tools/listTasks.js";
14
- import type { AcceptanceStatus } from "../../tools/createTask.js";
15
- import { safeAudit, safeDiffSummary, safeResult, safeTestSummary } from "../../tools/safeViews.js";
13
+ import { listTasks } from "../../tools/tasks/listTasks.js";
14
+ import { safeAudit, safeDiffSummary, safeResult, safeTestSummary } from "../../tools/diagnostics/safeViews.js";
16
15
  import { getTasksDir } from "../../config.js";
17
16
  import {
18
17
  augmentTaskWithStale,
@@ -20,11 +19,12 @@ import {
20
19
  fileMtimeIso,
21
20
  isValidTaskId,
22
21
  parseReviewVerdict,
22
+ reconstructTaskEntry,
23
23
  readWatcherStatusSafe,
24
24
  StaleClassification,
25
25
  TERMINAL_TASK_STATUSES,
26
26
  } from "../runtime.js";
27
- import { config, errorMessage, readJsonFileSafe, readTextFileSafe, sendJson } from "../shared.js";
27
+ import { config, errorMessage, guardControlPath, readJsonFileSafe, readTextFileSafe, sendJson } from "../shared.js";
28
28
 
29
29
  export interface TaskFilters {
30
30
  repo_path?: string;
@@ -32,6 +32,10 @@ export interface TaskFilters {
32
32
  acceptance_status?: string;
33
33
  agent?: string;
34
34
  warning_type?: string;
35
+ date_from?: string;
36
+ date_to?: string;
37
+ limit?: number;
38
+ cursor?: string;
35
39
  }
36
40
 
37
41
  export function handleTasks(res: ServerResponse, filters?: TaskFilters): void {
@@ -40,6 +44,7 @@ export function handleTasks(res: ServerResponse, filters?: TaskFilters): void {
40
44
  const watcher = result.watcher;
41
45
  const now = Date.now();
42
46
  let augmented = result.tasks.map((t) => augmentTaskWithStale(t, watcher, now));
47
+ const facetSource = augmented.slice();
43
48
  // Apply optional filters from query params.
44
49
  if (filters) {
45
50
  if (filters.repo_path) {
@@ -66,21 +71,58 @@ export function handleTasks(res: ServerResponse, filters?: TaskFilters): void {
66
71
  }
67
72
  if (filters.warning_type) {
68
73
  const wt = filters.warning_type;
69
- if (wt === "stale") {
74
+ if (wt === "stale" || wt === "stale_task") {
70
75
  augmented = augmented.filter((t) => t.is_stale);
71
76
  } else if (wt === "error") {
72
77
  augmented = augmented.filter((t) => t.error !== null && t.error !== "");
78
+ } else if (wt === "failed_verification") {
79
+ augmented = augmented.filter((t) => t.status === wt);
73
80
  } else {
74
- // Treat as a specific stale_reason token to match against.
75
- augmented = augmented.filter((t) => Array.isArray(t.stale_reasons) && t.stale_reasons.includes(wt));
81
+ augmented = augmented.filter((t) =>
82
+ t.status === wt || t.acceptance_status === wt ||
83
+ (Array.isArray(t.stale_reasons) && t.stale_reasons.includes(wt)),
84
+ );
76
85
  }
77
86
  }
87
+ if (filters.date_from || filters.date_to) {
88
+ const from = filters.date_from ? Date.parse(`${filters.date_from}T00:00:00`) : Number.NEGATIVE_INFINITY;
89
+ const to = filters.date_to ? Date.parse(`${filters.date_to}T23:59:59`) : Number.POSITIVE_INFINITY;
90
+ augmented = augmented.filter((task) => {
91
+ const timestamp = Date.parse(String(task.updated_at || task.created_at || ""));
92
+ return Number.isFinite(timestamp) && timestamp >= from && timestamp <= to;
93
+ });
94
+ }
78
95
  }
96
+ augmented.sort((a, b) => String(b.created_at || "").localeCompare(String(a.created_at || "")) || String(b.task_id).localeCompare(String(a.task_id)));
79
97
  const staleCount = augmented.filter((t) => t.is_stale).length;
98
+ const limit = Math.max(10, Math.min(filters?.limit || 25, 100));
99
+ const offset = filters?.cursor && /^\d+$/.test(filters.cursor) ? Number(filters.cursor) : 0;
100
+ const page = augmented.slice(offset, offset + limit);
101
+ const nextCursor = offset + page.length < augmented.length ? String(offset + page.length) : null;
102
+ const facets = {
103
+ repos: [...new Set(facetSource.map((task) => String(task.repo_path || ".")))].sort(),
104
+ statuses: [...new Set(facetSource.map((task) => String(task.status || "unknown")))].sort(),
105
+ agents: [...new Set(facetSource.map((task) => String(task.agent || "unknown")))].sort(),
106
+ };
80
107
  sendJson(res, 200, {
81
- tasks: augmented,
108
+ tasks: page,
82
109
  total: augmented.length,
83
- returned: augmented.length,
110
+ returned: page.length,
111
+ nextCursor,
112
+ next_cursor: nextCursor,
113
+ filters: {
114
+ applied: {
115
+ repo_path: filters?.repo_path || null,
116
+ status: filters?.status || null,
117
+ acceptance_status: filters?.acceptance_status || null,
118
+ agent: filters?.agent || null,
119
+ warning_type: filters?.warning_type || null,
120
+ date_from: filters?.date_from || null,
121
+ date_to: filters?.date_to || null,
122
+ },
123
+ options: facets,
124
+ },
125
+ facets,
84
126
  watcher,
85
127
  stale_count: staleCount,
86
128
  });
@@ -89,6 +131,10 @@ export function handleTasks(res: ServerResponse, filters?: TaskFilters): void {
89
131
  tasks: [],
90
132
  total: 0,
91
133
  returned: 0,
134
+ nextCursor: null,
135
+ next_cursor: null,
136
+ filters: { applied: {}, options: { repos: [], statuses: [], agents: [] } },
137
+ facets: { repos: [], statuses: [], agents: [] },
92
138
  watcher: null,
93
139
  stale_count: 0,
94
140
  error: errorMessage(err),
@@ -156,9 +202,13 @@ export function handleStaleTasks(res: ServerResponse): void {
156
202
 
157
203
  export function handleTaskDetail(res: ServerResponse, taskId: string): void {
158
204
  try {
205
+ if (!isValidTaskId(taskId)) {
206
+ sendJson(res, 400, { error: "Invalid task id" });
207
+ return;
208
+ }
159
209
  const tasksDir = getTasksDir(config);
160
- const taskDir = join(tasksDir, taskId);
161
- if (!existsSync(taskDir) || !statSync(taskDir).isDirectory()) {
210
+ const taskDir = guardControlPath(join(tasksDir, taskId), config.tasksDir);
211
+ if (!taskDir || !existsSync(taskDir) || !statSync(taskDir).isDirectory()) {
162
212
  sendJson(res, 404, { error: "Task not found" });
163
213
  return;
164
214
  }
@@ -204,33 +254,7 @@ export function handleTaskDetail(res: ServerResponse, taskId: string): void {
204
254
  let stale: StaleClassification | null = null;
205
255
  if (statusData) {
206
256
  const watcher = readWatcherStatusSafe();
207
- const VALID_ACCEPTANCE2 = ["pending", "accepted", "rejected", "needs_fix", "blocked"];
208
- const taskStatus2 = String(statusData.status || "pending");
209
- const taskAcceptanceStatus2 = taskStatus2 === "done_by_agent"
210
- ? (typeof statusData.acceptance_status === "string" && VALID_ACCEPTANCE2.includes(statusData.acceptance_status) ? (statusData.acceptance_status as AcceptanceStatus) : "pending" as AcceptanceStatus)
211
- : null;
212
- const entry: TaskEntry = {
213
- task_id: taskId,
214
- plan_id: String(statusData.plan_id || ""),
215
- title: "",
216
- agent: String(statusData.agent || ""),
217
- status: taskStatus2 as TaskEntry["status"],
218
- phase: String(runtimeData?.phase || statusData.phase || "queued") as TaskEntry["phase"],
219
- acceptance_status: taskAcceptanceStatus2,
220
- created_at: String(statusData.created_at || ""),
221
- updated_at: String(statusData.updated_at || ""),
222
- workspace_root: String(statusData.workspace_root || config.workspaceRoot),
223
- repo_path: String(statusData.repo_path || "."),
224
- resolved_repo_path: String(statusData.resolved_repo_path || statusData.repo_path || config.workspaceRoot),
225
- test_command: String(statusData.test_command || ""),
226
- verify_commands: Array.isArray(statusData.verify_commands) ? (statusData.verify_commands as string[]) : [],
227
- error: statusData.error ? String(statusData.error) : null,
228
- last_heartbeat_at: String(runtimeData?.last_heartbeat_at || statusData.last_heartbeat_at || statusData.updated_at || ""),
229
- current_command: runtimeData?.current_command === undefined ? null : String(runtimeData.current_command || "") || null,
230
- timeout_seconds: Number(statusData.timeout_seconds) || config.defaultTaskTimeoutSeconds,
231
- pending_reason: null,
232
- watcher_status: watcher.status,
233
- };
257
+ const entry = reconstructTaskEntry(taskId, statusData, runtimeData ?? {}, watcher);
234
258
  stale = classifyStaleTask(entry, watcher);
235
259
  }
236
260
 
@@ -264,11 +288,7 @@ export function handleTaskSafeResult(res: ServerResponse, taskId: string): void
264
288
  sendJson(res, 400, { error: "Invalid task id" });
265
289
  return;
266
290
  }
267
- try {
268
- sendJson(res, 200, safeResult(taskId, { max_items: 12 }));
269
- } catch (err) {
270
- sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });
271
- }
291
+ sendJson(res, 200, safeResult(taskId, { max_items: 12 }));
272
292
  } catch (err) {
273
293
  sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });
274
294
  }
@@ -280,11 +300,7 @@ export function handleTaskSafeAudit(res: ServerResponse, taskId: string): void {
280
300
  sendJson(res, 400, { error: "Invalid task id" });
281
301
  return;
282
302
  }
283
- try {
284
- sendJson(res, 200, safeAudit(taskId, { max_items: 12 }));
285
- } catch (err) {
286
- sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });
287
- }
303
+ sendJson(res, 200, safeAudit(taskId, { max_items: 12 }));
288
304
  } catch (err) {
289
305
  sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });
290
306
  }
@@ -296,11 +312,7 @@ export function handleTaskSafeTestSummary(res: ServerResponse, taskId: string):
296
312
  sendJson(res, 400, { error: "Invalid task id" });
297
313
  return;
298
314
  }
299
- try {
300
- sendJson(res, 200, safeTestSummary(taskId));
301
- } catch (err) {
302
- sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });
303
- }
315
+ sendJson(res, 200, safeTestSummary(taskId));
304
316
  } catch (err) {
305
317
  sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });
306
318
  }
@@ -312,11 +324,7 @@ export function handleTaskSafeDiffSummary(res: ServerResponse, taskId: string):
312
324
  sendJson(res, 400, { error: "Invalid task id" });
313
325
  return;
314
326
  }
315
- try {
316
- sendJson(res, 200, safeDiffSummary(taskId, { max_items: 12 }));
317
- } catch (err) {
318
- sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });
319
- }
327
+ sendJson(res, 200, safeDiffSummary(taskId, { max_items: 12 }));
320
328
  } catch (err) {
321
329
  sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });
322
330
  }
@@ -10,14 +10,16 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
10
10
  import { join } from "node:path";
11
11
  import { execFile } from "node:child_process";
12
12
  import { type ServerResponse } from "node:http";
13
- import { type AgentAvailability, listAgents } from "../../tools/listAgents.js";
13
+ import { type AgentAvailability, listAgents } from "../../tools/workspace/listAgents.js";
14
14
  import { resolveWorkspaceRoot } from "../../config.js";
15
15
  import { guardWorkspacePath } from "../../security/pathGuard.js";
16
+ import { buildGitEnvironment, redactProcessOutput, resolveTrustedExecutable } from "../../runner/processSecurity.js";
16
17
  import { config, errorMessage, sendJson } from "../shared.js";
17
18
 
18
19
  export function handleWorkspace(res: ServerResponse): void {
19
20
  let workspaceRoot: string | null = null;
20
21
  let directories: string[] = [];
22
+ let internalDirectories: string[] = [];
21
23
  let agents: AgentAvailability[] = [];
22
24
  let configSummary: { toolProfile: string | null; allowedTestCommandsCount: number; enableDirectProfile: boolean } | null = null;
23
25
 
@@ -28,10 +30,16 @@ export function handleWorkspace(res: ServerResponse): void {
28
30
  }
29
31
  if (workspaceRoot) {
30
32
  try {
31
- directories = readdirSync(workspaceRoot, { withFileTypes: true })
32
- .filter((e) => e.isDirectory())
33
- .map((e) => e.name)
34
- .sort();
33
+ const entries = readdirSync(workspaceRoot, { withFileTypes: true }).filter((e) => e.isDirectory());
34
+ for (const entry of entries) {
35
+ const child = join(workspaceRoot, entry.name);
36
+ const isInternal = entry.name.startsWith(".") || entry.name.startsWith("_");
37
+ const isGitRepo = existsSync(join(child, ".git"));
38
+ if (isGitRepo && !isInternal) directories.push(entry.name);
39
+ else internalDirectories.push(entry.name);
40
+ }
41
+ directories.sort();
42
+ internalDirectories.sort();
35
43
  } catch {
36
44
  directories = [];
37
45
  }
@@ -50,7 +58,7 @@ export function handleWorkspace(res: ServerResponse): void {
50
58
  } catch {
51
59
  configSummary = null;
52
60
  }
53
- sendJson(res, 200, { workspace_root: workspaceRoot, directories, agents, config: configSummary });
61
+ sendJson(res, 200, { workspace_root: workspaceRoot, directories, internal_directories: internalDirectories, agents, config: configSummary });
54
62
  }
55
63
 
56
64
  interface WorkspaceRepoEntry {
@@ -130,8 +138,13 @@ export function handleWorkspaceRepoStatus(res: ServerResponse, repoParam: string
130
138
  return;
131
139
  }
132
140
 
133
- // Reject obvious traversal in the raw parameter before resolving.
134
- if (repoParam.includes("\0") || repoParam.includes("..")) {
141
+ // Reject parent traversal segments before resolving without rejecting
142
+ // otherwise valid names such as "release..candidate".
143
+ const hasParentTraversal = repoParam
144
+ .replace(/\\/g, "/")
145
+ .split("/")
146
+ .some((segment) => segment === "..");
147
+ if (repoParam.includes("\0") || hasParentTraversal) {
135
148
  sendJson(res, 400, { error: "Invalid repo path: traversal segments are not allowed" });
136
149
  return;
137
150
  }
@@ -151,31 +164,28 @@ export function handleWorkspaceRepoStatus(res: ServerResponse, repoParam: string
151
164
  return;
152
165
  }
153
166
 
154
- // Only `git status --short` is permitted; no arbitrary git subcommand.
167
+ // Only porcelain status is permitted; no arbitrary git subcommand.
155
168
  // Timeout guards against a hung git prompt (e.g. credential dialog).
169
+ const env = buildGitEnvironment(repoAbs);
170
+ let git: string;
171
+ try {
172
+ git = resolveTrustedExecutable("git", repoAbs, { pathValue: env.PATH });
173
+ } catch (err) {
174
+ sendRepoStatusFailure(res, repoParam, repoAbs, err);
175
+ return;
176
+ }
156
177
  execFile(
157
- "git",
158
- ["status", "--short"],
159
- { cwd: repoAbs, maxBuffer: 1024 * 1024, timeout: 8000, windowsHide: true, encoding: "utf-8" },
178
+ git,
179
+ ["status", "--porcelain=v1"],
180
+ { cwd: repoAbs, env, maxBuffer: 1024 * 1024, timeout: 8000, windowsHide: true, encoding: "utf-8" },
160
181
  (err, stdout, stderr) => {
161
182
  if (err) {
162
183
  // Not a git repo, git missing, or git errored — return a structured
163
184
  // failure rather than 500 so the UI can render it gracefully.
164
- sendJson(res, 200, {
165
- repo_path: repoParam,
166
- resolved_repo_path: repoAbs,
167
- is_git_repo: false,
168
- changed_files_count: 0,
169
- untracked_count: 0,
170
- modified_count: 0,
171
- is_clean: true,
172
- short_status: "",
173
- error: errorMessage(err),
174
- stderr: stderr ? String(stderr).slice(0, 500) : "",
175
- });
185
+ sendRepoStatusFailure(res, repoParam, repoAbs, err, stderr);
176
186
  return;
177
187
  }
178
- const text = String(stdout);
188
+ const text = redactProcessOutput(String(stdout));
179
189
  const lines = text.split(/\r?\n/).filter((l) => l.length > 0);
180
190
  let modified = 0;
181
191
  let untracked = 0;
@@ -201,3 +211,24 @@ export function handleWorkspaceRepoStatus(res: ServerResponse, repoParam: string
201
211
  sendJson(res, 500, { error: errorMessage(err) });
202
212
  }
203
213
  }
214
+
215
+ function sendRepoStatusFailure(
216
+ res: ServerResponse,
217
+ repoParam: string,
218
+ repoAbs: string,
219
+ error: unknown,
220
+ stderr: unknown = "",
221
+ ): void {
222
+ sendJson(res, 200, {
223
+ repo_path: repoParam,
224
+ resolved_repo_path: repoAbs,
225
+ is_git_repo: false,
226
+ changed_files_count: 0,
227
+ untracked_count: 0,
228
+ modified_count: 0,
229
+ is_clean: true,
230
+ short_status: "",
231
+ error: redactProcessOutput(errorMessage(error)),
232
+ stderr: redactProcessOutput(String(stderr || "")).slice(0, 500),
233
+ });
234
+ }
@@ -7,15 +7,19 @@
7
7
  * file mtime, id validation). These are pure helpers consumed by the route
8
8
  * modules; they hold no HTTP-layer concerns beyond reusing `sendJson`.
9
9
  */
10
- import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
10
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, unlinkSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
  import { get as httpGet } from "node:http";
13
- import { listTasks, type TaskEntry } from "../tools/listTasks.js";
14
- import { listAgents, type AgentAvailability } from "../tools/listAgents.js";
13
+ import { listTasks, type TaskEntry } from "../tools/tasks/listTasks.js";
14
+ import { listAgents, type AgentAvailability } from "../tools/workspace/listAgents.js";
15
15
  import { readWatcherStatus, type WatcherStatusSnapshot } from "../watcherStatus.js";
16
16
  import { resolveWorkspaceRoot } from "../config.js";
17
17
  import { PATCHWARDEN_VERSION } from "../version.js";
18
+ import { isValidDirectSessionId as isValidStoredDirectSessionId } from "../direct/directSessionStore.js";
19
+ import { atomicWriteFileSync, atomicWriteJsonFileSync } from "../utils/atomicFile.js";
20
+ import { withFileLockSync } from "../utils/lockedJsonFile.js";
18
21
  import { logger } from "../logging.js";
22
+ import { TERMINAL_TASK_STATUSES as SHARED_TERMINAL_TASK_STATUSES } from "../tools/tasks/taskStates.js";
19
23
  import {
20
24
  config,
21
25
  controlCenterEventsPath,
@@ -201,16 +205,7 @@ export interface StaleClassification {
201
205
  stale_reasons: string[];
202
206
  }
203
207
 
204
- export const TERMINAL_TASK_STATUSES = new Set([
205
- "done",
206
- "done_by_agent",
207
- "failed",
208
- "failed_verification",
209
- "failed_scope_violation",
210
- "failed_policy_violation",
211
- "canceled",
212
- "timeout",
213
- ]);
208
+ export const TERMINAL_TASK_STATUSES = SHARED_TERMINAL_TASK_STATUSES;
214
209
 
215
210
  /**
216
211
  * Classify a task as stale based on Phase 2 rules:
@@ -323,7 +318,7 @@ export function writeStatusFile(): void {
323
318
  };
324
319
  try {
325
320
  mkdirSync(getControlCenterLogDir(), { recursive: true });
326
- writeFileSync(controlCenterStatusPath, JSON.stringify(status, null, 2), "utf-8");
321
+ atomicWriteJsonFileSync(controlCenterStatusPath, status);
327
322
  } catch (err) {
328
323
  logger.error("[control-center] Failed to write status file", { error: errorMessage(err) });
329
324
  }
@@ -359,29 +354,24 @@ export function recordEvent(type: string, payload?: Record<string, unknown>): vo
359
354
  };
360
355
  try {
361
356
  mkdirSync(getControlCenterLogDir(), { recursive: true });
362
- appendFileSync(controlCenterEventsPath, JSON.stringify(event) + "\n", "utf-8");
357
+ withFileLockSync(controlCenterEventsPath, () => {
358
+ appendFileSync(controlCenterEventsPath, JSON.stringify(event) + "\n", "utf-8");
359
+ const stat = statSync(controlCenterEventsPath);
360
+ if (stat.size > 512 * 1024) trimEventsFileUnlocked();
361
+ });
363
362
  } catch (err) {
364
363
  logger.error("[control-center] Failed to write event", { error: errorMessage(err) });
365
364
  }
366
- // Lazy trim: only when the file grows well past the cap.
367
- try {
368
- const stat = statSync(controlCenterEventsPath);
369
- if (stat.size > 512 * 1024) {
370
- trimEventsFile();
371
- }
372
- } catch {
373
- /* ignore */
374
- }
375
365
  }
376
366
 
377
- function trimEventsFile(): void {
367
+ function trimEventsFileUnlocked(): void {
378
368
  try {
379
369
  if (!existsSync(controlCenterEventsPath)) return;
380
370
  const raw = readFileSync(controlCenterEventsPath, "utf-8");
381
371
  const lines = raw.split(/\r?\n/).filter((l) => l.length > 0);
382
372
  if (lines.length <= MAX_EVENT_LINES) return;
383
373
  const trimmed = lines.slice(lines.length - MAX_EVENT_LINES);
384
- writeFileSync(controlCenterEventsPath, trimmed.join("\n") + "\n", "utf-8");
374
+ atomicWriteFileSync(controlCenterEventsPath, trimmed.join("\n") + "\n");
385
375
  } catch (err) {
386
376
  logger.error("[control-center] Failed to trim events file", { error: errorMessage(err) });
387
377
  }
@@ -412,13 +402,52 @@ export function readEvents(limit: number): ControlCenterEvent[] {
412
402
  export function readHiddenStaleIds(): string[] {
413
403
  const p = join(getControlCenterLogDir(), "hidden-stale-ids.json");
414
404
  if (!existsSync(p)) return [];
415
- return readJsonFileSafe<string[]>(p) || [];
405
+ return readStoredStringArray(p);
406
+ }
407
+
408
+ export function reconstructTaskEntry(
409
+ taskId: string,
410
+ statusData: Record<string, unknown>,
411
+ runtimeData: Record<string, unknown> = {},
412
+ watcher: WatcherStatusSnapshot = readWatcherStatusSafe(),
413
+ ): TaskEntry {
414
+ const validAcceptance = new Set(["pending", "accepted", "rejected", "needs_fix", "blocked"]);
415
+ const taskStatus = String(statusData.status || "pending");
416
+ const acceptanceStatus = taskStatus === "done_by_agent"
417
+ ? (typeof statusData.acceptance_status === "string" && validAcceptance.has(statusData.acceptance_status)
418
+ ? statusData.acceptance_status
419
+ : "pending")
420
+ : null;
421
+ return {
422
+ task_id: taskId,
423
+ plan_id: String(statusData.plan_id || ""),
424
+ title: "",
425
+ agent: String(statusData.agent || ""),
426
+ status: taskStatus as TaskEntry["status"],
427
+ phase: String(runtimeData.phase || statusData.phase || "queued") as TaskEntry["phase"],
428
+ acceptance_status: acceptanceStatus as TaskEntry["acceptance_status"],
429
+ created_at: String(statusData.created_at || ""),
430
+ updated_at: String(statusData.updated_at || ""),
431
+ workspace_root: String(statusData.workspace_root || config.workspaceRoot),
432
+ repo_path: String(statusData.repo_path || "."),
433
+ resolved_repo_path: String(statusData.resolved_repo_path || statusData.repo_path || config.workspaceRoot),
434
+ test_command: String(statusData.test_command || ""),
435
+ verify_commands: Array.isArray(statusData.verify_commands)
436
+ ? statusData.verify_commands.filter((command): command is string => typeof command === "string")
437
+ : [],
438
+ error: statusData.error ? String(statusData.error) : null,
439
+ last_heartbeat_at: String(runtimeData.last_heartbeat_at || statusData.last_heartbeat_at || statusData.updated_at || ""),
440
+ current_command: runtimeData.current_command === undefined ? null : String(runtimeData.current_command || "") || null,
441
+ timeout_seconds: Number(statusData.timeout_seconds) || config.defaultTaskTimeoutSeconds,
442
+ pending_reason: null,
443
+ watcher_status: watcher.status,
444
+ };
416
445
  }
417
446
 
418
447
  export function writeHiddenStaleIds(ids: string[]): void {
419
448
  const dir = getControlCenterLogDir();
420
449
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
421
- writeFileSync(join(dir, "hidden-stale-ids.json"), JSON.stringify(ids, null, 2));
450
+ atomicWriteJsonFileSync(join(dir, "hidden-stale-ids.json"), normalizeStoredIds(ids));
422
451
  }
423
452
 
424
453
  // ── Hidden direct session IDs (local UI state) ────────────────────
@@ -426,32 +455,31 @@ export function writeHiddenStaleIds(ids: string[]): void {
426
455
  export function readHiddenDirectSessionIds(): string[] {
427
456
  const p = join(getControlCenterLogDir(), "hidden-direct-session-ids.json");
428
457
  if (!existsSync(p)) return [];
429
- return readJsonFileSafe<string[]>(p) || [];
458
+ return readStoredStringArray(p);
430
459
  }
431
460
 
432
461
  export function writeHiddenDirectSessionIds(ids: string[]): void {
433
462
  const dir = getControlCenterLogDir();
434
463
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
435
- writeFileSync(join(dir, "hidden-direct-session-ids.json"), JSON.stringify(ids, null, 2));
464
+ atomicWriteJsonFileSync(join(dir, "hidden-direct-session-ids.json"), normalizeStoredIds(ids));
436
465
  }
437
466
 
438
467
  export function isValidDirectSessionId(sessionId: string): boolean {
439
- // Allow alphanumeric, dash, underscore only — reject path traversal and NUL.
440
- return (
441
- sessionId.length > 0 &&
442
- /^[A-Za-z0-9_-]+$/.test(sessionId) &&
443
- !sessionId.includes("\0")
444
- );
468
+ return isValidStoredDirectSessionId(sessionId);
445
469
  }
446
470
 
447
471
  export function isValidTaskId(taskId: string): boolean {
448
- return !(
449
- taskId === "." ||
450
- taskId === ".." ||
451
- taskId.includes("/") ||
452
- taskId.includes("\\") ||
453
- taskId.includes("\0")
454
- );
472
+ return /^[A-Za-z0-9][A-Za-z0-9_-]{0,159}$/.test(taskId);
473
+ }
474
+
475
+ function readStoredStringArray(path: string): string[] {
476
+ const value = readJsonFileSafe<unknown>(path);
477
+ if (!Array.isArray(value)) return [];
478
+ return normalizeStoredIds(value);
479
+ }
480
+
481
+ function normalizeStoredIds(value: unknown[]): string[] {
482
+ return [...new Set(value.filter((entry): entry is string => typeof entry === "string" && entry.length > 0))];
455
483
  }
456
484
 
457
485
  // ── Small shared helpers ──────────────────────────────────────────