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
@@ -1,7 +1,8 @@
1
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
1
+ import { closeSync, existsSync, fstatSync, openSync, readFileSync } from "node:fs";
2
2
  import { createHash } from "node:crypto";
3
- import { dirname } from "node:path";
4
3
  import { PatchWardenError } from "../errors.js";
4
+ import { atomicWriteFileSync } from "../utils/atomicFile.js";
5
+ import { redactSensitiveContent } from "../security/contentRedaction.js";
5
6
  // ── Hash helpers ───────────────────────────────────────────────────
6
7
  export function computeFileSha256(filePath) {
7
8
  const content = readFileSync(filePath);
@@ -21,12 +22,36 @@ export function validateExpectedSha256(filePath, expectedSha256) {
21
22
  return currentHash;
22
23
  }
23
24
  // ── Patch application ──────────────────────────────────────────────
24
- export function applyPatchOperations(filePath, operations) {
25
+ export function applyPatchOperations(filePath, operations, options = {}) {
25
26
  if (!existsSync(filePath)) {
26
27
  throw new PatchWardenError("patch_anchor_not_found", `File does not exist: "${filePath}".`, "Ensure the file exists before applying a patch. Direct mode does not support creating new files.", true, { path: filePath });
27
28
  }
28
- const beforeContent = readFileSync(filePath, "utf-8");
29
- const beforeSha256 = computeContentSha256(beforeContent);
29
+ let descriptor = null;
30
+ let beforeBytes;
31
+ let existingMode;
32
+ try {
33
+ descriptor = openSync(filePath, "r");
34
+ const metadata = fstatSync(descriptor);
35
+ if (!metadata.isFile()) {
36
+ throw new PatchWardenError("direct_target_not_file", `Patch target is not a regular file: "${filePath}".`, "Choose an existing text file inside the Direct session repository.");
37
+ }
38
+ guardFileByteLimit(metadata.size, options.maxFileBytes);
39
+ existingMode = metadata.mode;
40
+ beforeBytes = readFileSync(descriptor);
41
+ }
42
+ finally {
43
+ if (descriptor !== null)
44
+ closeSync(descriptor);
45
+ }
46
+ guardFileByteLimit(beforeBytes.length, options.maxFileBytes);
47
+ const beforeSha256 = createHash("sha256").update(beforeBytes).digest("hex");
48
+ if (options.expectedSha256 && beforeSha256 !== options.expectedSha256) {
49
+ throw fileHashMismatch(options.expectedSha256, beforeSha256);
50
+ }
51
+ const beforeContent = beforeBytes.toString("utf-8");
52
+ if (!Buffer.from(beforeContent, "utf-8").equals(beforeBytes)) {
53
+ throw new PatchWardenError("unsupported_text_encoding", `File "${filePath}" is not valid UTF-8 text.`, "Use a normal task or convert the file to UTF-8 before Direct editing.", true, { path: filePath });
54
+ }
30
55
  let content = beforeContent;
31
56
  let operationsApplied = 0;
32
57
  for (const op of operations) {
@@ -34,10 +59,19 @@ export function applyPatchOperations(filePath, operations) {
34
59
  operationsApplied++;
35
60
  }
36
61
  const afterSha256 = computeContentSha256(content);
62
+ guardFileByteLimit(Buffer.byteLength(content, "utf-8"), options.maxFileBytes);
63
+ const sensitive = redactSensitiveContent(content);
64
+ if (sensitive.redacted) {
65
+ throw new PatchWardenError("sensitive_content_blocked", `Patch result contains credential-like content (${sensitive.redaction_categories.join(", ")}).`, "Remove the sensitive value and retry with placeholders or environment-variable references.", true, { path: filePath, redaction_categories: sensitive.redaction_categories });
66
+ }
37
67
  const bytesChanged = Math.abs(Buffer.byteLength(content, "utf-8") - Buffer.byteLength(beforeContent, "utf-8"));
38
- // Ensure parent directory exists (should already exist for existing files)
39
- mkdirSync(dirname(filePath), { recursive: true });
40
- writeFileSync(filePath, content, "utf-8");
68
+ if (options.revalidatePath && !samePath(options.revalidatePath(), filePath)) {
69
+ throw new PatchWardenError("direct_path_changed_during_write", `Patch target path changed while preparing "${filePath}".`, "Retry only after concurrent filesystem changes stop.", true, { path: filePath, operation: "apply_patch" });
70
+ }
71
+ const currentSha256 = computeFileSha256(filePath);
72
+ if (currentSha256 !== beforeSha256)
73
+ throw fileHashMismatch(beforeSha256, currentSha256);
74
+ atomicWriteFileSync(filePath, content, { mode: existingMode });
41
75
  return {
42
76
  before_sha256: beforeSha256,
43
77
  after_sha256: afterSha256,
@@ -45,6 +79,19 @@ export function applyPatchOperations(filePath, operations) {
45
79
  bytes_changed: bytesChanged,
46
80
  };
47
81
  }
82
+ function guardFileByteLimit(fileBytes, maxFileBytes) {
83
+ if (maxFileBytes === undefined || fileBytes <= maxFileBytes)
84
+ return;
85
+ throw new PatchWardenError("file_too_large", `File size ${fileBytes} bytes exceeds maximum ${maxFileBytes} bytes.`, "Use a normal task for larger files.", true, { file_bytes: fileBytes, max_bytes: maxFileBytes });
86
+ }
87
+ function fileHashMismatch(expectedSha256, actualSha256) {
88
+ return new PatchWardenError("file_hash_mismatch", `File hash mismatch. Expected "${expectedSha256}" but got "${actualSha256}".`, "Re-read the file to get the current sha256, then retry the patch.", true, { expected_sha256: expectedSha256, actual_sha256: actualSha256 });
89
+ }
90
+ function samePath(left, right) {
91
+ return process.platform === "win32"
92
+ ? left.toLowerCase() === right.toLowerCase()
93
+ : left === right;
94
+ }
48
95
  function applySingleOperation(content, op) {
49
96
  switch (op.type) {
50
97
  case "replace_exact":
@@ -1,9 +1,12 @@
1
+ import { PatchWardenConfig } from "../config.js";
1
2
  import { type RepoSnapshot, type ChangeArtifacts } from "../runner/changeCapture.js";
2
3
  export interface DirectSessionOperation {
3
4
  index: number;
4
5
  timestamp: string;
5
6
  path: string;
6
- before_sha256: string;
7
+ operation_type?: "patch" | "sync";
8
+ source_path?: string;
9
+ before_sha256: string | null;
7
10
  after_sha256: string;
8
11
  operations_applied: number;
9
12
  bytes_changed: number;
@@ -48,12 +51,19 @@ export interface DirectSessionCreateInput {
48
51
  snapshot: RepoSnapshot;
49
52
  }
50
53
  export declare function generateDirectSessionId(): string;
51
- export declare function createDirectSessionDir(sessionId: string): string;
52
- export declare function getDirectSessionDir(sessionId: string): string;
54
+ /**
55
+ * Direct session IDs are directory names. Keep one shared, bounded grammar so
56
+ * MCP tools and Control Center routes cannot disagree about traversal input.
57
+ */
58
+ export declare function isValidDirectSessionId(sessionId: string): boolean;
59
+ export declare function createDirectSessionDir(sessionId: string, config?: PatchWardenConfig): string;
60
+ export declare function getDirectSessionDir(sessionId: string, config?: PatchWardenConfig): string;
53
61
  export declare function createDirectSession(input: DirectSessionCreateInput): DirectSessionRecord;
54
- export declare function readDirectSession(sessionId: string): DirectSessionRecord;
62
+ export declare function readDirectSession(sessionId: string, config?: PatchWardenConfig): DirectSessionRecord;
55
63
  export declare function updateDirectSession(sessionId: string, updates: Partial<DirectSessionRecord>): DirectSessionRecord;
56
- export declare function appendDirectSessionOperation(sessionId: string, operation: DirectSessionOperation): DirectSessionRecord;
64
+ export declare function appendDirectSessionOperation(sessionId: string, operation: DirectSessionOperation, config?: PatchWardenConfig): DirectSessionRecord;
65
+ export declare function withDirectSessionMutationLock<R>(sessionId: string, action: () => R, config?: PatchWardenConfig): R;
66
+ export declare function withDirectSessionMutationLockAsync<R>(sessionId: string, action: () => Promise<R>, config?: PatchWardenConfig): Promise<R>;
57
67
  export declare function appendDirectSessionVerificationRun(sessionId: string, run: DirectSessionVerificationRun): DirectSessionRecord;
58
68
  export interface DirectSessionValidationResult {
59
69
  valid: boolean;
@@ -61,5 +71,5 @@ export interface DirectSessionValidationResult {
61
71
  session: DirectSessionRecord | null;
62
72
  }
63
73
  export declare function validateDirectSessionFreshness(sessionId: string): DirectSessionValidationResult;
64
- export declare function finalizeDirectSessionRecord(sessionId: string, afterSnapshot: RepoSnapshot, changeArtifacts: ChangeArtifacts): DirectSessionRecord;
74
+ export declare function finalizeDirectSessionRecord(sessionId: string, changeArtifacts: ChangeArtifacts): DirectSessionRecord;
65
75
  export declare function computeWorkspaceFingerprint(snapshot: RepoSnapshot): string;
@@ -1,11 +1,14 @@
1
- import { mkdirSync, writeFileSync, existsSync, readFileSync, } from "node:fs";
1
+ import { mkdirSync, existsSync, } from "node:fs";
2
2
  import { createHash, randomBytes } from "node:crypto";
3
3
  import { resolve, join } from "node:path";
4
4
  import { getDirectSessionsDir, getConfig, getRepoDirectAllowedCommands, } from "../config.js";
5
5
  import { guardPath } from "../security/pathGuard.js";
6
6
  import { PatchWardenError } from "../errors.js";
7
7
  import { PATCHWARDEN_VERSION, TOOL_SCHEMA_EPOCH } from "../version.js";
8
- import { getLastToolCatalogSnapshot } from "../tools/toolCatalog.js";
8
+ import { atomicWriteFileSync, atomicWriteJsonFileSync } from "../utils/atomicFile.js";
9
+ import { redactSensitiveContent } from "../security/contentRedaction.js";
10
+ import { mutateLockedJsonFileSync, readJsonObjectFileSync, withFileLock, withFileLockSync, } from "../utils/lockedJsonFile.js";
11
+ import { getLastToolCatalogSnapshot } from "../tools/catalog/toolCatalog.js";
9
12
  // ── ID generation ──────────────────────────────────────────────────
10
13
  export function generateDirectSessionId() {
11
14
  const timestamp = new Date()
@@ -16,21 +19,32 @@ export function generateDirectSessionId() {
16
19
  const randomHex = randomBytes(16).toString("hex");
17
20
  return `direct_${timestamp}_${randomHex}`;
18
21
  }
22
+ /**
23
+ * Direct session IDs are directory names. Keep one shared, bounded grammar so
24
+ * MCP tools and Control Center routes cannot disagree about traversal input.
25
+ */
26
+ export function isValidDirectSessionId(sessionId) {
27
+ return /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/.test(sessionId);
28
+ }
29
+ function guardDirectSessionId(sessionId) {
30
+ if (!isValidDirectSessionId(sessionId)) {
31
+ throw new PatchWardenError("invalid_session_id", `Direct session ID "${sessionId}" is invalid.`, "Use the session_id returned by create_direct_session.", true, { session_id: sessionId });
32
+ }
33
+ }
19
34
  // ── Directory management ───────────────────────────────────────────
20
- export function createDirectSessionDir(sessionId) {
21
- const config = getConfig();
35
+ export function createDirectSessionDir(sessionId, config = getConfig()) {
36
+ guardDirectSessionId(sessionId);
22
37
  const sessionsDir = getDirectSessionsDir(config);
23
38
  guardPath(sessionsDir, config.workspaceRoot, config.directSessionsDir);
24
39
  mkdirSync(sessionsDir, { recursive: true });
25
- const dir = resolve(sessionsDir, sessionId);
26
- guardPath(dir, config.workspaceRoot, config.directSessionsDir);
40
+ const dir = getDirectSessionDir(sessionId, config);
27
41
  mkdirSync(dir, { recursive: true });
28
42
  return dir;
29
43
  }
30
- export function getDirectSessionDir(sessionId) {
31
- const config = getConfig();
44
+ export function getDirectSessionDir(sessionId, config = getConfig()) {
45
+ guardDirectSessionId(sessionId);
32
46
  const sessionsDir = getDirectSessionsDir(config);
33
- return resolve(sessionsDir, sessionId);
47
+ return guardPath(resolve(sessionsDir, sessionId), config.workspaceRoot, config.directSessionsDir);
34
48
  }
35
49
  // ── CRUD ───────────────────────────────────────────────────────────
36
50
  export function createDirectSession(input) {
@@ -48,7 +62,11 @@ export function createDirectSession(input) {
48
62
  const expiresAt = new Date(now.getTime() + config.directSessionTtlSeconds * 1000).toISOString();
49
63
  const record = {
50
64
  session_id: sessionId,
51
- title: input.title || "",
65
+ title: redactSensitiveContent(input.title || "")
66
+ .content
67
+ .replace(/[\r\n\t]+/g, " ")
68
+ .trim()
69
+ .slice(0, 500),
52
70
  repo_path: input.repo_path,
53
71
  resolved_repo_path: input.resolved_repo_path,
54
72
  created_at: now.toISOString(),
@@ -66,36 +84,55 @@ export function createDirectSession(input) {
66
84
  audited: false,
67
85
  change_artifacts: null,
68
86
  };
69
- writeFileSync(join(dir, "session.json"), JSON.stringify(record, null, 2), "utf-8");
87
+ atomicWriteJsonFileSync(join(dir, "session.json"), record);
70
88
  return record;
71
89
  }
72
- export function readDirectSession(sessionId) {
73
- const config = getConfig();
74
- const sessionsDir = getDirectSessionsDir(config);
75
- const dir = resolve(sessionsDir, sessionId);
76
- const file = join(dir, "session.json");
77
- guardPath(file, config.workspaceRoot, config.directSessionsDir);
90
+ export function readDirectSession(sessionId, config = getConfig()) {
91
+ const file = getDirectSessionFile(sessionId, config);
78
92
  if (!existsSync(file)) {
79
93
  throw new PatchWardenError("session_not_found", `Direct session "${sessionId}" not found.`, "Call create_direct_session first to get a valid session_id.", true, { session_id: sessionId });
80
94
  }
81
- return JSON.parse(readFileSync(file, "utf-8"));
95
+ let value;
96
+ try {
97
+ value = readJsonObjectFileSync(file);
98
+ }
99
+ catch {
100
+ throw invalidDirectSessionRecord(sessionId, "session.json is not valid JSON");
101
+ }
102
+ return validateDirectSessionRecord(sessionId, value);
82
103
  }
83
104
  export function updateDirectSession(sessionId, updates) {
84
- const session = readDirectSession(sessionId);
85
- const updated = { ...session, ...updates };
86
- const dir = getDirectSessionDir(sessionId);
87
- writeFileSync(join(dir, "session.json"), JSON.stringify(updated, null, 2), "utf-8");
88
- return updated;
105
+ return mutateDirectSessionRecord(sessionId, (session) => ({
106
+ ...session,
107
+ ...updates,
108
+ session_id: session.session_id,
109
+ }));
110
+ }
111
+ export function appendDirectSessionOperation(sessionId, operation, config = getConfig()) {
112
+ return mutateDirectSessionRecord(sessionId, (session) => ({
113
+ ...session,
114
+ operations: [
115
+ ...session.operations,
116
+ {
117
+ ...operation,
118
+ index: session.operations.reduce((max, entry) => Math.max(max, entry.index), -1) + 1,
119
+ },
120
+ ],
121
+ }), config);
89
122
  }
90
- export function appendDirectSessionOperation(sessionId, operation) {
91
- const session = readDirectSession(sessionId);
92
- const operations = [...session.operations, operation];
93
- return updateDirectSession(sessionId, { operations });
123
+ export function withDirectSessionMutationLock(sessionId, action, config = getConfig()) {
124
+ const lockTarget = join(getDirectSessionDir(sessionId, config), "workspace-mutation");
125
+ return withFileLockSync(lockTarget, action, directSessionBusyOptions(sessionId));
126
+ }
127
+ export function withDirectSessionMutationLockAsync(sessionId, action, config = getConfig()) {
128
+ const lockTarget = join(getDirectSessionDir(sessionId, config), "workspace-mutation");
129
+ return withFileLock(lockTarget, action, directSessionBusyOptions(sessionId));
94
130
  }
95
131
  export function appendDirectSessionVerificationRun(sessionId, run) {
96
- const session = readDirectSession(sessionId);
97
- const verification_runs = [...session.verification_runs, run];
98
- return updateDirectSession(sessionId, { verification_runs });
132
+ return mutateDirectSessionRecord(sessionId, (session) => ({
133
+ ...session,
134
+ verification_runs: [...session.verification_runs, run],
135
+ }));
99
136
  }
100
137
  export function validateDirectSessionFreshness(sessionId) {
101
138
  let session;
@@ -124,20 +161,22 @@ export function validateDirectSessionFreshness(sessionId) {
124
161
  return { valid: true, failure_reason: null, session };
125
162
  }
126
163
  // ── Finalization ───────────────────────────────────────────────────
127
- export function finalizeDirectSessionRecord(sessionId, afterSnapshot, changeArtifacts) {
164
+ export function finalizeDirectSessionRecord(sessionId, changeArtifacts) {
128
165
  const dir = getDirectSessionDir(sessionId);
129
166
  // Write change artifacts to session directory
130
- writeFileSync(join(dir, "changed-files.json"), JSON.stringify(changeArtifacts.changed_files, null, 2), "utf-8");
131
- writeFileSync(join(dir, "diff.patch"), changeArtifacts.diff, "utf-8");
167
+ atomicWriteJsonFileSync(join(dir, "changed-files.json"), changeArtifacts.changed_files);
168
+ atomicWriteFileSync(join(dir, "diff.patch"), changeArtifacts.diff);
132
169
  const summary = {
133
170
  changed_files_total: changeArtifacts.changed_files.length,
134
171
  additions: changeArtifacts.additions,
135
172
  deletions: changeArtifacts.deletions,
136
173
  patch_mode: changeArtifacts.patch_mode,
174
+ diff_redacted: changeArtifacts.diff_redacted === true,
175
+ diff_redaction_categories: changeArtifacts.diff_redaction_categories ?? [],
137
176
  artifact_hygiene: changeArtifacts.artifact_hygiene,
138
177
  };
139
- writeFileSync(join(dir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
140
- writeFileSync(join(dir, "summary.md"), formatSummaryMd(changeArtifacts), "utf-8");
178
+ atomicWriteJsonFileSync(join(dir, "summary.json"), summary);
179
+ atomicWriteFileSync(join(dir, "summary.md"), formatSummaryMd(changeArtifacts));
141
180
  return updateDirectSession(sessionId, {
142
181
  finalized: true,
143
182
  finalized_at: new Date().toISOString(),
@@ -166,6 +205,58 @@ function extractReason(error) {
166
205
  return error.message.slice(0, 80);
167
206
  return String(error).slice(0, 80);
168
207
  }
208
+ function invalidDirectSessionRecord(sessionId, detail) {
209
+ return new PatchWardenError("invalid_session_record", `Direct session "${sessionId}" is invalid: ${detail}.`, "Create a new direct session and do not modify its internal session.json file.", true, { session_id: sessionId });
210
+ }
211
+ function getDirectSessionFile(sessionId, config = getConfig()) {
212
+ const dir = getDirectSessionDir(sessionId, config);
213
+ return guardPath(join(dir, "session.json"), config.workspaceRoot, config.directSessionsDir);
214
+ }
215
+ function mutateDirectSessionRecord(sessionId, mutation, config = getConfig()) {
216
+ const file = getDirectSessionFile(sessionId, config);
217
+ if (!existsSync(file)) {
218
+ throw new PatchWardenError("session_not_found", `Direct session "${sessionId}" not found.`, "Call create_direct_session first to get a valid session_id.", true, { session_id: sessionId });
219
+ }
220
+ return mutateLockedJsonFileSync(file, (current) => {
221
+ const session = validateDirectSessionRecord(sessionId, current);
222
+ const next = mutation(session);
223
+ return { next, result: next };
224
+ }, {
225
+ // Session-record appends are short serialized writes. Give Windows
226
+ // antivirus-delayed lock detachment enough time to finish instead of
227
+ // dropping concurrent operation or verification evidence.
228
+ waitMs: 10_000,
229
+ busyError: () => new PatchWardenError("direct_session_busy", `Direct session "${sessionId}" is currently being updated.`, "Retry after the current Direct operation completes.", true, { session_id: sessionId }),
230
+ });
231
+ }
232
+ function directSessionBusyOptions(sessionId) {
233
+ return {
234
+ waitMs: 0,
235
+ busyError: () => new PatchWardenError("direct_session_busy", `Direct session "${sessionId}" already has a workspace operation in progress.`, "Wait for the active patch, sync, verification, or finalization to finish, then retry.", true, { session_id: sessionId }),
236
+ };
237
+ }
238
+ function validateDirectSessionRecord(sessionId, value) {
239
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
240
+ throw invalidDirectSessionRecord(sessionId, "session.json must contain an object");
241
+ }
242
+ const record = value;
243
+ if (record.session_id !== sessionId) {
244
+ throw invalidDirectSessionRecord(sessionId, "session_id does not match its directory");
245
+ }
246
+ if (typeof record.resolved_repo_path !== "string" || record.resolved_repo_path.trim() === "") {
247
+ throw invalidDirectSessionRecord(sessionId, "resolved_repo_path is missing");
248
+ }
249
+ if (typeof record.expires_at !== "string" || !Number.isFinite(Date.parse(record.expires_at))) {
250
+ throw invalidDirectSessionRecord(sessionId, "expires_at is invalid");
251
+ }
252
+ if (typeof record.finalized !== "boolean") {
253
+ throw invalidDirectSessionRecord(sessionId, "finalized must be a boolean");
254
+ }
255
+ if (!Array.isArray(record.operations) || !Array.isArray(record.verification_runs)) {
256
+ throw invalidDirectSessionRecord(sessionId, "operation or verification history is invalid");
257
+ }
258
+ return record;
259
+ }
169
260
  function formatSummaryMd(artifacts) {
170
261
  const lines = [
171
262
  "# Direct Session Change Summary",
@@ -174,6 +265,7 @@ function formatSummaryMd(artifacts) {
174
265
  `**Additions:** ${artifacts.additions}`,
175
266
  `**Deletions:** ${artifacts.deletions}`,
176
267
  `**Patch mode:** ${artifacts.patch_mode}`,
268
+ `**Diff redacted:** ${artifacts.diff_redacted === true ? "yes" : "no"}`,
177
269
  "",
178
270
  "## Artifact Hygiene",
179
271
  "",
@@ -1,10 +1,11 @@
1
- import { writeFileSync } from "node:fs";
2
1
  import { join } from "node:path";
3
2
  import { getConfig } from "../config.js";
4
3
  import { guardDirectCommand } from "../security/commandGuard.js";
5
4
  import { redactSensitiveContent } from "../security/contentRedaction.js";
6
5
  import { runSimpleProcess } from "../runner/simpleProcess.js";
6
+ import { resolveTrustedCommandLine } from "../runner/processSecurity.js";
7
7
  import { getDirectSessionDir } from "./directSessionStore.js";
8
+ import { atomicWriteJsonFileSync } from "../utils/atomicFile.js";
8
9
  const MAX_TAIL_CHARS = 10_000;
9
10
  const DEFAULT_TIMEOUT_SECONDS = 120;
10
11
  export async function runDirectVerification(input) {
@@ -13,7 +14,7 @@ export async function runDirectVerification(input) {
13
14
  const timeoutSeconds = input.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS;
14
15
  const timeoutMs = timeoutSeconds * 1000;
15
16
  // Parse command into command + args
16
- const parts = parseCommand(allowedCommand);
17
+ const parts = parseCommand(allowedCommand, input.resolvedRepoPath);
17
18
  if (!parts) {
18
19
  throw new Error(`Failed to parse command: "${allowedCommand}"`);
19
20
  }
@@ -73,27 +74,18 @@ export async function runDirectVerification(input) {
73
74
  ],
74
75
  spawn_error: result.spawnError,
75
76
  };
76
- writeFileSync(join(sessionDir, "verification.json"), JSON.stringify(verificationJson, null, 2), "utf-8");
77
+ atomicWriteJsonFileSync(join(sessionDir, "verification.json"), verificationJson);
77
78
  return { run, log_path: logPath };
78
79
  }
79
80
  // ── Helpers ────────────────────────────────────────────────────────
80
- function parseCommand(command) {
81
+ function parseCommand(command, cwd) {
81
82
  const trimmed = command.trim();
82
83
  if (!trimmed)
83
84
  return null;
84
- // Simple split by spaces - sufficient for allowlisted commands like "npm test"
85
- const parts = trimmed.split(/\s+/);
86
- // On Windows, npm/npx are batch files (.cmd) and spawn() cannot find them
87
- // without shell resolution. Wrap with cmd.exe /c for those commands.
88
- if (process.platform === "win32" && (parts[0] === "npm" || parts[0] === "npx")) {
89
- return {
90
- command: "cmd",
91
- args: ["/c", ...parts],
92
- };
93
- }
85
+ const invocation = resolveTrustedCommandLine(trimmed, cwd);
94
86
  return {
95
- command: parts[0],
96
- args: parts.slice(1),
87
+ command: invocation.command,
88
+ args: invocation.argsPrefix,
97
89
  };
98
90
  }
99
91
  function truncateTail(content, maxChars) {
package/dist/doctor.js CHANGED
@@ -9,28 +9,36 @@
9
9
  */
10
10
  import { existsSync, statSync, readFileSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
11
11
  import { isAbsolute, resolve, normalize, join } from "node:path";
12
- import { execSync } from "node:child_process";
13
12
  import { createServer } from "node:net";
14
13
  import { getConfig } from "./config.js";
15
14
  import { guardPath, guardWorkspacePath } from "./security/pathGuard.js";
16
15
  import { isSensitivePath } from "./security/sensitiveGuard.js";
17
16
  import { guardPlanContent } from "./security/planGuard.js";
18
- import { TASK_READ_ONLY_FILES } from "./tools/getTaskFile.js";
17
+ import { TASK_READ_ONLY_FILES } from "./tools/tasks/getTaskFile.js";
19
18
  import { getToolDefs } from "./tools/registry.js";
20
- import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES, selectToolsForProfile } from "./tools/toolCatalog.js";
21
- import { buildToolRegistry } from "./tools/toolRegistry.js";
22
- import { runAllSchemaDriftChecks } from "./tools/schemaDriftCheck.js";
19
+ import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES, selectToolsForProfile } from "./tools/catalog/toolCatalog.js";
20
+ import { buildToolRegistry } from "./tools/catalog/toolRegistry.js";
21
+ import { runAllSchemaDriftChecks } from "./tools/diagnostics/schemaDriftCheck.js";
23
22
  import { runReleaseGateCheck } from "./release/releaseGate.js";
24
23
  import { PATCHWARDEN_VERSION } from "./version.js";
25
24
  import { logger } from "./logging.js";
25
+ import { unsafeWorkspaceRootLabel } from "./security/workspaceRootGuard.js";
26
+ import { runSimpleProcessSync } from "./runner/simpleProcess.js";
27
+ import { resolveAgentLaunch } from "./runner/agentInvocation.js";
26
28
  // ── Helpers ────────────────────────────────────────────────────────
27
- function cmd(cmdStr) {
29
+ function cmd(command, args = []) {
28
30
  try {
29
- return execSync(cmdStr, {
30
- encoding: "utf-8",
31
- timeout: 5000,
32
- stdio: ["ignore", "pipe", "ignore"], // suppress stdin and stderr
33
- }).trim();
31
+ const result = runSimpleProcessSync({
32
+ command,
33
+ args: [...args],
34
+ cwd: process.cwd(),
35
+ timeoutMs: 5000,
36
+ maxStdoutBytes: 64 * 1024,
37
+ maxStderrBytes: 64 * 1024,
38
+ });
39
+ if (result.exitCode !== 0)
40
+ return "";
41
+ return `${result.stdout}${result.stderr}`.trim();
34
42
  }
35
43
  catch {
36
44
  return "";
@@ -75,7 +83,7 @@ const checkNpmAvailable = {
75
83
  id: "npm-available",
76
84
  description: "npm available",
77
85
  run() {
78
- const npmVer = cmd("npm --version");
86
+ const npmVer = cmd(process.platform === "win32" ? "npm.cmd" : "npm", ["--version"]);
79
87
  return [checkResult("npm available", npmVer !== "", npmVer || "npm not found in PATH")];
80
88
  },
81
89
  };
@@ -101,7 +109,7 @@ const checkGitAvailable = {
101
109
  id: "git-available",
102
110
  description: "Git available",
103
111
  run() {
104
- const gitVer = cmd("git --version");
112
+ const gitVer = cmd("git", ["--version"]);
105
113
  return [warnCheckResult("Git available", gitVer !== "", gitVer || "git not found — runner git.diff will not work")];
106
114
  },
107
115
  };
@@ -154,17 +162,9 @@ const checkWorkspaceRoot = {
154
162
  }
155
163
  catch { }
156
164
  results.push(checkResult("workspaceRoot is directory", isDir, ws));
157
- const dangerousRoots = [
158
- { pattern: /^[A-Za-z]:\\?$/, label: "drive root" },
159
- { pattern: /\\Users\\[^\\]+$/, label: "user home directory" },
160
- { pattern: /\\Desktop$/, label: "Desktop" },
161
- { pattern: /\\Downloads$/, label: "Downloads" },
162
- { pattern: /\\Documents$/, label: "Documents" },
163
- ];
164
- for (const { pattern, label } of dangerousRoots) {
165
- if (pattern.test(ws)) {
166
- results.push(warnResult(`workspaceRoot is ${label}: ${ws} — consider narrowing to a project directory`));
167
- }
165
+ const unsafeLabel = unsafeWorkspaceRootLabel(ws);
166
+ if (unsafeLabel) {
167
+ results.push(warnResult(`workspaceRoot is ${unsafeLabel}: ${ws} consider narrowing to a project directory`));
168
168
  }
169
169
  return results;
170
170
  },
@@ -258,7 +258,9 @@ const checkServerVersion = {
258
258
  const packageJson = JSON.parse(readFileSync(resolve(process.cwd(), "package.json"), "utf-8"));
259
259
  return [
260
260
  checkResult("Server version matches package.json", packageJson.version === PATCHWARDEN_VERSION, `${PATCHWARDEN_VERSION} vs ${packageJson.version}`),
261
- checkResult("Manifest preflight script exists", existsSync(resolve(process.cwd(), "scripts/checks/mcp-manifest-check.js")), "scripts/checks/mcp-manifest-check.js"),
261
+ process.env.PATCHWARDEN_DESKTOP_RUNTIME === "1"
262
+ ? okResult("Manifest preflight covered by compiled desktop runtime checks")
263
+ : checkResult("Manifest preflight script exists", existsSync(resolve(process.cwd(), "scripts/checks/mcp-manifest-check.js")), "scripts/checks/mcp-manifest-check.js"),
262
264
  ];
263
265
  },
264
266
  };
@@ -375,23 +377,23 @@ const checkToolModules = {
375
377
  description: "Tool module checks",
376
378
  run() {
377
379
  const newTools = [
378
- "listTasks",
379
- "listAgents",
380
- "cancelTask",
381
- "killTask",
382
- "retryTask",
383
- "getTaskProgress",
384
- "getTaskSummary",
385
- "waitForTask",
386
- "getTaskStdoutTail",
387
- "healthCheck",
388
- "auditTask",
380
+ { name: "listTasks", subdir: "tasks" },
381
+ { name: "listAgents", subdir: "workspace" },
382
+ { name: "cancelTask", subdir: "tasks" },
383
+ { name: "killTask", subdir: "tasks" },
384
+ { name: "retryTask", subdir: "tasks" },
385
+ { name: "getTaskProgress", subdir: "tasks" },
386
+ { name: "getTaskSummary", subdir: "tasks" },
387
+ { name: "waitForTask", subdir: "tasks" },
388
+ { name: "getTaskStdoutTail", subdir: "tasks" },
389
+ { name: "healthCheck", subdir: "diagnostics" },
390
+ { name: "auditTask", subdir: "diagnostics" },
389
391
  ];
390
392
  const results = [];
391
393
  for (const t of newTools) {
392
- const compiled = resolve(process.cwd(), "dist/tools", `${t}.js`);
394
+ const compiled = resolve(process.cwd(), "dist/tools", t.subdir, `${t.name}.js`);
393
395
  const exists = existsSync(compiled);
394
- results.push(checkResult(`Tool module: ${t}`, exists, exists ? "compiled" : "missing"));
396
+ results.push(checkResult(`Tool module: ${t.name}`, exists, exists ? "compiled" : "missing"));
395
397
  }
396
398
  return results;
397
399
  },
@@ -401,18 +403,18 @@ const checkDirectToolModules = {
401
403
  description: "Direct tool module checks",
402
404
  run() {
403
405
  const directToolModules = [
404
- "createDirectSession",
405
- "searchWorkspace",
406
- "applyPatch",
407
- "runVerification",
408
- "finalizeDirectSession",
409
- "auditSession",
406
+ { name: "createDirectSession", subdir: "direct" },
407
+ { name: "searchWorkspace", subdir: "workspace" },
408
+ { name: "applyPatch", subdir: "workspace" },
409
+ { name: "runVerification", subdir: "tasks" },
410
+ { name: "finalizeDirectSession", subdir: "direct" },
411
+ { name: "auditSession", subdir: "diagnostics" },
410
412
  ];
411
413
  const results = [];
412
414
  for (const t of directToolModules) {
413
- const compiled = resolve(process.cwd(), "dist/tools", `${t}.js`);
415
+ const compiled = resolve(process.cwd(), "dist/tools", t.subdir, `${t.name}.js`);
414
416
  const exists = existsSync(compiled);
415
- results.push(checkResult(`Direct tool module: ${t}`, exists, exists ? "compiled" : "missing"));
417
+ results.push(checkResult(`Direct tool module: ${t.name}`, exists, exists ? "compiled" : "missing"));
416
418
  }
417
419
  return results;
418
420
  },
@@ -514,8 +516,7 @@ const checkAgentCommands = {
514
516
  if (!context.config)
515
517
  return [];
516
518
  const results = [];
517
- const agents = context.config.agents || {};
518
- for (const [name, agentCfg] of Object.entries(agents)) {
519
+ for (const [name, agentCfg] of Object.entries(context.config.agents)) {
519
520
  const cmdName = agentCfg.command;
520
521
  const looksLikePath = isAbsolute(cmdName) || cmdName.includes("/") || cmdName.includes("\\");
521
522
  if (looksLikePath) {
@@ -523,11 +524,20 @@ const checkAgentCommands = {
523
524
  results.push(warnCheckResult(`Agent "${name}" command available`, agentExists, agentExists ? `Found: ${cmdName}` : `"${cmdName}" does not exist — agent tasks will fail`));
524
525
  continue;
525
526
  }
526
- // Platform-appropriate lookup: 'where' on Windows, 'command -v' on Unix
527
+ // Use an argumentized lookup utility; never interpolate config into a shell.
527
528
  const isWin = process.platform === "win32";
528
- const lookupCmd = isWin ? `where ${cmdName}` : `command -v ${cmdName}`;
529
- const fallbackCmd = isWin ? `command -v ${cmdName}` : `which ${cmdName}`;
530
- const found = cmd(lookupCmd) || cmd(fallbackCmd);
529
+ let found = "";
530
+ if (isWin) {
531
+ try {
532
+ found = resolveAgentLaunch(name, cmdName, process.platform, process.env.PATH || "", existsSync, context.config.workspaceRoot, typeof agentCfg.adapter === "string" ? agentCfg.adapter : name).command;
533
+ }
534
+ catch {
535
+ found = "";
536
+ }
537
+ }
538
+ else {
539
+ found = cmd("which", [cmdName]);
540
+ }
531
541
  results.push(warnCheckResult(`Agent "${name}" command available`, found !== "", found ? `Found: ${found.split("\n")[0]}` : `"${cmdName}" not in PATH — agent tasks will fail`));
532
542
  }
533
543
  return results;
@@ -12,7 +12,7 @@
12
12
  * 3. 存在 warn 级检查项或文档不同步 → NEEDS_FIX
13
13
  * 4. 所有检查通过 → ACCEPTED
14
14
  */
15
- import type { AcceptanceStatus } from "../tools/createTask.js";
15
+ import type { AcceptanceStatus } from "../tools/tasks/createTask.js";
16
16
  export type AcceptanceVerdict = "ACCEPTED" | "NEEDS_FIX" | "REJECTED" | "BLOCKED_BY_APPROVAL";
17
17
  export interface AcceptanceCheck {
18
18
  name: string;