patchwarden 1.5.1 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (591) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +158 -26
  3. package/README.md +124 -24
  4. package/dist/assessments/agentAssessor.js +21 -12
  5. package/dist/assessments/assessmentDiagnostics.d.ts +29 -0
  6. package/dist/assessments/assessmentDiagnostics.js +132 -0
  7. package/dist/assessments/assessmentStore.d.ts +39 -0
  8. package/dist/assessments/assessmentStore.js +207 -45
  9. package/dist/assessments/securitySnapshot.d.ts +50 -0
  10. package/dist/assessments/securitySnapshot.js +158 -0
  11. package/dist/config.d.ts +15 -0
  12. package/dist/config.js +66 -13
  13. package/dist/control/fileManager.d.ts +2 -0
  14. package/dist/control/fileManager.js +39 -0
  15. package/dist/control/middleware/auth.d.ts +6 -0
  16. package/dist/control/middleware/auth.js +14 -5
  17. package/dist/control/middleware/static.js +16 -3
  18. package/dist/control/routeTable.d.ts +24 -0
  19. package/dist/control/routeTable.js +263 -0
  20. package/dist/control/routes/audit.js +17 -7
  21. package/dist/control/routes/evidence.js +1 -1
  22. package/dist/control/routes/lineage.js +1 -1
  23. package/dist/control/routes/process.d.ts +7 -0
  24. package/dist/control/routes/process.js +329 -42
  25. package/dist/control/routes/sessions.d.ts +9 -1
  26. package/dist/control/routes/sessions.js +84 -21
  27. package/dist/control/routes/status.d.ts +60 -1
  28. package/dist/control/routes/status.js +94 -15
  29. package/dist/control/routes/taskActions.js +24 -60
  30. package/dist/control/routes/tasks.d.ts +4 -0
  31. package/dist/control/routes/tasks.js +61 -62
  32. package/dist/control/routes/workspace.js +50 -23
  33. package/dist/control/runtime.d.ts +3 -2
  34. package/dist/control/runtime.js +65 -40
  35. package/dist/control/server.js +37 -365
  36. package/dist/control/shared.d.ts +25 -1
  37. package/dist/control/shared.js +64 -11
  38. package/dist/diagnostics/allowedTestCommandSafety.d.ts +1 -0
  39. package/dist/diagnostics/allowedTestCommandSafety.js +11 -0
  40. package/dist/direct/directAudit.js +234 -225
  41. package/dist/direct/directGuards.d.ts +2 -1
  42. package/dist/direct/directGuards.js +42 -29
  43. package/dist/direct/directPatch.d.ts +6 -1
  44. package/dist/direct/directPatch.js +55 -8
  45. package/dist/direct/directSessionStore.d.ts +16 -6
  46. package/dist/direct/directSessionStore.js +128 -35
  47. package/dist/direct/directVerification.js +8 -16
  48. package/dist/doctor.js +65 -57
  49. package/dist/goal/acceptanceEngine.d.ts +1 -1
  50. package/dist/goal/goalProgress.js +75 -69
  51. package/dist/goal/goalReport.js +5 -12
  52. package/dist/goal/goalStatus.d.ts +2 -0
  53. package/dist/goal/goalStatus.js +10 -3
  54. package/dist/goal/goalStore.d.ts +9 -2
  55. package/dist/goal/goalStore.js +152 -45
  56. package/dist/goal/handoffExport.js +6 -6
  57. package/dist/goal/specKitImport.js +97 -96
  58. package/dist/goal/subgoalSync.js +11 -9
  59. package/dist/goal/worktreeManager.d.ts +2 -0
  60. package/dist/goal/worktreeManager.js +209 -136
  61. package/dist/httpServer.js +111 -32
  62. package/dist/logging.js +5 -3
  63. package/dist/policy/projectPolicy.js +5 -2
  64. package/dist/release/releaseGate.d.ts +1 -1
  65. package/dist/release/releaseGate.js +78 -25
  66. package/dist/runner/agentInvocation.d.ts +14 -0
  67. package/dist/runner/agentInvocation.js +96 -4
  68. package/dist/runner/changeCapture.d.ts +13 -5
  69. package/dist/runner/changeCapture.js +243 -61
  70. package/dist/runner/postTaskCleanup.js +48 -19
  71. package/dist/runner/processSecurity.d.ts +56 -0
  72. package/dist/runner/processSecurity.js +373 -0
  73. package/dist/runner/runTask.d.ts +2 -1
  74. package/dist/runner/runTask.js +162 -93
  75. package/dist/runner/simpleProcess.d.ts +3 -0
  76. package/dist/runner/simpleProcess.js +50 -36
  77. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  78. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  79. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +3 -1
  80. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  81. package/dist/runner/taskStatusStore.d.ts +11 -0
  82. package/dist/runner/taskStatusStore.js +35 -0
  83. package/dist/runner/watch.d.ts +14 -1
  84. package/dist/runner/watch.js +320 -49
  85. package/dist/security/contentRedaction.js +2 -2
  86. package/dist/security/discoveryTokenStore.d.ts +3 -2
  87. package/dist/security/discoveryTokenStore.js +17 -2
  88. package/dist/security/loopbackHost.d.ts +2 -0
  89. package/dist/security/loopbackHost.js +16 -0
  90. package/dist/security/pathGuard.js +32 -17
  91. package/dist/security/planGuard.js +2 -1
  92. package/dist/security/runtimeGuard.js +9 -6
  93. package/dist/security/secretComparison.d.ts +3 -0
  94. package/dist/security/secretComparison.js +11 -0
  95. package/dist/security/sensitiveGuard.d.ts +5 -0
  96. package/dist/security/sensitiveGuard.js +27 -8
  97. package/dist/security/toolInvocationGuard.d.ts +2 -2
  98. package/dist/security/toolInvocationGuard.js +1 -1
  99. package/dist/security/workspaceRootGuard.d.ts +8 -0
  100. package/dist/security/workspaceRootGuard.js +47 -0
  101. package/dist/smoke-test.js +27 -26
  102. package/dist/tools/catalog/index.d.ts +4 -0
  103. package/dist/tools/catalog/index.js +4 -0
  104. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  105. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  106. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +6 -15
  107. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  108. package/dist/tools/definitions/toolDefs.js +1177 -0
  109. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  110. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  111. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +2 -1
  112. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +134 -36
  113. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +18 -6
  114. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +36 -8
  115. package/dist/tools/diagnostics/index.d.ts +6 -0
  116. package/dist/tools/diagnostics/index.js +6 -0
  117. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  118. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  119. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +12 -8
  120. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +23 -13
  121. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  122. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  123. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +6 -6
  124. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +1 -1
  125. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  126. package/dist/tools/direct/index.d.ts +3 -0
  127. package/dist/tools/direct/index.js +3 -0
  128. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  129. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  130. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  131. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  132. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  133. package/dist/tools/discovery/index.d.ts +3 -0
  134. package/dist/tools/discovery/index.js +3 -0
  135. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  136. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  137. package/dist/tools/dispatch/coreDispatch.js +32 -31
  138. package/dist/tools/dispatch/diagnosticDispatch.d.ts +3 -5
  139. package/dist/tools/dispatch/diagnosticDispatch.js +70 -68
  140. package/dist/tools/dispatch/directDispatch.js +11 -10
  141. package/dist/tools/dispatch/goalDispatch.js +5 -4
  142. package/dist/tools/dispatch/releaseDispatch.js +3 -2
  143. package/dist/tools/dispatch/validation.d.ts +7 -0
  144. package/dist/tools/dispatch/validation.js +71 -0
  145. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  146. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +1 -1
  147. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  148. package/dist/tools/goals/index.d.ts +3 -0
  149. package/dist/tools/goals/index.js +3 -0
  150. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  151. package/dist/tools/registry.d.ts +7 -11
  152. package/dist/tools/registry.js +17 -1178
  153. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  154. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  155. package/dist/tools/release/index.d.ts +2 -0
  156. package/dist/tools/release/index.js +2 -0
  157. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  158. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  159. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  160. package/dist/tools/tasks/cancelTask.js +86 -0
  161. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
  162. package/dist/tools/{createTask.js → tasks/createTask.js} +110 -34
  163. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  164. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +12 -16
  165. package/dist/tools/{evidencePack.js → tasks/evidencePack.js} +34 -22
  166. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  167. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  168. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +11 -6
  169. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  170. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +35 -36
  171. package/dist/tools/tasks/index.d.ts +18 -0
  172. package/dist/tools/tasks/index.js +18 -0
  173. package/dist/tools/tasks/killTask.d.ts +1 -0
  174. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  175. package/dist/tools/{listTasks.js → tasks/listTasks.js} +5 -4
  176. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +3 -2
  177. package/dist/tools/tasks/reconcileTasks.js +496 -0
  178. package/dist/tools/tasks/retryTask.js +60 -0
  179. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  180. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +63 -41
  181. package/dist/tools/tasks/runVerification.js +38 -0
  182. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  183. package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +2 -2
  184. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  185. package/dist/tools/tasks/taskStates.d.ts +6 -0
  186. package/dist/tools/tasks/taskStates.js +52 -0
  187. package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +3 -11
  188. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  189. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  190. package/dist/tools/workspace/applyPatch.js +53 -0
  191. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  192. package/dist/tools/workspace/index.d.ts +10 -0
  193. package/dist/tools/workspace/index.js +10 -0
  194. package/dist/tools/workspace/listAgents.d.ts +23 -0
  195. package/dist/tools/workspace/listAgents.js +56 -0
  196. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  197. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  198. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  199. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  200. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  201. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  202. package/dist/tools/workspace/syncFile.js +134 -0
  203. package/dist/utils/atomicFile.d.ts +7 -0
  204. package/dist/utils/atomicFile.js +67 -0
  205. package/dist/utils/boundedFile.d.ts +8 -0
  206. package/dist/utils/boundedFile.js +93 -0
  207. package/dist/utils/lockedJsonFile.d.ts +15 -0
  208. package/dist/utils/lockedJsonFile.js +224 -0
  209. package/dist/utils/platform.d.ts +11 -0
  210. package/dist/utils/platform.js +26 -0
  211. package/dist/utils/stableJson.d.ts +2 -0
  212. package/dist/utils/stableJson.js +12 -0
  213. package/dist/version.d.ts +3 -2
  214. package/dist/version.js +3 -2
  215. package/dist/watcherStatus.d.ts +2 -2
  216. package/dist/watcherStatus.js +18 -0
  217. package/docs/CODE_WIKI.md +808 -143
  218. package/docs/architecture.md +15 -55
  219. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  220. package/docs/control-center/README.md +4 -4
  221. package/docs/dashboard-overview.md +4 -4
  222. package/docs/desktop-app.md +175 -0
  223. package/docs/evidence-pack-schema.md +3 -3
  224. package/docs/open-source-application.md +16 -11
  225. package/docs/release-checklist.md +1 -1
  226. package/docs/release-evidence.md +35 -39
  227. package/docs/task-safe-review-workflow.md +5 -5
  228. package/docs/threat-model.md +19 -1
  229. package/examples/config.example.json +4 -2
  230. package/package.json +27 -10
  231. package/scripts/README.md +15 -6
  232. package/scripts/build.js +11 -0
  233. package/scripts/checks/brand-check.js +3 -0
  234. package/scripts/checks/build-output-check.js +37 -0
  235. package/scripts/checks/control-center-smoke.js +102 -10
  236. package/scripts/checks/control-smoke.js +19 -8
  237. package/scripts/checks/http-mcp-smoke.js +82 -4
  238. package/scripts/checks/lifecycle-smoke.js +15 -12
  239. package/scripts/checks/mcp-manifest-check.js +1 -1
  240. package/scripts/checks/mcp-smoke.js +58 -4
  241. package/scripts/checks/package-manifest-check.js +20 -7
  242. package/scripts/checks/unit-tests.js +3 -5
  243. package/scripts/checks/watcher-supervisor-smoke.js +38 -1
  244. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  245. package/scripts/control/manage-patchwarden.ps1 +69 -4
  246. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  247. package/scripts/control/run-background-supervisor.ps1 +41 -0
  248. package/scripts/control/start-patchwarden-tunnel.ps1 +191 -33
  249. package/scripts/e2e/demo-runtime.mjs +153 -0
  250. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  251. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  252. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  253. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  254. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  255. package/scripts/lib/clean-generated-output.js +11 -0
  256. package/scripts/lib/file-discovery.js +12 -0
  257. package/scripts/release/desktop-preflight.js +211 -0
  258. package/scripts/release/pack-clean.js +59 -14
  259. package/src/assessments/agentAssessor.ts +26 -12
  260. package/src/assessments/assessmentDiagnostics.ts +172 -0
  261. package/src/assessments/assessmentStore.ts +288 -51
  262. package/src/assessments/securitySnapshot.ts +231 -0
  263. package/src/config.ts +70 -13
  264. package/src/control/fileManager.ts +40 -0
  265. package/src/control/middleware/auth.ts +22 -4
  266. package/src/control/middleware/static.ts +16 -3
  267. package/src/control/routeTable.ts +301 -0
  268. package/src/control/routes/audit.ts +14 -6
  269. package/src/control/routes/evidence.ts +1 -1
  270. package/src/control/routes/lineage.ts +1 -1
  271. package/src/control/routes/process.ts +341 -40
  272. package/src/control/routes/sessions.ts +88 -25
  273. package/src/control/routes/status.ts +134 -17
  274. package/src/control/routes/taskActions.ts +24 -58
  275. package/src/control/routes/tasks.ts +66 -58
  276. package/src/control/routes/workspace.ts +56 -25
  277. package/src/control/runtime.ts +71 -43
  278. package/src/control/server.ts +37 -342
  279. package/src/control/shared.ts +77 -11
  280. package/src/diagnostics/allowedTestCommandSafety.ts +12 -0
  281. package/src/direct/directAudit.ts +17 -9
  282. package/src/direct/directGuards.ts +72 -44
  283. package/src/direct/directPatch.ts +95 -8
  284. package/src/direct/directSessionStore.ts +213 -55
  285. package/src/direct/directVerification.ts +9 -23
  286. package/src/doctor.ts +76 -64
  287. package/src/goal/acceptanceEngine.ts +1 -1
  288. package/src/goal/goalProgress.ts +17 -13
  289. package/src/goal/goalReport.ts +5 -13
  290. package/src/goal/goalStatus.ts +19 -3
  291. package/src/goal/goalStore.ts +241 -54
  292. package/src/goal/handoffExport.ts +6 -6
  293. package/src/goal/specKitImport.ts +12 -12
  294. package/src/goal/subgoalSync.ts +11 -8
  295. package/src/goal/worktreeManager.ts +139 -22
  296. package/src/httpServer.ts +113 -30
  297. package/src/logging.ts +10 -3
  298. package/src/policy/projectPolicy.ts +5 -2
  299. package/src/release/releaseGate.ts +74 -27
  300. package/src/runner/agentInvocation.ts +126 -4
  301. package/src/runner/changeCapture.ts +291 -57
  302. package/src/runner/postTaskCleanup.ts +41 -17
  303. package/src/runner/processSecurity.ts +433 -0
  304. package/src/runner/runTask.ts +197 -97
  305. package/src/runner/simpleProcess.ts +66 -31
  306. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  307. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +6 -3
  308. package/src/runner/taskStatusStore.ts +69 -0
  309. package/src/runner/watch.ts +348 -52
  310. package/src/security/contentRedaction.ts +2 -2
  311. package/src/security/discoveryTokenStore.ts +18 -4
  312. package/src/security/loopbackHost.ts +19 -0
  313. package/src/security/pathGuard.ts +35 -16
  314. package/src/security/planGuard.ts +2 -1
  315. package/src/security/runtimeGuard.ts +9 -6
  316. package/src/security/secretComparison.ts +13 -0
  317. package/src/security/sensitiveGuard.ts +27 -8
  318. package/src/security/toolInvocationGuard.ts +2 -2
  319. package/src/security/workspaceRootGuard.ts +53 -0
  320. package/src/smoke-test.ts +27 -26
  321. package/src/tools/catalog/index.ts +4 -0
  322. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  323. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +7 -16
  324. package/src/tools/definitions/toolDefs.ts +1264 -0
  325. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  326. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +136 -39
  327. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +37 -9
  328. package/src/tools/diagnostics/index.ts +6 -0
  329. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  330. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +27 -17
  331. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  332. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +6 -6
  333. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +7 -5
  334. package/src/tools/direct/index.ts +3 -0
  335. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  336. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  337. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  338. package/src/tools/discovery/index.ts +3 -0
  339. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  340. package/src/tools/dispatch/coreDispatch.ts +36 -31
  341. package/src/tools/dispatch/diagnosticDispatch.ts +26 -21
  342. package/src/tools/dispatch/directDispatch.ts +11 -10
  343. package/src/tools/dispatch/goalDispatch.ts +5 -4
  344. package/src/tools/dispatch/releaseDispatch.ts +3 -2
  345. package/src/tools/dispatch/validation.ts +78 -0
  346. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  347. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +41 -26
  348. package/src/tools/goals/index.ts +3 -0
  349. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  350. package/src/tools/registry.ts +17 -1261
  351. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  352. package/src/tools/release/index.ts +2 -0
  353. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  354. package/src/tools/tasks/cancelTask.ts +110 -0
  355. package/src/tools/{createTask.ts → tasks/createTask.ts} +161 -33
  356. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +12 -16
  357. package/src/tools/{evidencePack.ts → tasks/evidencePack.ts} +36 -22
  358. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  359. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +13 -6
  360. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  361. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +46 -44
  362. package/src/tools/tasks/index.ts +18 -0
  363. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +6 -5
  364. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +228 -77
  365. package/src/tools/tasks/retryTask.ts +69 -0
  366. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +73 -46
  367. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  368. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  369. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  370. package/src/tools/tasks/taskStates.ts +54 -0
  371. package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +3 -12
  372. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  373. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  374. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  375. package/src/tools/workspace/index.ts +10 -0
  376. package/src/tools/workspace/listAgents.ts +75 -0
  377. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  378. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  379. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  380. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  381. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  382. package/src/tools/workspace/syncFile.ts +292 -0
  383. package/src/utils/atomicFile.ts +74 -0
  384. package/src/utils/boundedFile.ts +116 -0
  385. package/src/utils/lockedJsonFile.ts +251 -0
  386. package/src/utils/platform.ts +31 -0
  387. package/src/utils/stableJson.ts +12 -0
  388. package/src/version.ts +3 -2
  389. package/src/watcherStatus.ts +19 -1
  390. package/ui/desktop-bootstrap.js +11 -0
  391. package/ui/desktop-bridge.js +36 -0
  392. package/ui/desktop.css +160 -0
  393. package/ui/getting-started.js +93 -0
  394. package/ui/i18n.js +484 -0
  395. package/ui/log-parser.js +30 -0
  396. package/ui/pages/audit.html +12 -0
  397. package/ui/pages/dashboard.html +94 -147
  398. package/ui/pages/direct-sessions.html +123 -26
  399. package/ui/pages/getting-started.html +43 -0
  400. package/ui/pages/logs.html +134 -22
  401. package/ui/pages/settings.html +101 -0
  402. package/ui/pages/task-detail.html +12 -0
  403. package/ui/pages/tasks.html +54 -8
  404. package/ui/pages/workspace.html +38 -1
  405. package/ui/settings.js +254 -0
  406. package/PatchWarden-Control.cmd +0 -6
  407. package/Restart-PatchWarden-Control.cmd +0 -6
  408. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  409. package/dist/test/unit/acceptance-engine.test.js +0 -228
  410. package/dist/test/unit/agent-router.test.d.ts +0 -1
  411. package/dist/test/unit/agent-router.test.js +0 -287
  412. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  413. package/dist/test/unit/android-doctor.test.js +0 -118
  414. package/dist/test/unit/apply-patch.test.d.ts +0 -1
  415. package/dist/test/unit/apply-patch.test.js +0 -225
  416. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  417. package/dist/test/unit/audit-checks.test.js +0 -350
  418. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  419. package/dist/test/unit/chinese-path.test.js +0 -91
  420. package/dist/test/unit/command-guard.test.d.ts +0 -1
  421. package/dist/test/unit/command-guard.test.js +0 -160
  422. package/dist/test/unit/create-task.test.d.ts +0 -1
  423. package/dist/test/unit/create-task.test.js +0 -197
  424. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  425. package/dist/test/unit/diagnose-task.test.js +0 -457
  426. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  427. package/dist/test/unit/direct-guards.test.js +0 -328
  428. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  429. package/dist/test/unit/discovery-token-store.test.js +0 -139
  430. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  431. package/dist/test/unit/evidence-pack.test.js +0 -224
  432. package/dist/test/unit/get-task-status.test.d.ts +0 -1
  433. package/dist/test/unit/get-task-status.test.js +0 -174
  434. package/dist/test/unit/get-task-summary.test.d.ts +0 -1
  435. package/dist/test/unit/get-task-summary.test.js +0 -146
  436. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  437. package/dist/test/unit/goal-graph.test.js +0 -298
  438. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  439. package/dist/test/unit/goal-progress.test.js +0 -381
  440. package/dist/test/unit/goal-report.test.d.ts +0 -1
  441. package/dist/test/unit/goal-report.test.js +0 -159
  442. package/dist/test/unit/goal-status.test.d.ts +0 -1
  443. package/dist/test/unit/goal-status.test.js +0 -215
  444. package/dist/test/unit/goal-store.test.d.ts +0 -1
  445. package/dist/test/unit/goal-store.test.js +0 -253
  446. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  447. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  448. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  449. package/dist/test/unit/goal-tools-registry.test.js +0 -192
  450. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  451. package/dist/test/unit/handoff-export.test.js +0 -263
  452. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  453. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  454. package/dist/test/unit/logging.test.d.ts +0 -1
  455. package/dist/test/unit/logging.test.js +0 -397
  456. package/dist/test/unit/path-guard.test.d.ts +0 -1
  457. package/dist/test/unit/path-guard.test.js +0 -133
  458. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  459. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  460. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  461. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  462. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  463. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  464. package/dist/test/unit/release-gate.test.d.ts +0 -1
  465. package/dist/test/unit/release-gate.test.js +0 -242
  466. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  467. package/dist/test/unit/run-task-loop.test.js +0 -380
  468. package/dist/test/unit/safe-status.test.d.ts +0 -1
  469. package/dist/test/unit/safe-status.test.js +0 -165
  470. package/dist/test/unit/safe-views.test.d.ts +0 -1
  471. package/dist/test/unit/safe-views.test.js +0 -171
  472. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  473. package/dist/test/unit/schema-drift-check.test.js +0 -176
  474. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  475. package/dist/test/unit/sensitive-guard.test.js +0 -104
  476. package/dist/test/unit/spec-kit-import.test.d.ts +0 -1
  477. package/dist/test/unit/spec-kit-import.test.js +0 -341
  478. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  479. package/dist/test/unit/subgoal-sync.test.js +0 -183
  480. package/dist/test/unit/sync-file.test.d.ts +0 -1
  481. package/dist/test/unit/sync-file.test.js +0 -154
  482. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  483. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  484. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  485. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  486. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  487. package/dist/test/unit/toolSearch.test.js +0 -571
  488. package/dist/test/unit/wait-for-task.test.d.ts +0 -1
  489. package/dist/test/unit/wait-for-task.test.js +0 -144
  490. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  491. package/dist/test/unit/watcher-status.test.js +0 -169
  492. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  493. package/dist/test/unit/worktree-manager.test.js +0 -176
  494. package/dist/tools/applyPatch.js +0 -41
  495. package/dist/tools/cancelTask.d.ts +0 -30
  496. package/dist/tools/cancelTask.js +0 -64
  497. package/dist/tools/finalizeDirectSession.js +0 -84
  498. package/dist/tools/goalSubgoalTask.js +0 -110
  499. package/dist/tools/killTask.d.ts +0 -15
  500. package/dist/tools/listAgents.d.ts +0 -12
  501. package/dist/tools/listAgents.js +0 -42
  502. package/dist/tools/reconcileTasks.js +0 -352
  503. package/dist/tools/retryTask.js +0 -45
  504. package/dist/tools/runVerification.js +0 -36
  505. package/dist/tools/syncFile.js +0 -65
  506. package/docs/control-center/control-center-daily-driver.md +0 -211
  507. package/docs/control-center/control-center-mvp.md +0 -205
  508. package/docs/control-center/control-center-phase2.md +0 -159
  509. package/docs/execution-plan-2026-07-09.md +0 -315
  510. package/docs/release-v0.6.0.md +0 -71
  511. package/docs/release-v0.6.1.md +0 -75
  512. package/docs/release-v0.6.4.md +0 -45
  513. package/docs/roadmap-execution-and-acceptance.md +0 -365
  514. package/src/test/unit/acceptance-engine.test.ts +0 -261
  515. package/src/test/unit/agent-router.test.ts +0 -342
  516. package/src/test/unit/android-doctor.test.ts +0 -158
  517. package/src/test/unit/apply-patch.test.ts +0 -293
  518. package/src/test/unit/audit-checks.test.ts +0 -567
  519. package/src/test/unit/chinese-path.test.ts +0 -106
  520. package/src/test/unit/command-guard.test.ts +0 -221
  521. package/src/test/unit/create-task.test.ts +0 -255
  522. package/src/test/unit/diagnose-task.test.ts +0 -544
  523. package/src/test/unit/direct-guards.test.ts +0 -467
  524. package/src/test/unit/discovery-token-store.test.ts +0 -181
  525. package/src/test/unit/evidence-pack.test.ts +0 -251
  526. package/src/test/unit/get-task-status.test.ts +0 -203
  527. package/src/test/unit/get-task-summary.test.ts +0 -173
  528. package/src/test/unit/goal-graph.test.ts +0 -347
  529. package/src/test/unit/goal-progress.test.ts +0 -538
  530. package/src/test/unit/goal-report.test.ts +0 -189
  531. package/src/test/unit/goal-status.test.ts +0 -270
  532. package/src/test/unit/goal-store.test.ts +0 -318
  533. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  534. package/src/test/unit/goal-tools-registry.test.ts +0 -245
  535. package/src/test/unit/handoff-export.test.ts +0 -295
  536. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  537. package/src/test/unit/logging.test.ts +0 -497
  538. package/src/test/unit/path-guard.test.ts +0 -180
  539. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  540. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  541. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  542. package/src/test/unit/release-gate.test.ts +0 -314
  543. package/src/test/unit/run-task-loop.test.ts +0 -425
  544. package/src/test/unit/safe-status.test.ts +0 -187
  545. package/src/test/unit/safe-views.test.ts +0 -184
  546. package/src/test/unit/schema-drift-check.test.ts +0 -260
  547. package/src/test/unit/sensitive-guard.test.ts +0 -124
  548. package/src/test/unit/spec-kit-import.test.ts +0 -429
  549. package/src/test/unit/subgoal-sync.test.ts +0 -236
  550. package/src/test/unit/sync-file.test.ts +0 -231
  551. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  552. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  553. package/src/test/unit/toolSearch.test.ts +0 -756
  554. package/src/test/unit/wait-for-task.test.ts +0 -176
  555. package/src/test/unit/watcher-status.test.ts +0 -190
  556. package/src/test/unit/worktree-manager.test.ts +0 -247
  557. package/src/tools/cancelTask.ts +0 -70
  558. package/src/tools/listAgents.ts +0 -54
  559. package/src/tools/retryTask.ts +0 -53
  560. package/src/tools/syncFile.ts +0 -122
  561. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  562. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  563. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  564. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  565. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  566. /package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +0 -0
  567. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  568. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  569. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  570. /package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +0 -0
  571. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  572. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  573. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  574. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  575. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  576. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  577. /package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +0 -0
  578. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  579. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  580. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  581. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  582. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  583. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  584. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  585. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  586. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  587. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  588. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  589. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  590. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  591. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
