patchwarden 1.5.1 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (573) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +152 -25
  3. package/README.md +120 -23
  4. package/dist/assessments/agentAssessor.js +21 -12
  5. package/dist/assessments/assessmentStore.js +42 -34
  6. package/dist/config.d.ts +15 -0
  7. package/dist/config.js +66 -13
  8. package/dist/control/fileManager.d.ts +2 -0
  9. package/dist/control/fileManager.js +39 -0
  10. package/dist/control/middleware/auth.d.ts +6 -0
  11. package/dist/control/middleware/auth.js +14 -5
  12. package/dist/control/middleware/static.js +16 -3
  13. package/dist/control/routeTable.d.ts +24 -0
  14. package/dist/control/routeTable.js +263 -0
  15. package/dist/control/routes/audit.js +17 -7
  16. package/dist/control/routes/evidence.js +1 -1
  17. package/dist/control/routes/lineage.js +1 -1
  18. package/dist/control/routes/process.d.ts +7 -0
  19. package/dist/control/routes/process.js +329 -42
  20. package/dist/control/routes/sessions.d.ts +9 -1
  21. package/dist/control/routes/sessions.js +84 -21
  22. package/dist/control/routes/status.d.ts +46 -1
  23. package/dist/control/routes/status.js +72 -15
  24. package/dist/control/routes/taskActions.js +24 -60
  25. package/dist/control/routes/tasks.d.ts +4 -0
  26. package/dist/control/routes/tasks.js +61 -62
  27. package/dist/control/routes/workspace.js +50 -23
  28. package/dist/control/runtime.d.ts +3 -2
  29. package/dist/control/runtime.js +63 -30
  30. package/dist/control/server.js +36 -365
  31. package/dist/control/shared.d.ts +25 -1
  32. package/dist/control/shared.js +64 -11
  33. package/dist/direct/directAudit.js +234 -225
  34. package/dist/direct/directGuards.d.ts +2 -1
  35. package/dist/direct/directGuards.js +42 -29
  36. package/dist/direct/directPatch.d.ts +6 -1
  37. package/dist/direct/directPatch.js +55 -8
  38. package/dist/direct/directSessionStore.d.ts +16 -6
  39. package/dist/direct/directSessionStore.js +127 -35
  40. package/dist/direct/directVerification.js +8 -16
  41. package/dist/doctor.js +62 -52
  42. package/dist/goal/acceptanceEngine.d.ts +1 -1
  43. package/dist/goal/goalProgress.js +75 -69
  44. package/dist/goal/goalReport.js +5 -12
  45. package/dist/goal/goalStatus.d.ts +2 -0
  46. package/dist/goal/goalStatus.js +10 -3
  47. package/dist/goal/goalStore.d.ts +9 -2
  48. package/dist/goal/goalStore.js +152 -45
  49. package/dist/goal/handoffExport.js +6 -6
  50. package/dist/goal/specKitImport.js +97 -96
  51. package/dist/goal/subgoalSync.js +11 -9
  52. package/dist/goal/worktreeManager.d.ts +2 -0
  53. package/dist/goal/worktreeManager.js +209 -136
  54. package/dist/httpServer.js +111 -32
  55. package/dist/logging.js +5 -3
  56. package/dist/policy/projectPolicy.js +5 -2
  57. package/dist/release/releaseGate.d.ts +1 -1
  58. package/dist/release/releaseGate.js +78 -25
  59. package/dist/runner/agentInvocation.d.ts +14 -0
  60. package/dist/runner/agentInvocation.js +96 -4
  61. package/dist/runner/changeCapture.d.ts +13 -5
  62. package/dist/runner/changeCapture.js +243 -61
  63. package/dist/runner/postTaskCleanup.js +48 -19
  64. package/dist/runner/processSecurity.d.ts +56 -0
  65. package/dist/runner/processSecurity.js +373 -0
  66. package/dist/runner/runTask.d.ts +1 -1
  67. package/dist/runner/runTask.js +115 -87
  68. package/dist/runner/simpleProcess.d.ts +3 -0
  69. package/dist/runner/simpleProcess.js +50 -36
  70. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  71. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  72. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
  73. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  74. package/dist/runner/taskStatusStore.d.ts +11 -0
  75. package/dist/runner/taskStatusStore.js +24 -0
  76. package/dist/runner/watch.d.ts +14 -1
  77. package/dist/runner/watch.js +256 -48
  78. package/dist/security/contentRedaction.js +2 -2
  79. package/dist/security/discoveryTokenStore.d.ts +3 -2
  80. package/dist/security/discoveryTokenStore.js +17 -2
  81. package/dist/security/loopbackHost.d.ts +2 -0
  82. package/dist/security/loopbackHost.js +16 -0
  83. package/dist/security/pathGuard.js +32 -17
  84. package/dist/security/planGuard.js +2 -1
  85. package/dist/security/runtimeGuard.js +9 -6
  86. package/dist/security/secretComparison.d.ts +3 -0
  87. package/dist/security/secretComparison.js +11 -0
  88. package/dist/security/sensitiveGuard.d.ts +5 -0
  89. package/dist/security/sensitiveGuard.js +27 -8
  90. package/dist/security/toolInvocationGuard.d.ts +2 -2
  91. package/dist/security/toolInvocationGuard.js +1 -1
  92. package/dist/security/workspaceRootGuard.d.ts +8 -0
  93. package/dist/security/workspaceRootGuard.js +47 -0
  94. package/dist/smoke-test.js +24 -23
  95. package/dist/tools/catalog/index.d.ts +4 -0
  96. package/dist/tools/catalog/index.js +4 -0
  97. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  98. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  99. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +6 -15
  100. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  101. package/dist/tools/definitions/toolDefs.js +1177 -0
  102. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  103. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  104. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +1 -1
  105. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +99 -29
  106. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
  107. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +7 -7
  108. package/dist/tools/diagnostics/index.d.ts +6 -0
  109. package/dist/tools/diagnostics/index.js +6 -0
  110. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  111. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  112. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +12 -8
  113. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +23 -13
  114. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  115. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  116. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +6 -6
  117. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +1 -1
  118. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  119. package/dist/tools/direct/index.d.ts +3 -0
  120. package/dist/tools/direct/index.js +3 -0
  121. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  122. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  123. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  124. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  125. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  126. package/dist/tools/discovery/index.d.ts +3 -0
  127. package/dist/tools/discovery/index.js +3 -0
  128. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  129. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  130. package/dist/tools/dispatch/coreDispatch.js +32 -31
  131. package/dist/tools/dispatch/diagnosticDispatch.d.ts +3 -5
  132. package/dist/tools/dispatch/diagnosticDispatch.js +70 -68
  133. package/dist/tools/dispatch/directDispatch.js +11 -10
  134. package/dist/tools/dispatch/goalDispatch.js +5 -4
  135. package/dist/tools/dispatch/releaseDispatch.js +3 -2
  136. package/dist/tools/dispatch/validation.d.ts +7 -0
  137. package/dist/tools/dispatch/validation.js +71 -0
  138. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  139. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +1 -1
  140. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  141. package/dist/tools/goals/index.d.ts +3 -0
  142. package/dist/tools/goals/index.js +3 -0
  143. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  144. package/dist/tools/registry.d.ts +7 -11
  145. package/dist/tools/registry.js +17 -1178
  146. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  147. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  148. package/dist/tools/release/index.d.ts +2 -0
  149. package/dist/tools/release/index.js +2 -0
  150. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  151. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  152. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  153. package/dist/tools/tasks/cancelTask.js +85 -0
  154. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +10 -2
  155. package/dist/tools/{createTask.js → tasks/createTask.js} +42 -27
  156. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  157. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
  158. package/dist/tools/{evidencePack.js → tasks/evidencePack.js} +34 -22
  159. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  160. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  161. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
  162. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  163. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
  164. package/dist/tools/tasks/index.d.ts +18 -0
  165. package/dist/tools/tasks/index.js +18 -0
  166. package/dist/tools/tasks/killTask.d.ts +1 -0
  167. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  168. package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
  169. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
  170. package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
  171. package/dist/tools/tasks/retryTask.js +60 -0
  172. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  173. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +58 -30
  174. package/dist/tools/tasks/runVerification.js +38 -0
  175. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  176. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  177. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  178. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  179. package/dist/tools/workspace/applyPatch.js +53 -0
  180. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  181. package/dist/tools/workspace/index.d.ts +10 -0
  182. package/dist/tools/workspace/index.js +10 -0
  183. package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
  184. package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
  185. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  186. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  187. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  188. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  189. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  190. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  191. package/dist/tools/workspace/syncFile.js +134 -0
  192. package/dist/utils/atomicFile.d.ts +7 -0
  193. package/dist/utils/atomicFile.js +48 -0
  194. package/dist/utils/boundedFile.d.ts +8 -0
  195. package/dist/utils/boundedFile.js +93 -0
  196. package/dist/utils/lockedJsonFile.d.ts +15 -0
  197. package/dist/utils/lockedJsonFile.js +224 -0
  198. package/dist/utils/platform.d.ts +11 -0
  199. package/dist/utils/platform.js +26 -0
  200. package/dist/utils/stableJson.d.ts +2 -0
  201. package/dist/utils/stableJson.js +12 -0
  202. package/dist/version.d.ts +3 -2
  203. package/dist/version.js +3 -2
  204. package/dist/watcherStatus.d.ts +2 -2
  205. package/dist/watcherStatus.js +18 -0
  206. package/docs/CODE_WIKI.md +808 -143
  207. package/docs/architecture.md +15 -55
  208. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  209. package/docs/control-center/README.md +4 -4
  210. package/docs/dashboard-overview.md +4 -4
  211. package/docs/desktop-app.md +175 -0
  212. package/docs/evidence-pack-schema.md +3 -3
  213. package/docs/open-source-application.md +17 -11
  214. package/docs/release-checklist.md +1 -1
  215. package/docs/release-evidence.md +39 -29
  216. package/docs/task-safe-review-workflow.md +5 -5
  217. package/docs/threat-model.md +19 -1
  218. package/examples/config.example.json +4 -2
  219. package/package.json +27 -10
  220. package/scripts/README.md +15 -6
  221. package/scripts/build.js +11 -0
  222. package/scripts/checks/brand-check.js +3 -0
  223. package/scripts/checks/build-output-check.js +37 -0
  224. package/scripts/checks/control-center-smoke.js +91 -6
  225. package/scripts/checks/control-smoke.js +19 -8
  226. package/scripts/checks/http-mcp-smoke.js +82 -4
  227. package/scripts/checks/lifecycle-smoke.js +10 -10
  228. package/scripts/checks/mcp-manifest-check.js +1 -1
  229. package/scripts/checks/mcp-smoke.js +27 -3
  230. package/scripts/checks/package-manifest-check.js +20 -7
  231. package/scripts/checks/unit-tests.js +3 -5
  232. package/scripts/checks/watcher-supervisor-smoke.js +9 -0
  233. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  234. package/scripts/control/manage-patchwarden.ps1 +69 -4
  235. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  236. package/scripts/control/run-background-supervisor.ps1 +41 -0
  237. package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
  238. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  239. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  240. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  241. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  242. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  243. package/scripts/lib/clean-generated-output.js +11 -0
  244. package/scripts/lib/file-discovery.js +12 -0
  245. package/scripts/release/desktop-preflight.js +211 -0
  246. package/scripts/release/pack-clean.js +59 -14
  247. package/src/assessments/agentAssessor.ts +26 -12
  248. package/src/assessments/assessmentStore.ts +52 -40
  249. package/src/config.ts +70 -13
  250. package/src/control/fileManager.ts +40 -0
  251. package/src/control/middleware/auth.ts +22 -4
  252. package/src/control/middleware/static.ts +16 -3
  253. package/src/control/routeTable.ts +301 -0
  254. package/src/control/routes/audit.ts +14 -6
  255. package/src/control/routes/evidence.ts +1 -1
  256. package/src/control/routes/lineage.ts +1 -1
  257. package/src/control/routes/process.ts +341 -40
  258. package/src/control/routes/sessions.ts +88 -25
  259. package/src/control/routes/status.ts +97 -17
  260. package/src/control/routes/taskActions.ts +24 -58
  261. package/src/control/routes/tasks.ts +66 -58
  262. package/src/control/routes/workspace.ts +56 -25
  263. package/src/control/runtime.ts +69 -33
  264. package/src/control/server.ts +36 -342
  265. package/src/control/shared.ts +77 -11
  266. package/src/direct/directAudit.ts +17 -9
  267. package/src/direct/directGuards.ts +72 -44
  268. package/src/direct/directPatch.ts +95 -8
  269. package/src/direct/directSessionStore.ts +211 -55
  270. package/src/direct/directVerification.ts +9 -23
  271. package/src/doctor.ts +73 -59
  272. package/src/goal/acceptanceEngine.ts +1 -1
  273. package/src/goal/goalProgress.ts +17 -13
  274. package/src/goal/goalReport.ts +5 -13
  275. package/src/goal/goalStatus.ts +19 -3
  276. package/src/goal/goalStore.ts +241 -54
  277. package/src/goal/handoffExport.ts +6 -6
  278. package/src/goal/specKitImport.ts +12 -12
  279. package/src/goal/subgoalSync.ts +11 -8
  280. package/src/goal/worktreeManager.ts +139 -22
  281. package/src/httpServer.ts +113 -30
  282. package/src/logging.ts +10 -3
  283. package/src/policy/projectPolicy.ts +5 -2
  284. package/src/release/releaseGate.ts +74 -27
  285. package/src/runner/agentInvocation.ts +126 -4
  286. package/src/runner/changeCapture.ts +291 -57
  287. package/src/runner/postTaskCleanup.ts +41 -17
  288. package/src/runner/processSecurity.ts +433 -0
  289. package/src/runner/runTask.ts +138 -91
  290. package/src/runner/simpleProcess.ts +66 -31
  291. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  292. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
  293. package/src/runner/taskStatusStore.ts +54 -0
  294. package/src/runner/watch.ts +281 -51
  295. package/src/security/contentRedaction.ts +2 -2
  296. package/src/security/discoveryTokenStore.ts +18 -4
  297. package/src/security/loopbackHost.ts +19 -0
  298. package/src/security/pathGuard.ts +35 -16
  299. package/src/security/planGuard.ts +2 -1
  300. package/src/security/runtimeGuard.ts +9 -6
  301. package/src/security/secretComparison.ts +13 -0
  302. package/src/security/sensitiveGuard.ts +27 -8
  303. package/src/security/toolInvocationGuard.ts +2 -2
  304. package/src/security/workspaceRootGuard.ts +53 -0
  305. package/src/smoke-test.ts +25 -24
  306. package/src/tools/catalog/index.ts +4 -0
  307. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  308. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +7 -16
  309. package/src/tools/definitions/toolDefs.ts +1264 -0
  310. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  311. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +99 -32
  312. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +8 -8
  313. package/src/tools/diagnostics/index.ts +6 -0
  314. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  315. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +27 -17
  316. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  317. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +6 -6
  318. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +7 -5
  319. package/src/tools/direct/index.ts +3 -0
  320. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  321. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  322. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  323. package/src/tools/discovery/index.ts +3 -0
  324. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  325. package/src/tools/dispatch/coreDispatch.ts +36 -31
  326. package/src/tools/dispatch/diagnosticDispatch.ts +26 -21
  327. package/src/tools/dispatch/directDispatch.ts +11 -10
  328. package/src/tools/dispatch/goalDispatch.ts +5 -4
  329. package/src/tools/dispatch/releaseDispatch.ts +3 -2
  330. package/src/tools/dispatch/validation.ts +78 -0
  331. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  332. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +41 -26
  333. package/src/tools/goals/index.ts +3 -0
  334. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  335. package/src/tools/registry.ts +17 -1261
  336. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  337. package/src/tools/release/index.ts +2 -0
  338. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  339. package/src/tools/tasks/cancelTask.ts +109 -0
  340. package/src/tools/{createTask.ts → tasks/createTask.ts} +49 -26
  341. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
  342. package/src/tools/{evidencePack.ts → tasks/evidencePack.ts} +36 -22
  343. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  344. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
  345. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  346. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
  347. package/src/tools/tasks/index.ts +18 -0
  348. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
  349. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
  350. package/src/tools/tasks/retryTask.ts +69 -0
  351. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +69 -34
  352. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  353. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  354. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  355. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  356. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  357. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  358. package/src/tools/workspace/index.ts +10 -0
  359. package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
  360. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  361. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  362. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  363. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  364. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  365. package/src/tools/workspace/syncFile.ts +292 -0
  366. package/src/utils/atomicFile.ts +58 -0
  367. package/src/utils/boundedFile.ts +116 -0
  368. package/src/utils/lockedJsonFile.ts +251 -0
  369. package/src/utils/platform.ts +31 -0
  370. package/src/utils/stableJson.ts +12 -0
  371. package/src/version.ts +3 -2
  372. package/src/watcherStatus.ts +19 -1
  373. package/ui/desktop-bootstrap.js +11 -0
  374. package/ui/desktop-bridge.js +36 -0
  375. package/ui/desktop.css +160 -0
  376. package/ui/getting-started.js +93 -0
  377. package/ui/i18n.js +484 -0
  378. package/ui/log-parser.js +30 -0
  379. package/ui/pages/audit.html +12 -0
  380. package/ui/pages/dashboard.html +90 -147
  381. package/ui/pages/direct-sessions.html +123 -26
  382. package/ui/pages/getting-started.html +43 -0
  383. package/ui/pages/logs.html +105 -22
  384. package/ui/pages/settings.html +101 -0
  385. package/ui/pages/task-detail.html +12 -0
  386. package/ui/pages/tasks.html +54 -8
  387. package/ui/pages/workspace.html +38 -1
  388. package/ui/settings.js +245 -0
  389. package/PatchWarden-Control.cmd +0 -6
  390. package/Restart-PatchWarden-Control.cmd +0 -6
  391. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  392. package/dist/test/unit/acceptance-engine.test.js +0 -228
  393. package/dist/test/unit/agent-router.test.d.ts +0 -1
  394. package/dist/test/unit/agent-router.test.js +0 -287
  395. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  396. package/dist/test/unit/android-doctor.test.js +0 -118
  397. package/dist/test/unit/apply-patch.test.d.ts +0 -1
  398. package/dist/test/unit/apply-patch.test.js +0 -225
  399. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  400. package/dist/test/unit/audit-checks.test.js +0 -350
  401. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  402. package/dist/test/unit/chinese-path.test.js +0 -91
  403. package/dist/test/unit/command-guard.test.d.ts +0 -1
  404. package/dist/test/unit/command-guard.test.js +0 -160
  405. package/dist/test/unit/create-task.test.d.ts +0 -1
  406. package/dist/test/unit/create-task.test.js +0 -197
  407. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  408. package/dist/test/unit/diagnose-task.test.js +0 -457
  409. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  410. package/dist/test/unit/direct-guards.test.js +0 -328
  411. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  412. package/dist/test/unit/discovery-token-store.test.js +0 -139
  413. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  414. package/dist/test/unit/evidence-pack.test.js +0 -224
  415. package/dist/test/unit/get-task-status.test.d.ts +0 -1
  416. package/dist/test/unit/get-task-status.test.js +0 -174
  417. package/dist/test/unit/get-task-summary.test.d.ts +0 -1
  418. package/dist/test/unit/get-task-summary.test.js +0 -146
  419. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  420. package/dist/test/unit/goal-graph.test.js +0 -298
  421. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  422. package/dist/test/unit/goal-progress.test.js +0 -381
  423. package/dist/test/unit/goal-report.test.d.ts +0 -1
  424. package/dist/test/unit/goal-report.test.js +0 -159
  425. package/dist/test/unit/goal-status.test.d.ts +0 -1
  426. package/dist/test/unit/goal-status.test.js +0 -215
  427. package/dist/test/unit/goal-store.test.d.ts +0 -1
  428. package/dist/test/unit/goal-store.test.js +0 -253
  429. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  430. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  431. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  432. package/dist/test/unit/goal-tools-registry.test.js +0 -192
  433. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  434. package/dist/test/unit/handoff-export.test.js +0 -263
  435. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  436. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  437. package/dist/test/unit/logging.test.d.ts +0 -1
  438. package/dist/test/unit/logging.test.js +0 -397
  439. package/dist/test/unit/path-guard.test.d.ts +0 -1
  440. package/dist/test/unit/path-guard.test.js +0 -133
  441. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  442. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  443. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  444. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  445. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  446. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  447. package/dist/test/unit/release-gate.test.d.ts +0 -1
  448. package/dist/test/unit/release-gate.test.js +0 -242
  449. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  450. package/dist/test/unit/run-task-loop.test.js +0 -380
  451. package/dist/test/unit/safe-status.test.d.ts +0 -1
  452. package/dist/test/unit/safe-status.test.js +0 -165
  453. package/dist/test/unit/safe-views.test.d.ts +0 -1
  454. package/dist/test/unit/safe-views.test.js +0 -171
  455. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  456. package/dist/test/unit/schema-drift-check.test.js +0 -176
  457. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  458. package/dist/test/unit/sensitive-guard.test.js +0 -104
  459. package/dist/test/unit/spec-kit-import.test.d.ts +0 -1
  460. package/dist/test/unit/spec-kit-import.test.js +0 -341
  461. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  462. package/dist/test/unit/subgoal-sync.test.js +0 -183
  463. package/dist/test/unit/sync-file.test.d.ts +0 -1
  464. package/dist/test/unit/sync-file.test.js +0 -154
  465. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  466. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  467. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  468. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  469. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  470. package/dist/test/unit/toolSearch.test.js +0 -571
  471. package/dist/test/unit/wait-for-task.test.d.ts +0 -1
  472. package/dist/test/unit/wait-for-task.test.js +0 -144
  473. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  474. package/dist/test/unit/watcher-status.test.js +0 -169
  475. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  476. package/dist/test/unit/worktree-manager.test.js +0 -176
  477. package/dist/tools/applyPatch.js +0 -41
  478. package/dist/tools/cancelTask.d.ts +0 -30
  479. package/dist/tools/cancelTask.js +0 -64
  480. package/dist/tools/finalizeDirectSession.js +0 -84
  481. package/dist/tools/goalSubgoalTask.js +0 -110
  482. package/dist/tools/killTask.d.ts +0 -15
  483. package/dist/tools/retryTask.js +0 -45
  484. package/dist/tools/runVerification.js +0 -36
  485. package/dist/tools/syncFile.js +0 -65
  486. package/docs/control-center/control-center-daily-driver.md +0 -211
  487. package/docs/control-center/control-center-mvp.md +0 -205
  488. package/docs/control-center/control-center-phase2.md +0 -159
  489. package/docs/execution-plan-2026-07-09.md +0 -315
  490. package/docs/release-v0.6.0.md +0 -71
  491. package/docs/release-v0.6.1.md +0 -75
  492. package/docs/release-v0.6.4.md +0 -45
  493. package/docs/roadmap-execution-and-acceptance.md +0 -365
  494. package/src/test/unit/acceptance-engine.test.ts +0 -261
  495. package/src/test/unit/agent-router.test.ts +0 -342
  496. package/src/test/unit/android-doctor.test.ts +0 -158
  497. package/src/test/unit/apply-patch.test.ts +0 -293
  498. package/src/test/unit/audit-checks.test.ts +0 -567
  499. package/src/test/unit/chinese-path.test.ts +0 -106
  500. package/src/test/unit/command-guard.test.ts +0 -221
  501. package/src/test/unit/create-task.test.ts +0 -255
  502. package/src/test/unit/diagnose-task.test.ts +0 -544
  503. package/src/test/unit/direct-guards.test.ts +0 -467
  504. package/src/test/unit/discovery-token-store.test.ts +0 -181
  505. package/src/test/unit/evidence-pack.test.ts +0 -251
  506. package/src/test/unit/get-task-status.test.ts +0 -203
  507. package/src/test/unit/get-task-summary.test.ts +0 -173
  508. package/src/test/unit/goal-graph.test.ts +0 -347
  509. package/src/test/unit/goal-progress.test.ts +0 -538
  510. package/src/test/unit/goal-report.test.ts +0 -189
  511. package/src/test/unit/goal-status.test.ts +0 -270
  512. package/src/test/unit/goal-store.test.ts +0 -318
  513. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  514. package/src/test/unit/goal-tools-registry.test.ts +0 -245
  515. package/src/test/unit/handoff-export.test.ts +0 -295
  516. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  517. package/src/test/unit/logging.test.ts +0 -497
  518. package/src/test/unit/path-guard.test.ts +0 -180
  519. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  520. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  521. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  522. package/src/test/unit/release-gate.test.ts +0 -314
  523. package/src/test/unit/run-task-loop.test.ts +0 -425
  524. package/src/test/unit/safe-status.test.ts +0 -187
  525. package/src/test/unit/safe-views.test.ts +0 -184
  526. package/src/test/unit/schema-drift-check.test.ts +0 -260
  527. package/src/test/unit/sensitive-guard.test.ts +0 -124
  528. package/src/test/unit/spec-kit-import.test.ts +0 -429
  529. package/src/test/unit/subgoal-sync.test.ts +0 -236
  530. package/src/test/unit/sync-file.test.ts +0 -231
  531. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  532. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  533. package/src/test/unit/toolSearch.test.ts +0 -756
  534. package/src/test/unit/wait-for-task.test.ts +0 -176
  535. package/src/test/unit/watcher-status.test.ts +0 -190
  536. package/src/test/unit/worktree-manager.test.ts +0 -247
  537. package/src/tools/cancelTask.ts +0 -70
  538. package/src/tools/retryTask.ts +0 -53
  539. package/src/tools/syncFile.ts +0 -122
  540. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  541. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  542. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  543. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  544. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  545. /package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +0 -0
  546. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  547. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  548. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  549. /package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +0 -0
  550. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  551. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  552. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  553. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  554. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  555. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  556. /package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +0 -0
  557. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  558. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  559. /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
  560. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  561. /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
  562. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  563. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  564. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  565. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  566. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  567. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  568. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  569. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  570. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  571. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  572. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
  573. /package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +0 -0
@@ -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,14 +7,17 @@
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";
19
22
  import {
20
23
  config,
@@ -323,7 +326,7 @@ export function writeStatusFile(): void {
323
326
  };
324
327
  try {
325
328
  mkdirSync(getControlCenterLogDir(), { recursive: true });
326
- writeFileSync(controlCenterStatusPath, JSON.stringify(status, null, 2), "utf-8");
329
+ atomicWriteJsonFileSync(controlCenterStatusPath, status);
327
330
  } catch (err) {
328
331
  logger.error("[control-center] Failed to write status file", { error: errorMessage(err) });
329
332
  }
@@ -359,29 +362,24 @@ export function recordEvent(type: string, payload?: Record<string, unknown>): vo
359
362
  };
360
363
  try {
361
364
  mkdirSync(getControlCenterLogDir(), { recursive: true });
362
- appendFileSync(controlCenterEventsPath, JSON.stringify(event) + "\n", "utf-8");
365
+ withFileLockSync(controlCenterEventsPath, () => {
366
+ appendFileSync(controlCenterEventsPath, JSON.stringify(event) + "\n", "utf-8");
367
+ const stat = statSync(controlCenterEventsPath);
368
+ if (stat.size > 512 * 1024) trimEventsFileUnlocked();
369
+ });
363
370
  } catch (err) {
364
371
  logger.error("[control-center] Failed to write event", { error: errorMessage(err) });
365
372
  }
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
373
  }