@@ -0,0 +1,75 @@
1
+ import { existsSync, statSync } from "node:fs";
2
+ import { basename, delimiter, extname, isAbsolute, join, resolve } from "node:path";
3
+ import { getConfig } from "../../config.js";
4
+ import { sanitizeTrustedPath } from "../../runner/processSecurity.js";
5
+
6
+ export interface AgentAvailability {
7
+ name: string;
8
+ configured: true;
9
+ available: boolean;
10
+ command: string;
11
+ reason: string | null;
12
+ checked_at: string;
13
+ adapter: string | null;
14
+ model: string | null;
15
+ capabilities: { model_override: boolean };
16
+ availability_scope: "executable_only";
17
+ provider_status: "not_checked";
18
+ invocation_ready?: boolean;
19
+ model_argument_present?: boolean;
20
+ }
21
+
22
+ export function listAgents(): { agents: AgentAvailability[]; total: number; config_path: string; workspace_root: string } {
23
+ const config = getConfig();
24
+ const checkedAt = new Date().toISOString();
25
+ const configPath = process.env.PATCHWARDEN_CONFIG
26
+ ? resolve(process.env.PATCHWARDEN_CONFIG)
27
+ : resolve(process.cwd(), "patchwarden.config.json");
28
+ const agents = Object.entries(config.agents)
29
+ .sort(([left], [right]) => left.localeCompare(right))
30
+ .map(([name, agent]) => {
31
+ const available = commandExists(agent.command, config.workspaceRoot);
32
+ const modelArgumentPresent = typeof agent.model === "string"
33
+ ? agent.args.some((arg, index) => arg === "--model" && agent.args[index + 1] === agent.model)
34
+ : true;
35
+ return {
36
+ name,
37
+ configured: true as const,
38
+ available,
39
+ command: basename(agent.command),
40
+ reason: available ? null : "Configured executable was not found on disk or PATH.",
41
+ checked_at: checkedAt,
42
+ adapter: typeof agent.adapter === "string" ? agent.adapter : ["codex", "opencode"].includes(name) ? name : null,
43
+ model: typeof agent.model === "string" ? agent.model : null,
44
+ capabilities: { model_override: typeof agent.adapter === "string" || ["codex", "opencode"].includes(name) },
45
+ availability_scope: "executable_only" as const,
46
+ provider_status: "not_checked" as const,
47
+ invocation_ready: available && modelArgumentPresent,
48
+ model_argument_present: modelArgumentPresent,
49
+ };
50
+ });
51
+ return { agents, total: agents.length, config_path: configPath, workspace_root: config.workspaceRoot };
52
+ }
53
+
54
+ function commandExists(command: string, workspaceRoot: string): boolean {
55
+ if (isAbsolute(command) || command.includes("/") || command.includes("\\")) {
56
+ return isFile(command);
57
+ }
58
+
59
+ const pathEntries = sanitizeTrustedPath(process.env.PATH || "", workspaceRoot).split(delimiter).filter(Boolean);
60
+ const extensions = process.platform === "win32"
61
+ ? (process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";")
62
+ : [""];
63
+ const candidates = extname(command)
64
+ ? pathEntries.map((entry) => join(entry, command))
65
+ : pathEntries.flatMap((entry) => extensions.map((extension) => join(entry, `${command}${extension}`)));
66
+ return candidates.some(isFile);
67
+ }
68
+
69
+ function isFile(path: string): boolean {
70
+ try {
71
+ return existsSync(path) && statSync(path).isFile();
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
@@ -1,8 +1,8 @@
1
1
  import { readdirSync, statSync, existsSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
- import { getConfig } from "../config.js";
4
- import { guardPath } from "../security/pathGuard.js";
5
- import { guardSensitivePath } from "../security/sensitiveGuard.js";
3
+ import { getConfig } from "../../config.js";
4
+ import { guardPath } from "../../security/pathGuard.js";
5
+ import { guardSensitivePath } from "../../security/sensitiveGuard.js";
6
6
 
7
7
  export interface ListEntry {
8
8
  name: string;
@@ -5,9 +5,9 @@
5
5
  * 参考 src/tools/auditTask.ts 的导出/返回风格。
6
6
  */
7
7
 
8
- import { getConfig } from "../config.js";
9
- import { guardWorkspacePath } from "../security/pathGuard.js";
10
- import { mergeWorktree } from "../goal/worktreeManager.js";
8
+ import { getConfig } from "../../config.js";
9
+ import { guardWorkspacePath } from "../../security/pathGuard.js";
10
+ import { mergeWorktree } from "../../goal/worktreeManager.js";
11
11
 
12
12
  export interface MergeWorktreeInput {
13
13
  worktree_id: string;
@@ -1,14 +1,14 @@
1
1
  import { readFileSync, statSync, existsSync } from "node:fs";
2
2
  import { createHash } from "node:crypto";
3
3
  import { resolve, relative, isAbsolute } from "node:path";
4
- import { getConfig } from "../config.js";
5
- import { guardReadPath } from "../security/pathGuard.js";
6
- import { guardSensitivePath } from "../security/sensitiveGuard.js";
7
- import { redactSensitiveContent } from "../security/contentRedaction.js";
8
- import { PatchWardenError } from "../errors.js";
9
- import { readDirectSession } from "../direct/directSessionStore.js";
10
- import { guardDirectSessionActive, guardDirectReadPath, isBinaryFile, guardDirectFileSize } from "../direct/directGuards.js";
11
- import { resolveToolProfile } from "./toolCatalog.js";
4
+ import { getConfig } from "../../config.js";
5
+ import { guardReadPath } from "../../security/pathGuard.js";
6
+ import { guardSensitivePath } from "../../security/sensitiveGuard.js";
7
+ import { redactSensitiveContent } from "../../security/contentRedaction.js";
8
+ import { PatchWardenError } from "../../errors.js";
9
+ import { readDirectSession } from "../../direct/directSessionStore.js";
10
+ import { guardDirectSessionActive, guardDirectReadPath, isBinaryFile, guardDirectFileSize } from "../../direct/directGuards.js";
11
+ import { resolveToolProfile } from "../catalog/toolCatalog.js";
12
12
 
13
13
  export interface ReadWorkspaceFileOutput {
14
14
  path: string;
@@ -1,6 +1,6 @@
1
- import { getConfig } from "../config.js";
2
- import { guardWorkspacePath } from "../security/pathGuard.js";
3
- import { routeAgent } from "../agents/agentRouter.js";
1
+ import { getConfig } from "../../config.js";
2
+ import { guardWorkspacePath } from "../../security/pathGuard.js";
3
+ import { routeAgent } from "../../agents/agentRouter.js";
4
4
 
5
5
  export interface RecommendAgentForTaskInput {
6
6
  repo_path: string;
@@ -1,13 +1,13 @@
1
1
  import { readdirSync, readFileSync, statSync, existsSync } from "node:fs";
2
2
  import { relative, join, extname } from "node:path";
3
- import { getConfig } from "../config.js";
4
- import { PatchWardenError } from "../errors.js";
5
- import { isSensitivePath } from "../security/sensitiveGuard.js";
3
+ import { getConfig } from "../../config.js";
4
+ import { PatchWardenError } from "../../errors.js";
5
+ import { isSensitivePath } from "../../security/sensitiveGuard.js";
6
6
  import {
7
7
  readDirectSession,
8
8
  validateDirectSessionFreshness,
9
- } from "../direct/directSessionStore.js";
10
- import { guardDirectSessionActive } from "../direct/directGuards.js";
9
+ } from "../../direct/directSessionStore.js";
10
+ import { guardDirectSessionActive } from "../../direct/directGuards.js";
11
11
 
12
12
  // ── Types ──────────────────────────────────────────────────────────
13
13
 
@@ -0,0 +1,292 @@
1
+ import { readFileSync, existsSync, mkdirSync, statSync } from "node:fs";
2
+ import { createHash } from "node:crypto";
3
+ import { dirname } from "node:path";
4
+ import { PatchWardenError } from "../../errors.js";
5
+ import { getConfig, type PatchWardenConfig } from "../../config.js";
6
+ import {
7
+ guardDirectReadPath,
8
+ guardDirectFileSize,
9
+ guardDirectSessionActive,
10
+ guardDirectWritePath,
11
+ } from "../../direct/directGuards.js";
12
+ import {
13
+ appendDirectSessionOperation,
14
+ readDirectSession,
15
+ withDirectSessionMutationLock,
16
+ } from "../../direct/directSessionStore.js";
17
+ import { computeFileSha256 } from "../../direct/directPatch.js";
18
+ import { atomicWriteFileSync } from "../../utils/atomicFile.js";
19
+ import { redactSensitiveContent } from "../../security/contentRedaction.js";
20
+
21
+ export interface SyncFileResult {
22
+ source_path: string;
23
+ target_path: string;
24
+ before_target_sha256: string | null;
25
+ after_target_sha256: string;
26
+ source_sha256: string;
27
+ copied_bytes: number;
28
+ changed: boolean;
29
+ }
30
+
31
+ /**
32
+ * Copy a file from source to target within the same session repo.
33
+ * Both source and target must be inside the session's repo_path.
34
+ */
35
+ export function syncFile(
36
+ sessionId: string,
37
+ sourcePath: string,
38
+ targetPath: string,
39
+ options?: {
40
+ expected_source_sha256?: string;
41
+ expected_target_sha256?: string;
42
+ },
43
+ config?: PatchWardenConfig
44
+ ): SyncFileResult {
45
+ const cfg = config || getConfig();
46
+ return withDirectSessionMutationLock(sessionId, () => {
47
+ const session = readDirectSession(sessionId, cfg);
48
+ guardDirectSessionActive(session);
49
+ const repoPath = session.resolved_repo_path;
50
+ const workspaceRoot = cfg.workspaceRoot;
51
+
52
+ // Guard source path — must be inside repo and not internal/sensitive/binary.
53
+ const resolvedSource = guardDirectReadPath(sourcePath, repoPath, workspaceRoot);
54
+
55
+ if (!existsSync(resolvedSource)) {
56
+ throw new PatchWardenError(
57
+ "source_file_not_found",
58
+ `Source file does not exist: "${sourcePath}".`,
59
+ "Ensure the source path is correct.",
60
+ true,
61
+ { source_path: sourcePath }
62
+ );
63
+ }
64
+
65
+ // Guard target path — must be inside repo, not in blocked dirs
66
+ const resolvedTarget = guardDirectWritePath(targetPath, repoPath, workspaceRoot);
67
+
68
+ const sourceMetadata = statSync(resolvedSource);
69
+ if (!sourceMetadata.isFile()) {
70
+ throw new PatchWardenError(
71
+ "direct_source_not_file",
72
+ `Source path is not a regular file: "${sourcePath}".`,
73
+ "Choose an existing text file inside the Direct session repository.",
74
+ true,
75
+ { source_path: sourcePath },
76
+ );
77
+ }
78
+ guardDirectFileSize(sourceMetadata.size, cfg);
79
+
80
+ // Read once, then hash the exact bytes that will be copied.
81
+ const sourceContent = readFileSync(resolvedSource);
82
+ guardDirectFileSize(sourceContent.length, cfg);
83
+ const sourceText = sourceContent.toString("utf-8");
84
+ if (!Buffer.from(sourceText, "utf-8").equals(sourceContent)) {
85
+ throw new PatchWardenError(
86
+ "unsupported_text_encoding",
87
+ `Source file "${sourcePath}" is not valid UTF-8 text.`,
88
+ "Use a normal task or convert the file to UTF-8 before Direct sync.",
89
+ true,
90
+ { source_path: sourcePath },
91
+ );
92
+ }
93
+ const sensitive = redactSensitiveContent(sourceText);
94
+ if (sensitive.redacted) {
95
+ throw new PatchWardenError(
96
+ "sensitive_content_blocked",
97
+ `Source file contains credential-like content (${sensitive.redaction_categories.join(", ")}).`,
98
+ "Remove the sensitive value and retry with placeholders or environment-variable references.",
99
+ true,
100
+ { source_path: sourcePath, redaction_categories: sensitive.redaction_categories },
101
+ );
102
+ }
103
+ const sourceSha256 = createHash("sha256").update(sourceContent).digest("hex");
104
+ if (options?.expected_source_sha256 && options.expected_source_sha256 !== sourceSha256) {
105
+ throw new PatchWardenError(
106
+ "source_hash_mismatch",
107
+ `Source file hash mismatch. Expected "${options.expected_source_sha256}" but got "${sourceSha256}".`,
108
+ "Re-read the source file to get the current sha256.",
109
+ true,
110
+ { expected_sha256: options.expected_source_sha256, actual_sha256: sourceSha256 }
111
+ );
112
+ }
113
+
114
+ // Get target sha256 before copy
115
+ let beforeTargetSha256: string | null = null;
116
+ if (existsSync(resolvedTarget)) {
117
+ const targetMetadata = statSync(resolvedTarget);
118
+ if (!targetMetadata.isFile()) {
119
+ throw new PatchWardenError(
120
+ "direct_target_not_file",
121
+ `Target path is not a regular file: "${targetPath}".`,
122
+ "Choose a text-file target inside the Direct session repository.",
123
+ true,
124
+ { target_path: targetPath },
125
+ );
126
+ }
127
+ guardDirectFileSize(targetMetadata.size, cfg);
128
+ beforeTargetSha256 = computeFileSha256(resolvedTarget);
129
+ // Verify target sha256 if provided
130
+ if (options?.expected_target_sha256 && options.expected_target_sha256 !== beforeTargetSha256) {
131
+ throw new PatchWardenError(
132
+ "target_hash_mismatch",
133
+ `Target file hash mismatch. Expected "${options.expected_target_sha256}" but got "${beforeTargetSha256}".`,
134
+ "Re-read the target file to get the current sha256.",
135
+ true,
136
+ { expected_sha256: options.expected_target_sha256, actual_sha256: beforeTargetSha256 }
137
+ );
138
+ }
139
+ }
140
+
141
+ const copiedBytes = sourceContent.length;
142
+
143
+ if (beforeTargetSha256 === sourceSha256) {
144
+ assertSourceUnchanged(sourcePath, resolvedSource, sourceSha256, repoPath, workspaceRoot);
145
+ appendSyncOperation(
146
+ sessionId,
147
+ sourcePath,
148
+ targetPath,
149
+ beforeTargetSha256,
150
+ sourceSha256,
151
+ 0,
152
+ 0,
153
+ cfg,
154
+ );
155
+ return buildSyncResult(
156
+ sourcePath,
157
+ targetPath,
158
+ beforeTargetSha256,
159
+ sourceSha256,
160
+ copiedBytes,
161
+ false,
162
+ );
163
+ }
164
+
165
+ // Create target directory if needed
166
+ mkdirSync(dirname(resolvedTarget), { recursive: true });
167
+
168
+ // Re-resolve after directory creation so a newly exposed link/reparse point
169
+ // cannot reuse the earlier lexical decision. Replace atomically in the
170
+ // validated target directory and preserve an existing file's permissions.
171
+ const revalidatedTarget = guardDirectWritePath(resolvedTarget, repoPath, workspaceRoot);
172
+ const currentTargetSha256 = existsSync(revalidatedTarget)
173
+ ? computeFileSha256(revalidatedTarget)
174
+ : null;
175
+ if (currentTargetSha256 !== beforeTargetSha256) {
176
+ throw new PatchWardenError(
177
+ "direct_target_changed_during_sync",
178
+ `Target file "${targetPath}" changed while sync_file was preparing the copy.`,
179
+ "Re-read the target file and retry after concurrent filesystem changes stop.",
180
+ true,
181
+ {
182
+ path: targetPath,
183
+ before_sha256: beforeTargetSha256,
184
+ current_sha256: currentTargetSha256,
185
+ },
186
+ );
187
+ }
188
+ assertSourceUnchanged(sourcePath, resolvedSource, sourceSha256, repoPath, workspaceRoot);
189
+ const existingMode = existsSync(revalidatedTarget) ? statSync(revalidatedTarget).mode : undefined;
190
+ atomicWriteFileSync(revalidatedTarget, sourceContent, { mode: existingMode });
191
+ const finalTarget = guardDirectWritePath(revalidatedTarget, repoPath, workspaceRoot);
192
+ if (finalTarget !== revalidatedTarget) {
193
+ throw new PatchWardenError(
194
+ "direct_path_changed_during_write",
195
+ `Target path "${targetPath}" changed while sync_file was writing it.`,
196
+ "Inspect the repository path and retry only after concurrent filesystem changes stop.",
197
+ true,
198
+ { path: targetPath, operation: "sync_file" }
199
+ );
200
+ }
201
+
202
+ // Compute after hash
203
+ const afterTargetSha256 = computeFileSha256(finalTarget);
204
+ const changed = beforeTargetSha256 !== afterTargetSha256;
205
+
206
+ appendSyncOperation(
207
+ sessionId,
208
+ sourcePath,
209
+ targetPath,
210
+ beforeTargetSha256,
211
+ afterTargetSha256,
212
+ 1,
213
+ copiedBytes,
214
+ cfg,
215
+ );
216
+ return buildSyncResult(
217
+ sourcePath,
218
+ targetPath,
219
+ beforeTargetSha256,
220
+ afterTargetSha256,
221
+ copiedBytes,
222
+ changed,
223
+ );
224
+ }, cfg);
225
+ }
226
+
227
+ function assertSourceUnchanged(
228
+ sourcePath: string,
229
+ resolvedSource: string,
230
+ expectedSha256: string,
231
+ repoPath: string,
232
+ workspaceRoot: string,
233
+ ): void {
234
+ const currentPath = guardDirectReadPath(sourcePath, repoPath, workspaceRoot);
235
+ const samePath = process.platform === "win32"
236
+ ? currentPath.toLowerCase() === resolvedSource.toLowerCase()
237
+ : currentPath === resolvedSource;
238
+ const currentSha256 = samePath && existsSync(currentPath)
239
+ ? computeFileSha256(currentPath)
240
+ : null;
241
+ if (!samePath || currentSha256 !== expectedSha256) {
242
+ throw new PatchWardenError(
243
+ "direct_source_changed_during_sync",
244
+ `Source file "${sourcePath}" changed while sync_file was preparing the copy.`,
245
+ "Re-read the source file and retry after concurrent filesystem changes stop.",
246
+ true,
247
+ { source_path: sourcePath, expected_sha256: expectedSha256, actual_sha256: currentSha256 },
248
+ );
249
+ }
250
+ }
251
+
252
+ function appendSyncOperation(
253
+ sessionId: string,
254
+ sourcePath: string,
255
+ targetPath: string,
256
+ beforeSha256: string | null,
257
+ afterSha256: string,
258
+ operationsApplied: number,
259
+ bytesChanged: number,
260
+ config: PatchWardenConfig,
261
+ ): void {
262
+ appendDirectSessionOperation(sessionId, {
263
+ index: 0,
264
+ timestamp: new Date().toISOString(),
265
+ path: targetPath,
266
+ source_path: sourcePath,
267
+ operation_type: "sync",
268
+ before_sha256: beforeSha256,
269
+ after_sha256: afterSha256,
270
+ operations_applied: operationsApplied,
271
+ bytes_changed: bytesChanged,
272
+ }, config);
273
+ }
274
+
275
+ function buildSyncResult(
276
+ sourcePath: string,
277
+ targetPath: string,
278
+ beforeSha256: string | null,
279
+ afterSha256: string,
280
+ copiedBytes: number,
281
+ changed: boolean,
282
+ ): SyncFileResult {
283
+ return {
284
+ source_path: sourcePath,
285
+ target_path: targetPath,
286
+ before_target_sha256: beforeSha256,
287
+ after_target_sha256: afterSha256,
288
+ source_sha256: afterSha256,
289
+ copied_bytes: copiedBytes,
290
+ changed,
291
+ };
292
+ }
@@ -0,0 +1,74 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { closeSync, fsyncSync, openSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
+
4
+ export interface AtomicWriteOptions {
5
+ encoding?: BufferEncoding;
6
+ mode?: number;
7
+ }
8
+
9
+ const transientWindowsRenameErrors = new Set(["EACCES", "EBUSY", "EPERM"]);
10
+ const renameRetryDeadlineMs = 1_000;
11
+ const renameRetryDelayMs = 10;
12
+ const waitArray = new Int32Array(new SharedArrayBuffer(4));
13
+
14
+ /** Replace a file from a complete same-directory temporary file. */
15
+ export function atomicWriteFileSync(
16
+ path: string,
17
+ content: string | NodeJS.ArrayBufferView,
18
+ options: AtomicWriteOptions = {},
19
+ ): void {
20
+ const temporaryPath = `${path}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
21
+ try {
22
+ writeFileSync(temporaryPath, content, {
23
+ encoding: options.encoding ?? "utf-8",
24
+ flag: "wx",
25
+ ...(options.mode === undefined ? {} : { mode: options.mode }),
26
+ });
27
+ const descriptor = openSync(temporaryPath, "r");
28
+ try {
29
+ try {
30
+ fsyncSync(descriptor);
31
+ } catch (error) {
32
+ const code = error && typeof error === "object" && "code" in error
33
+ ? String((error as NodeJS.ErrnoException).code || "")
34
+ : "";
35
+ // Some Windows filesystems and restricted runtimes reject fsync even
36
+ // after a successful complete write. Keep the same-directory atomic
37
+ // rename guarantee there; unexpected I/O errors still fail closed.
38
+ if (!new Set(["EPERM", "EINVAL", "ENOSYS", "ENOTSUP"]).has(code)) throw error;
39
+ }
40
+ } finally {
41
+ closeSync(descriptor);
42
+ }
43
+ replaceFileSync(temporaryPath, path);
44
+ } catch (error) {
45
+ try { rmSync(temporaryPath, { force: true }); } catch { /* best effort */ }
46
+ throw error;
47
+ }
48
+ }
49
+
50
+ function replaceFileSync(temporaryPath: string, path: string): void {
51
+ const deadline = Date.now() + renameRetryDeadlineMs;
52
+ while (true) {
53
+ try {
54
+ renameSync(temporaryPath, path);
55
+ return;
56
+ } catch (error) {
57
+ const code = error && typeof error === "object" && "code" in error
58
+ ? String((error as NodeJS.ErrnoException).code || "")
59
+ : "";
60
+ if (
61
+ process.platform !== "win32" ||
62
+ !transientWindowsRenameErrors.has(code) ||
63
+ Date.now() >= deadline
64
+ ) {
65
+ throw error;
66
+ }
67
+ Atomics.wait(waitArray, 0, 0, renameRetryDelayMs);
68
+ }
69
+ }
70
+ }
71
+
72
+ export function atomicWriteJsonFileSync(path: string, value: unknown, mode = 0o600): void {
73
+ atomicWriteFileSync(path, `${JSON.stringify(value, null, 2)}\n`, { mode });
74
+ }
@@ -0,0 +1,116 @@
1
+ import {
2
+ appendFileSync,
3
+ closeSync,
4
+ existsSync,
5
+ fstatSync,
6
+ openSync,
7
+ readSync,
8
+ statSync,
9
+ } from "node:fs";
10
+ import { atomicWriteFileSync } from "./atomicFile.js";
11
+ import { withFileLockSync } from "./lockedJsonFile.js";
12
+
13
+ const TRUNCATION_MARKER = Buffer.from("[earlier content truncated]\n", "utf-8");
14
+
15
+ export interface BoundedTextRead {
16
+ content: string;
17
+ totalBytes: number;
18
+ truncated: boolean;
19
+ }
20
+
21
+ export function readTextFilePrefixSync(path: string, maxBytes: number): BoundedTextRead {
22
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
23
+ throw new Error("maxBytes must be a positive integer");
24
+ }
25
+ const descriptor = openSync(path, "r");
26
+ try {
27
+ const totalBytes = fstatSync(descriptor).size;
28
+ const length = Math.min(totalBytes, maxBytes);
29
+ const buffer = Buffer.alloc(length);
30
+ let offset = 0;
31
+ while (offset < length) {
32
+ const bytesRead = readSync(descriptor, buffer, offset, length - offset, offset);
33
+ if (bytesRead === 0) break;
34
+ offset += bytesRead;
35
+ }
36
+ let content = buffer.subarray(0, offset).toString("utf-8");
37
+ if (totalBytes > offset && content.endsWith("\uFFFD")) content = content.slice(0, -1);
38
+ return { content, totalBytes, truncated: totalBytes > offset };
39
+ } finally {
40
+ closeSync(descriptor);
41
+ }
42
+ }
43
+
44
+ export function readTextFileTailLinesSync(
45
+ path: string,
46
+ lines: number,
47
+ maxBytes = 1024 * 1024,
48
+ ): string {
49
+ if (!existsSync(path)) return "";
50
+ const boundedLines = Number.isFinite(lines)
51
+ ? Math.max(1, Math.min(1000, Math.trunc(lines)))
52
+ : 100;
53
+ const suffix = readFileSuffix(path, maxBytes);
54
+ let text = suffix.content.toString("utf-8");
55
+ if (suffix.truncated) {
56
+ const firstNewline = text.indexOf("\n");
57
+ if (firstNewline >= 0) text = text.slice(firstNewline + 1);
58
+ }
59
+ const allLines = text.split(/\r?\n/);
60
+ if (allLines.at(-1) === "") allLines.pop();
61
+ return allLines.slice(-boundedLines).join("\n");
62
+ }
63
+
64
+ export function appendBoundedTextFileSync(
65
+ path: string,
66
+ content: string,
67
+ maxBytes = 2 * 1024 * 1024,
68
+ ): void {
69
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= TRUNCATION_MARKER.length) {
70
+ throw new Error("maxBytes must exceed the truncation marker size");
71
+ }
72
+ withFileLockSync(path, () => {
73
+ const incoming = Buffer.from(content, "utf-8");
74
+ const existingSize = existsSync(path) ? statSync(path).size : 0;
75
+ if (existingSize + incoming.length <= maxBytes) {
76
+ appendFileSync(path, incoming, { mode: 0o600 });
77
+ return;
78
+ }
79
+
80
+ const incomingSuffix = incoming.subarray(Math.max(0, incoming.length - maxBytes + TRUNCATION_MARKER.length));
81
+ const existingBudget = Math.max(
82
+ 0,
83
+ maxBytes - TRUNCATION_MARKER.length - incomingSuffix.length,
84
+ );
85
+ const existingSuffix = existingBudget > 0 && existingSize > 0
86
+ ? readFileSuffix(path, existingBudget).content
87
+ : Buffer.alloc(0);
88
+ const output = Buffer.concat([TRUNCATION_MARKER, existingSuffix, incomingSuffix]);
89
+ const mode = existsSync(path) ? statSync(path).mode : 0o600;
90
+ atomicWriteFileSync(path, output, { mode });
91
+ });
92
+ }
93
+
94
+ function readFileSuffix(path: string, maxBytes: number): { content: Buffer; truncated: boolean } {
95
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
96
+ throw new Error("maxBytes must be a positive integer");
97
+ }
98
+ const descriptor = openSync(path, "r");
99
+ try {
100
+ const size = fstatSync(descriptor).size;
101
+ const length = Math.min(size, maxBytes);
102
+ const buffer = Buffer.alloc(length);
103
+ let offset = 0;
104
+ while (offset < length) {
105
+ const bytesRead = readSync(descriptor, buffer, offset, length - offset, size - length + offset);
106
+ if (bytesRead === 0) break;
107
+ offset += bytesRead;
108
+ }
109
+ return {
110
+ content: offset === length ? buffer : buffer.subarray(0, offset),
111
+ truncated: size > length,
112
+ };
113
+ } finally {
114
+ closeSync(descriptor);
115
+ }
116
+ }