376
374
 
377
- function trimEventsFile(): void {
375
+ function trimEventsFileUnlocked(): void {
378
376
  try {
379
377
  if (!existsSync(controlCenterEventsPath)) return;
380
378
  const raw = readFileSync(controlCenterEventsPath, "utf-8");
381
379
  const lines = raw.split(/\r?\n/).filter((l) => l.length > 0);
382
380
  if (lines.length <= MAX_EVENT_LINES) return;
383
381
  const trimmed = lines.slice(lines.length - MAX_EVENT_LINES);
384
- writeFileSync(controlCenterEventsPath, trimmed.join("\n") + "\n", "utf-8");
382
+ atomicWriteFileSync(controlCenterEventsPath, trimmed.join("\n") + "\n");
385
383
  } catch (err) {
386
384
  logger.error("[control-center] Failed to trim events file", { error: errorMessage(err) });
387
385
  }
@@ -412,13 +410,52 @@ export function readEvents(limit: number): ControlCenterEvent[] {
412
410
  export function readHiddenStaleIds(): string[] {
413
411
  const p = join(getControlCenterLogDir(), "hidden-stale-ids.json");
414
412
  if (!existsSync(p)) return [];
415
- return readJsonFileSafe<string[]>(p) || [];
413
+ return readStoredStringArray(p);
414
+ }
415
+
416
+ export function reconstructTaskEntry(
417
+ taskId: string,
418
+ statusData: Record<string, unknown>,
419
+ runtimeData: Record<string, unknown> = {},
420
+ watcher: WatcherStatusSnapshot = readWatcherStatusSafe(),
421
+ ): TaskEntry {
422
+ const validAcceptance = new Set(["pending", "accepted", "rejected", "needs_fix", "blocked"]);
423
+ const taskStatus = String(statusData.status || "pending");
424
+ const acceptanceStatus = taskStatus === "done_by_agent"
425
+ ? (typeof statusData.acceptance_status === "string" && validAcceptance.has(statusData.acceptance_status)
426
+ ? statusData.acceptance_status
427
+ : "pending")
428
+ : null;
429
+ return {
430
+ task_id: taskId,
431
+ plan_id: String(statusData.plan_id || ""),
432
+ title: "",
433
+ agent: String(statusData.agent || ""),
434
+ status: taskStatus as TaskEntry["status"],
435
+ phase: String(runtimeData.phase || statusData.phase || "queued") as TaskEntry["phase"],
436
+ acceptance_status: acceptanceStatus as TaskEntry["acceptance_status"],
437
+ created_at: String(statusData.created_at || ""),
438
+ updated_at: String(statusData.updated_at || ""),
439
+ workspace_root: String(statusData.workspace_root || config.workspaceRoot),
440
+ repo_path: String(statusData.repo_path || "."),
441
+ resolved_repo_path: String(statusData.resolved_repo_path || statusData.repo_path || config.workspaceRoot),
442
+ test_command: String(statusData.test_command || ""),
443
+ verify_commands: Array.isArray(statusData.verify_commands)
444
+ ? statusData.verify_commands.filter((command): command is string => typeof command === "string")
445
+ : [],
446
+ error: statusData.error ? String(statusData.error) : null,
447
+ last_heartbeat_at: String(runtimeData.last_heartbeat_at || statusData.last_heartbeat_at || statusData.updated_at || ""),
448
+ current_command: runtimeData.current_command === undefined ? null : String(runtimeData.current_command || "") || null,
449
+ timeout_seconds: Number(statusData.timeout_seconds) || config.defaultTaskTimeoutSeconds,
450
+ pending_reason: null,
451
+ watcher_status: watcher.status,
452
+ };
416
453
  }
417
454
 
418
455
  export function writeHiddenStaleIds(ids: string[]): void {
419
456
  const dir = getControlCenterLogDir();
420
457
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
421
- writeFileSync(join(dir, "hidden-stale-ids.json"), JSON.stringify(ids, null, 2));
458
+ atomicWriteJsonFileSync(join(dir, "hidden-stale-ids.json"), normalizeStoredIds(ids));
422
459
  }
423
460
 
424
461
  // ── Hidden direct session IDs (local UI state) ────────────────────
@@ -426,32 +463,31 @@ export function writeHiddenStaleIds(ids: string[]): void {
426
463
  export function readHiddenDirectSessionIds(): string[] {
427
464
  const p = join(getControlCenterLogDir(), "hidden-direct-session-ids.json");
428
465
  if (!existsSync(p)) return [];
429
- return readJsonFileSafe<string[]>(p) || [];
466
+ return readStoredStringArray(p);
430
467
  }
431
468
 
432
469
  export function writeHiddenDirectSessionIds(ids: string[]): void {
433
470
  const dir = getControlCenterLogDir();
434
471
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
435
- writeFileSync(join(dir, "hidden-direct-session-ids.json"), JSON.stringify(ids, null, 2));
472
+ atomicWriteJsonFileSync(join(dir, "hidden-direct-session-ids.json"), normalizeStoredIds(ids));
436
473
  }
437
474
 
438
475
  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
- );
476
+ return isValidStoredDirectSessionId(sessionId);
445
477
  }
446
478
 
447
479
  export function isValidTaskId(taskId: string): boolean {
448
- return !(
449
- taskId === "." ||
450
- taskId === ".." ||
451
- taskId.includes("/") ||
452
- taskId.includes("\\") ||
453
- taskId.includes("\0")
454
- );
480
+ return /^[A-Za-z0-9][A-Za-z0-9_-]{0,159}$/.test(taskId);
481
+ }
482
+
483
+ function readStoredStringArray(path: string): string[] {
484
+ const value = readJsonFileSafe<unknown>(path);
485
+ if (!Array.isArray(value)) return [];
486
+ return normalizeStoredIds(value);
487
+ }
488
+
489
+ function normalizeStoredIds(value: unknown[]): string[] {
490
+ return [...new Set(value.filter((entry): entry is string => typeof entry === "string" && entry.length > 0))];
455
491
  }
456
492
 
457
493
  // ── Small shared helpers ──────────────────────────────────────────