patchwarden 1.5.0 → 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 (597) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +194 -26
  3. package/README.md +160 -22
  4. package/dist/assessments/agentAssessor.d.ts +1 -1
  5. package/dist/assessments/agentAssessor.js +24 -15
  6. package/dist/assessments/assessmentStore.d.ts +1 -1
  7. package/dist/assessments/assessmentStore.js +44 -36
  8. package/dist/assessments/confirmCli.js +5 -4
  9. package/dist/config.d.ts +22 -0
  10. package/dist/config.js +98 -11
  11. package/dist/control/fileManager.d.ts +2 -0
  12. package/dist/control/fileManager.js +39 -0
  13. package/dist/control/middleware/auth.d.ts +16 -0
  14. package/dist/control/middleware/auth.js +17 -0
  15. package/dist/control/middleware/static.d.ts +3 -0
  16. package/dist/control/middleware/static.js +78 -0
  17. package/dist/control/routeTable.d.ts +24 -0
  18. package/dist/control/routeTable.js +263 -0
  19. package/dist/control/routes/audit.d.ts +15 -0
  20. package/dist/control/routes/audit.js +287 -0
  21. package/dist/control/routes/evidence.d.ts +4 -0
  22. package/dist/control/routes/evidence.js +96 -0
  23. package/dist/control/routes/lineage.d.ts +3 -0
  24. package/dist/control/routes/lineage.js +71 -0
  25. package/dist/control/routes/policy.d.ts +3 -0
  26. package/dist/control/routes/policy.js +81 -0
  27. package/dist/control/routes/process.d.ts +12 -0
  28. package/dist/control/routes/process.js +487 -0
  29. package/dist/control/routes/sessions.d.ts +30 -0
  30. package/dist/control/routes/sessions.js +287 -0
  31. package/dist/control/routes/status.d.ts +51 -0
  32. package/dist/control/routes/status.js +307 -0
  33. package/dist/control/routes/taskActions.d.ts +21 -0
  34. package/dist/control/routes/taskActions.js +197 -0
  35. package/dist/control/routes/tasks.d.ts +24 -0
  36. package/dist/control/routes/tasks.js +309 -0
  37. package/dist/control/routes/workspace.d.ts +15 -0
  38. package/dist/control/routes/workspace.js +220 -0
  39. package/dist/control/runtime.d.ts +92 -0
  40. package/dist/control/runtime.js +425 -0
  41. package/dist/control/server.d.ts +13 -0
  42. package/dist/control/server.js +150 -0
  43. package/dist/control/shared.d.ts +59 -0
  44. package/dist/control/shared.js +341 -0
  45. package/dist/controlCenter.d.ts +6 -0
  46. package/dist/controlCenter.js +7 -2197
  47. package/dist/direct/directAudit.js +234 -225
  48. package/dist/direct/directGuards.d.ts +2 -1
  49. package/dist/direct/directGuards.js +72 -37
  50. package/dist/direct/directPatch.d.ts +6 -1
  51. package/dist/direct/directPatch.js +55 -8
  52. package/dist/direct/directSessionStore.d.ts +16 -6
  53. package/dist/direct/directSessionStore.js +127 -35
  54. package/dist/direct/directVerification.js +8 -16
  55. package/dist/doctor.d.ts +18 -1
  56. package/dist/doctor.js +614 -373
  57. package/dist/goal/acceptanceEngine.d.ts +1 -1
  58. package/dist/goal/goalProgress.js +75 -69
  59. package/dist/goal/goalReport.d.ts +54 -0
  60. package/dist/goal/goalReport.js +197 -0
  61. package/dist/goal/goalStatus.d.ts +8 -0
  62. package/dist/goal/goalStatus.js +10 -3
  63. package/dist/goal/goalStore.d.ts +9 -2
  64. package/dist/goal/goalStore.js +152 -45
  65. package/dist/goal/handoffExport.js +6 -6
  66. package/dist/goal/specKitImport.d.ts +63 -0
  67. package/dist/goal/specKitImport.js +221 -0
  68. package/dist/goal/subgoalSync.js +13 -10
  69. package/dist/goal/worktreeManager.d.ts +2 -0
  70. package/dist/goal/worktreeManager.js +209 -136
  71. package/dist/httpServer.js +126 -44
  72. package/dist/index.js +7 -4
  73. package/dist/logging.d.ts +7 -1
  74. package/dist/logging.js +13 -3
  75. package/dist/policy/projectPolicy.js +5 -2
  76. package/dist/release/releaseGate.d.ts +1 -1
  77. package/dist/release/releaseGate.js +78 -25
  78. package/dist/runner/agentInvocation.d.ts +14 -0
  79. package/dist/runner/agentInvocation.js +96 -4
  80. package/dist/runner/changeCapture.d.ts +14 -6
  81. package/dist/runner/changeCapture.js +287 -81
  82. package/dist/runner/cli.js +7 -6
  83. package/dist/runner/postTaskCleanup.js +60 -7
  84. package/dist/runner/processSecurity.d.ts +56 -0
  85. package/dist/runner/processSecurity.js +373 -0
  86. package/dist/runner/runTask.d.ts +1 -1
  87. package/dist/runner/runTask.js +339 -287
  88. package/dist/runner/simpleProcess.d.ts +3 -0
  89. package/dist/runner/simpleProcess.js +52 -38
  90. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  91. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  92. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
  93. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  94. package/dist/runner/taskStatusStore.d.ts +11 -0
  95. package/dist/runner/taskStatusStore.js +24 -0
  96. package/dist/runner/watch.d.ts +14 -1
  97. package/dist/runner/watch.js +265 -54
  98. package/dist/security/contentRedaction.d.ts +6 -0
  99. package/dist/security/contentRedaction.js +24 -2
  100. package/dist/security/discoveryTokenStore.d.ts +3 -2
  101. package/dist/security/discoveryTokenStore.js +17 -2
  102. package/dist/security/loopbackHost.d.ts +2 -0
  103. package/dist/security/loopbackHost.js +16 -0
  104. package/dist/security/pathGuard.js +32 -17
  105. package/dist/security/planGuard.js +2 -1
  106. package/dist/security/runtimeGuard.js +9 -6
  107. package/dist/security/secretComparison.d.ts +3 -0
  108. package/dist/security/secretComparison.js +11 -0
  109. package/dist/security/sensitiveGuard.d.ts +5 -0
  110. package/dist/security/sensitiveGuard.js +27 -8
  111. package/dist/security/toolInvocationGuard.d.ts +2 -2
  112. package/dist/security/toolInvocationGuard.js +1 -1
  113. package/dist/security/workspaceRootGuard.d.ts +8 -0
  114. package/dist/security/workspaceRootGuard.js +47 -0
  115. package/dist/smoke-test.js +280 -273
  116. package/dist/tools/catalog/index.d.ts +4 -0
  117. package/dist/tools/catalog/index.js +4 -0
  118. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  119. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  120. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +28 -15
  121. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  122. package/dist/tools/definitions/toolDefs.js +1177 -0
  123. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  124. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  125. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +8 -63
  126. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +111 -37
  127. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
  128. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +10 -10
  129. package/dist/tools/diagnostics/index.d.ts +6 -0
  130. package/dist/tools/diagnostics/index.js +6 -0
  131. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  132. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  133. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +14 -10
  134. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +25 -15
  135. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  136. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  137. package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +1 -1
  138. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +8 -8
  139. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +2 -2
  140. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  141. package/dist/tools/direct/index.d.ts +3 -0
  142. package/dist/tools/direct/index.js +3 -0
  143. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  144. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  145. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  146. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  147. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  148. package/dist/tools/discovery/index.d.ts +3 -0
  149. package/dist/tools/discovery/index.js +3 -0
  150. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  151. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  152. package/dist/tools/dispatch/coreDispatch.d.ts +9 -0
  153. package/dist/tools/dispatch/coreDispatch.js +283 -0
  154. package/dist/tools/dispatch/diagnosticDispatch.d.ts +12 -0
  155. package/dist/tools/dispatch/diagnosticDispatch.js +80 -0
  156. package/dist/tools/dispatch/directDispatch.d.ts +8 -0
  157. package/dist/tools/dispatch/directDispatch.js +116 -0
  158. package/dist/tools/dispatch/goalDispatch.d.ts +8 -0
  159. package/dist/tools/dispatch/goalDispatch.js +92 -0
  160. package/dist/tools/dispatch/releaseDispatch.d.ts +8 -0
  161. package/dist/tools/dispatch/releaseDispatch.js +46 -0
  162. package/dist/tools/dispatch/types.d.ts +23 -0
  163. package/dist/tools/dispatch/types.js +15 -0
  164. package/dist/tools/dispatch/validation.d.ts +7 -0
  165. package/dist/tools/dispatch/validation.js +71 -0
  166. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  167. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +2 -2
  168. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  169. package/dist/tools/goals/index.d.ts +3 -0
  170. package/dist/tools/goals/index.js +3 -0
  171. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  172. package/dist/tools/registry.d.ts +10 -14
  173. package/dist/tools/registry.js +47 -1651
  174. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  175. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  176. package/dist/tools/release/index.d.ts +2 -0
  177. package/dist/tools/release/index.js +2 -0
  178. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  179. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  180. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  181. package/dist/tools/tasks/cancelTask.js +85 -0
  182. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
  183. package/dist/tools/{createTask.js → tasks/createTask.js} +46 -31
  184. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  185. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
  186. package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +6 -0
  187. package/dist/tools/tasks/evidencePack.js +387 -0
  188. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  189. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  190. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
  191. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  192. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
  193. package/dist/tools/tasks/index.d.ts +18 -0
  194. package/dist/tools/tasks/index.js +18 -0
  195. package/dist/tools/tasks/killTask.d.ts +1 -0
  196. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  197. package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
  198. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
  199. package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
  200. package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +2 -2
  201. package/dist/tools/tasks/retryTask.js +60 -0
  202. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  203. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +59 -31
  204. package/dist/tools/tasks/runVerification.js +38 -0
  205. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  206. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  207. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  208. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  209. package/dist/tools/workspace/applyPatch.js +53 -0
  210. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  211. package/dist/tools/workspace/index.d.ts +10 -0
  212. package/dist/tools/workspace/index.js +10 -0
  213. package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
  214. package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
  215. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  216. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  217. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  218. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  219. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  220. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  221. package/dist/tools/workspace/syncFile.js +134 -0
  222. package/dist/utils/atomicFile.d.ts +7 -0
  223. package/dist/utils/atomicFile.js +48 -0
  224. package/dist/utils/boundedFile.d.ts +8 -0
  225. package/dist/utils/boundedFile.js +93 -0
  226. package/dist/utils/lockedJsonFile.d.ts +15 -0
  227. package/dist/utils/lockedJsonFile.js +224 -0
  228. package/dist/utils/platform.d.ts +11 -0
  229. package/dist/utils/platform.js +26 -0
  230. package/dist/utils/stableJson.d.ts +2 -0
  231. package/dist/utils/stableJson.js +12 -0
  232. package/dist/version.d.ts +3 -2
  233. package/dist/version.js +3 -2
  234. package/dist/watcherStatus.d.ts +2 -2
  235. package/dist/watcherStatus.js +18 -0
  236. package/docs/CODE_WIKI.md +1648 -0
  237. package/docs/agentseal-integration.md +150 -0
  238. package/docs/architecture.md +23 -0
  239. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  240. package/docs/assets/patchwarden-oss-demo.gif +0 -0
  241. package/docs/control-center/README.md +4 -4
  242. package/docs/dashboard-overview.md +86 -0
  243. package/docs/demo.md +8 -0
  244. package/docs/desktop-app.md +175 -0
  245. package/docs/direct-session-workflow.md +98 -0
  246. package/docs/evidence-pack-schema.md +215 -0
  247. package/docs/lineage-evidence-pack-workflow.md +127 -0
  248. package/docs/mcp-inspector-testing.md +200 -0
  249. package/docs/open-source-application.md +168 -0
  250. package/docs/opencode-worker.md +151 -0
  251. package/docs/openhands-worker.md +181 -0
  252. package/docs/release-checklist.md +1 -1
  253. package/docs/release-evidence.md +82 -0
  254. package/docs/spec-kit-integration.md +131 -0
  255. package/docs/task-safe-review-workflow.md +98 -0
  256. package/docs/threat-model.md +97 -0
  257. package/docs/user-feedback.md +40 -0
  258. package/docs/why-patchwarden.md +110 -0
  259. package/examples/config.example.json +4 -2
  260. package/package.json +28 -11
  261. package/scripts/README.md +15 -6
  262. package/scripts/build.js +11 -0
  263. package/scripts/checks/brand-check.js +3 -0
  264. package/scripts/checks/build-output-check.js +37 -0
  265. package/scripts/checks/control-center-smoke.js +447 -6
  266. package/scripts/checks/control-smoke.js +19 -8
  267. package/scripts/checks/http-mcp-smoke.js +82 -4
  268. package/scripts/checks/lifecycle-smoke.js +39 -33
  269. package/scripts/checks/mcp-manifest-check.js +1 -1
  270. package/scripts/checks/mcp-smoke.js +30 -4
  271. package/scripts/checks/package-manifest-check.js +20 -7
  272. package/scripts/checks/unit-tests.js +3 -5
  273. package/scripts/checks/watcher-supervisor-smoke.js +9 -0
  274. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  275. package/scripts/control/manage-patchwarden.ps1 +69 -4
  276. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  277. package/scripts/control/run-background-supervisor.ps1 +41 -0
  278. package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
  279. package/scripts/generate-demo-gif.py +320 -0
  280. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  281. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  282. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  283. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  284. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  285. package/scripts/lib/clean-generated-output.js +11 -0
  286. package/scripts/lib/file-discovery.js +12 -0
  287. package/scripts/release/desktop-preflight.js +211 -0
  288. package/scripts/release/pack-clean.js +59 -14
  289. package/src/assessments/agentAssessor.ts +29 -15
  290. package/src/assessments/assessmentStore.ts +55 -43
  291. package/src/assessments/confirmCli.ts +5 -4
  292. package/src/config.ts +105 -11
  293. package/src/control/fileManager.ts +40 -0
  294. package/src/control/middleware/auth.ts +35 -0
  295. package/src/control/middleware/static.ts +84 -0
  296. package/src/control/routeTable.ts +301 -0
  297. package/src/control/routes/audit.ts +329 -0
  298. package/src/control/routes/evidence.ts +107 -0
  299. package/src/control/routes/lineage.ts +92 -0
  300. package/src/control/routes/policy.ts +81 -0
  301. package/src/control/routes/process.ts +505 -0
  302. package/src/control/routes/sessions.ts +314 -0
  303. package/src/control/routes/status.ts +405 -0
  304. package/src/control/routes/taskActions.ts +214 -0
  305. package/src/control/routes/tasks.ts +331 -0
  306. package/src/control/routes/workspace.ts +234 -0
  307. package/src/control/runtime.ts +508 -0
  308. package/src/control/server.ts +165 -0
  309. package/src/control/shared.ts +360 -0
  310. package/src/controlCenter.ts +7 -2347
  311. package/src/direct/directAudit.ts +17 -9
  312. package/src/direct/directGuards.ts +100 -51
  313. package/src/direct/directPatch.ts +95 -8
  314. package/src/direct/directSessionStore.ts +211 -55
  315. package/src/direct/directVerification.ts +9 -23
  316. package/src/doctor.ts +766 -492
  317. package/src/goal/acceptanceEngine.ts +1 -1
  318. package/src/goal/goalProgress.ts +17 -13
  319. package/src/goal/goalReport.ts +263 -0
  320. package/src/goal/goalStatus.ts +25 -3
  321. package/src/goal/goalStore.ts +241 -54
  322. package/src/goal/handoffExport.ts +6 -6
  323. package/src/goal/specKitImport.ts +355 -0
  324. package/src/goal/subgoalSync.ts +15 -10
  325. package/src/goal/worktreeManager.ts +139 -22
  326. package/src/httpServer.ts +130 -44
  327. package/src/index.ts +7 -4
  328. package/src/logging.ts +20 -4
  329. package/src/policy/projectPolicy.ts +5 -2
  330. package/src/release/releaseGate.ts +74 -27
  331. package/src/runner/agentInvocation.ts +126 -4
  332. package/src/runner/changeCapture.ts +342 -80
  333. package/src/runner/cli.ts +7 -6
  334. package/src/runner/postTaskCleanup.ts +55 -7
  335. package/src/runner/processSecurity.ts +433 -0
  336. package/src/runner/runTask.ts +441 -292
  337. package/src/runner/simpleProcess.ts +68 -33
  338. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  339. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
  340. package/src/runner/taskStatusStore.ts +54 -0
  341. package/src/runner/watch.ts +290 -57
  342. package/src/security/contentRedaction.ts +31 -2
  343. package/src/security/discoveryTokenStore.ts +18 -4
  344. package/src/security/loopbackHost.ts +19 -0
  345. package/src/security/pathGuard.ts +35 -16
  346. package/src/security/planGuard.ts +2 -1
  347. package/src/security/runtimeGuard.ts +9 -6
  348. package/src/security/secretComparison.ts +13 -0
  349. package/src/security/sensitiveGuard.ts +27 -8
  350. package/src/security/toolInvocationGuard.ts +2 -2
  351. package/src/security/workspaceRootGuard.ts +53 -0
  352. package/src/smoke-test.ts +276 -273
  353. package/src/tools/catalog/index.ts +4 -0
  354. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  355. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +29 -16
  356. package/src/tools/definitions/toolDefs.ts +1264 -0
  357. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  358. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +196 -89
  359. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +11 -11
  360. package/src/tools/diagnostics/index.ts +6 -0
  361. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  362. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +29 -19
  363. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  364. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +9 -9
  365. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +11 -9
  366. package/src/tools/direct/index.ts +3 -0
  367. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  368. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  369. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  370. package/src/tools/discovery/index.ts +3 -0
  371. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  372. package/src/tools/dispatch/coreDispatch.ts +379 -0
  373. package/src/tools/dispatch/diagnosticDispatch.ts +106 -0
  374. package/src/tools/dispatch/directDispatch.ts +168 -0
  375. package/src/tools/dispatch/goalDispatch.ts +128 -0
  376. package/src/tools/dispatch/releaseDispatch.ts +66 -0
  377. package/src/tools/dispatch/types.ts +24 -0
  378. package/src/tools/dispatch/validation.ts +78 -0
  379. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  380. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +43 -28
  381. package/src/tools/goals/index.ts +3 -0
  382. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  383. package/src/tools/registry.ts +54 -1858
  384. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  385. package/src/tools/release/index.ts +2 -0
  386. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  387. package/src/tools/tasks/cancelTask.ts +109 -0
  388. package/src/tools/{createTask.ts → tasks/createTask.ts} +56 -33
  389. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
  390. package/src/tools/tasks/evidencePack.ts +504 -0
  391. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  392. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
  393. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  394. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
  395. package/src/tools/tasks/index.ts +18 -0
  396. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
  397. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
  398. package/src/tools/tasks/retryTask.ts +69 -0
  399. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +70 -35
  400. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  401. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  402. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  403. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  404. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  405. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  406. package/src/tools/workspace/index.ts +10 -0
  407. package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
  408. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  409. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  410. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  411. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  412. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  413. package/src/tools/workspace/syncFile.ts +292 -0
  414. package/src/utils/atomicFile.ts +58 -0
  415. package/src/utils/boundedFile.ts +116 -0
  416. package/src/utils/lockedJsonFile.ts +251 -0
  417. package/src/utils/platform.ts +31 -0
  418. package/src/utils/stableJson.ts +12 -0
  419. package/src/version.ts +3 -2
  420. package/src/watcherStatus.ts +19 -1
  421. package/ui/desktop-bootstrap.js +11 -0
  422. package/ui/desktop-bridge.js +36 -0
  423. package/ui/desktop.css +160 -0
  424. package/ui/getting-started.js +93 -0
  425. package/ui/i18n.js +484 -0
  426. package/ui/log-parser.js +30 -0
  427. package/ui/pages/audit.html +204 -3
  428. package/ui/pages/dashboard.html +937 -79
  429. package/ui/pages/direct-sessions.html +609 -60
  430. package/ui/pages/getting-started.html +43 -0
  431. package/ui/pages/logs.html +105 -22
  432. package/ui/pages/settings.html +101 -0
  433. package/ui/pages/task-detail.html +468 -438
  434. package/ui/pages/tasks.html +648 -65
  435. package/ui/pages/workspace.html +38 -1
  436. package/ui/settings.js +245 -0
  437. package/PatchWarden-Control.cmd +0 -6
  438. package/Restart-PatchWarden-Control.cmd +0 -6
  439. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  440. package/dist/test/unit/acceptance-engine.test.js +0 -228
  441. package/dist/test/unit/agent-router.test.d.ts +0 -1
  442. package/dist/test/unit/agent-router.test.js +0 -287
  443. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  444. package/dist/test/unit/android-doctor.test.js +0 -118
  445. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  446. package/dist/test/unit/audit-checks.test.js +0 -350
  447. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  448. package/dist/test/unit/chinese-path.test.js +0 -91
  449. package/dist/test/unit/command-guard.test.d.ts +0 -1
  450. package/dist/test/unit/command-guard.test.js +0 -160
  451. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  452. package/dist/test/unit/diagnose-task.test.js +0 -457
  453. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  454. package/dist/test/unit/direct-guards.test.js +0 -213
  455. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  456. package/dist/test/unit/discovery-token-store.test.js +0 -139
  457. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  458. package/dist/test/unit/evidence-pack.test.js +0 -130
  459. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  460. package/dist/test/unit/goal-graph.test.js +0 -298
  461. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  462. package/dist/test/unit/goal-progress.test.js +0 -381
  463. package/dist/test/unit/goal-status.test.d.ts +0 -1
  464. package/dist/test/unit/goal-status.test.js +0 -215
  465. package/dist/test/unit/goal-store.test.d.ts +0 -1
  466. package/dist/test/unit/goal-store.test.js +0 -253
  467. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  468. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  469. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  470. package/dist/test/unit/goal-tools-registry.test.js +0 -190
  471. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  472. package/dist/test/unit/handoff-export.test.js +0 -263
  473. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  474. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  475. package/dist/test/unit/logging.test.d.ts +0 -1
  476. package/dist/test/unit/logging.test.js +0 -397
  477. package/dist/test/unit/path-guard.test.d.ts +0 -1
  478. package/dist/test/unit/path-guard.test.js +0 -109
  479. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  480. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  481. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  482. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  483. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  484. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  485. package/dist/test/unit/release-gate.test.d.ts +0 -1
  486. package/dist/test/unit/release-gate.test.js +0 -242
  487. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  488. package/dist/test/unit/run-task-loop.test.js +0 -380
  489. package/dist/test/unit/safe-status.test.d.ts +0 -1
  490. package/dist/test/unit/safe-status.test.js +0 -165
  491. package/dist/test/unit/safe-views.test.d.ts +0 -1
  492. package/dist/test/unit/safe-views.test.js +0 -171
  493. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  494. package/dist/test/unit/schema-drift-check.test.js +0 -176
  495. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  496. package/dist/test/unit/sensitive-guard.test.js +0 -104
  497. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  498. package/dist/test/unit/subgoal-sync.test.js +0 -183
  499. package/dist/test/unit/sync-file.test.d.ts +0 -1
  500. package/dist/test/unit/sync-file.test.js +0 -154
  501. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  502. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  503. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  504. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  505. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  506. package/dist/test/unit/toolSearch.test.js +0 -571
  507. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  508. package/dist/test/unit/watcher-status.test.js +0 -169
  509. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  510. package/dist/test/unit/worktree-manager.test.js +0 -176
  511. package/dist/tools/applyPatch.js +0 -41
  512. package/dist/tools/cancelTask.d.ts +0 -30
  513. package/dist/tools/cancelTask.js +0 -64
  514. package/dist/tools/evidencePack.js +0 -168
  515. package/dist/tools/finalizeDirectSession.js +0 -84
  516. package/dist/tools/goalSubgoalTask.js +0 -110
  517. package/dist/tools/killTask.d.ts +0 -15
  518. package/dist/tools/retryTask.js +0 -45
  519. package/dist/tools/runVerification.js +0 -36
  520. package/dist/tools/syncFile.js +0 -65
  521. package/docs/control-center/control-center-daily-driver.md +0 -211
  522. package/docs/control-center/control-center-mvp.md +0 -205
  523. package/docs/control-center/control-center-phase2.md +0 -159
  524. package/docs/release-v0.6.0.md +0 -71
  525. package/docs/release-v0.6.1.md +0 -75
  526. package/docs/release-v0.6.4.md +0 -45
  527. package/src/test/unit/acceptance-engine.test.ts +0 -261
  528. package/src/test/unit/agent-router.test.ts +0 -342
  529. package/src/test/unit/android-doctor.test.ts +0 -158
  530. package/src/test/unit/audit-checks.test.ts +0 -567
  531. package/src/test/unit/chinese-path.test.ts +0 -106
  532. package/src/test/unit/command-guard.test.ts +0 -221
  533. package/src/test/unit/diagnose-task.test.ts +0 -544
  534. package/src/test/unit/direct-guards.test.ts +0 -297
  535. package/src/test/unit/discovery-token-store.test.ts +0 -181
  536. package/src/test/unit/evidence-pack.test.ts +0 -142
  537. package/src/test/unit/goal-graph.test.ts +0 -347
  538. package/src/test/unit/goal-progress.test.ts +0 -538
  539. package/src/test/unit/goal-status.test.ts +0 -270
  540. package/src/test/unit/goal-store.test.ts +0 -318
  541. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  542. package/src/test/unit/goal-tools-registry.test.ts +0 -243
  543. package/src/test/unit/handoff-export.test.ts +0 -295
  544. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  545. package/src/test/unit/logging.test.ts +0 -497
  546. package/src/test/unit/path-guard.test.ts +0 -150
  547. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  548. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  549. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  550. package/src/test/unit/release-gate.test.ts +0 -314
  551. package/src/test/unit/run-task-loop.test.ts +0 -425
  552. package/src/test/unit/safe-status.test.ts +0 -187
  553. package/src/test/unit/safe-views.test.ts +0 -184
  554. package/src/test/unit/schema-drift-check.test.ts +0 -260
  555. package/src/test/unit/sensitive-guard.test.ts +0 -124
  556. package/src/test/unit/subgoal-sync.test.ts +0 -236
  557. package/src/test/unit/sync-file.test.ts +0 -231
  558. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  559. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  560. package/src/test/unit/toolSearch.test.ts +0 -756
  561. package/src/test/unit/watcher-status.test.ts +0 -190
  562. package/src/test/unit/worktree-manager.test.ts +0 -247
  563. package/src/tools/cancelTask.ts +0 -70
  564. package/src/tools/evidencePack.ts +0 -205
  565. package/src/tools/retryTask.ts +0 -53
  566. package/src/tools/syncFile.ts +0 -122
  567. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  568. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  569. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  570. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  571. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  572. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  573. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  574. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  575. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  576. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  577. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  578. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  579. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  580. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  581. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  582. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  583. /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
  584. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  585. /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
  586. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  587. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  588. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  589. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  590. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  591. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  592. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  593. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  594. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  595. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  596. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
  597. /package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +0 -0
@@ -1,9 +1,10 @@
1
1
  import { getTaskFile } from "./getTaskFile.js";
2
- import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { existsSync, statSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
- import { getConfig, getTasksDir } from "../config.js";
5
- import { guardPath } from "../security/pathGuard.js";
6
- import { redactSensitiveContent } from "../security/contentRedaction.js";
4
+ import { getConfig, getTasksDir } from "../../config.js";
5
+ import { guardPath } from "../../security/pathGuard.js";
6
+ import { redactSensitiveContent } from "../../security/contentRedaction.js";
7
+ import { readTextFilePrefixSync, readTextFileTailLinesSync, } from "../../utils/boundedFile.js";
7
8
  export function getResult(taskId) {
8
9
  return getTaskFile(taskId, "result.md");
9
10
  }
@@ -20,10 +21,9 @@ export function getDiff(taskId) {
20
21
  }
21
22
  const filePath = existsSync(preferred) ? preferred : fallback;
22
23
  guardPath(filePath, config.workspaceRoot, config.tasksDir);
23
- const size = statSync(filePath).size;
24
- const raw = readFileSync(filePath, "utf-8");
25
- const patchHead = raw.slice(0, config.maxReadFileBytes);
26
- const rawReturnedBytes = Buffer.byteLength(patchHead, "utf-8");
24
+ const bounded = readTextFilePrefixSync(filePath, config.maxReadFileBytes);
25
+ const size = bounded.totalBytes;
26
+ const patchHead = bounded.content;
27
27
  const redaction = redactSensitiveContent(patchHead);
28
28
  const content = redaction.content;
29
29
  const diff = {
@@ -32,7 +32,7 @@ export function getDiff(taskId) {
32
32
  content,
33
33
  path: filePath,
34
34
  available: true,
35
- truncated: size > rawReturnedBytes,
35
+ truncated: bounded.truncated,
36
36
  total_bytes: size,
37
37
  returned_bytes: Buffer.byteLength(content, "utf-8"),
38
38
  patch_head: content,
@@ -90,8 +90,7 @@ export function getTaskLogTail(taskId, file, options) {
90
90
  }
91
91
  const stat = statSync(filePath);
92
92
  const totalBytes = stat.size;
93
- const raw = readFileSync(filePath, "utf-8");
94
- const tail = raw.split("\n").slice(-maxLines).join("\n");
93
+ const tail = readTextFileTailLinesSync(filePath, maxLines);
95
94
  const content = applyRedact ? redactSensitiveContent(tail) : { content: tail, redacted: false, redaction_categories: [] };
96
95
  return {
97
96
  task_id: taskId,
@@ -115,13 +114,15 @@ function getTaskFileTail(taskId, filename, options) {
115
114
  return getTaskFile(taskId, filename);
116
115
  }
117
116
  const stat = statSync(filePath);
118
- const raw = readFileSync(filePath, "utf-8");
119
- let content = raw;
117
+ const maxBytes = options.maxBytes > 0
118
+ ? Math.min(options.maxBytes, config.maxReadFileBytes)
119
+ : config.maxReadFileBytes;
120
+ let content;
120
121
  if (options.tailLines > 0) {
121
- content = raw.split("\n").slice(-options.tailLines).join("\n");
122
+ content = readTextFileTailLinesSync(filePath, Math.min(Math.max(1, options.tailLines), 1000), maxBytes);
122
123
  }
123
- if (options.maxBytes > 0 && Buffer.byteLength(content, "utf-8") > options.maxBytes) {
124
- content = content.slice(0, options.maxBytes);
124
+ else {
125
+ content = readTextFilePrefixSync(filePath, maxBytes).content;
125
126
  }
126
127
  const redaction = redactSensitiveContent(content);
127
128
  return {
@@ -7,9 +7,9 @@
7
7
  * and never installs global dependencies. It only runs `java -version` (read-only)
8
8
  * and inspects files/directories/env vars that already exist.
9
9
  */
10
- import { execSync } from "node:child_process";
11
10
  import { existsSync, readFileSync, statSync } from "node:fs";
12
11
  import { join } from "node:path";
12
+ import { runSimpleProcessSync } from "../../runner/simpleProcess.js";
13
13
  // ── Constants ──────────────────────────────────────────────────────
14
14
  /**
15
15
  * Required explanatory sentence emitted whenever the Android SDK is missing.
@@ -18,17 +18,23 @@ import { join } from "node:path";
18
18
  const SDK_MISSING_REASON = "Android project exists, APK not built because Android SDK is missing.";
19
19
  // ── Helpers ────────────────────────────────────────────────────────
20
20
  /**
21
- * Run a shell command and return its trimmed combined stdout/stderr output.
21
+ * Run a shell-free command and return its trimmed combined stdout/stderr output.
22
22
  * Returns an empty string when the command is missing or fails so callers can
23
23
  * treat absence gracefully. A timeout guards against hanging processes.
24
24
  */
25
- function runCmd(cmdStr) {
25
+ function runCmd(command, args, cwd = process.cwd()) {
26
26
  try {
27
- return execSync(cmdStr, {
28
- encoding: "utf-8",
29
- timeout: 8000,
30
- stdio: ["ignore", "pipe", "ignore"],
31
- }).trim();
27
+ const result = runSimpleProcessSync({
28
+ command,
29
+ args: [...args],
30
+ cwd,
31
+ timeoutMs: 8000,
32
+ maxStdoutBytes: 64 * 1024,
33
+ maxStderrBytes: 64 * 1024,
34
+ });
35
+ if (result.exitCode !== 0)
36
+ return "";
37
+ return `${result.stdout}\n${result.stderr}`.trim();
32
38
  }
33
39
  catch {
34
40
  return "";
@@ -95,8 +101,8 @@ export function diagnoseAndroidBuild(repoPath) {
95
101
  const sdkRoot = androidHomeEnv || androidSdkRootEnv;
96
102
  const sdkExists = sdkRoot !== "" && isDirectory(sdkRoot);
97
103
  // 1. java -version ------------------------------------------------------
98
- // `java -version` writes to stderr; redirect with 2>&1 so execSync captures it.
99
- const javaOutput = runCmd("java -version 2>&1");
104
+ // `java -version` normally writes to stderr; runSimpleProcessSync captures both.
105
+ const javaOutput = runCmd("java", ["-version"], repoPath);
100
106
  const javaVersion = parseJavaVersion(javaOutput);
101
107
  if (javaVersion) {
102
108
  const major = parseJavaMajor(javaVersion);
@@ -1,4 +1,4 @@
1
- import { type PatchOperation } from "../direct/directPatch.js";
1
+ import { type PatchOperation } from "../../direct/directPatch.js";
2
2
  export interface ApplyPatchInput {
3
3
  session_id: string;
4
4
  path: string;
@@ -0,0 +1,53 @@
1
+ import { getConfig } from "../../config.js";
2
+ import { PatchWardenError } from "../../errors.js";
3
+ import { readDirectSession, appendDirectSessionOperation, withDirectSessionMutationLock, } from "../../direct/directSessionStore.js";
4
+ import { guardDirectSessionActive, guardDirectWritePath, guardDirectPatchSize, } from "../../direct/directGuards.js";
5
+ import { applyPatchOperations, computeFileSha256, } from "../../direct/directPatch.js";
6
+ export function applyPatch(input) {
7
+ const config = getConfig();
8
+ return withDirectSessionMutationLock(input.session_id, () => {
9
+ // 1. Read session and guard active (not expired, not finalized)
10
+ const session = readDirectSession(input.session_id);
11
+ guardDirectSessionActive(session);
12
+ // 2. Guard write path (inside repo, not sensitive, not binary,
13
+ // not node_modules/release/dist)
14
+ const resolvedPath = guardDirectWritePath(input.path, session.resolved_repo_path, config.workspaceRoot);
15
+ // 3. Calculate patch size and guard against directMaxPatchBytes
16
+ const patchBytes = Buffer.byteLength(JSON.stringify(input.operations), "utf-8");
17
+ guardDirectPatchSize(patchBytes);
18
+ // 4. Apply against the same bytes whose hash is validated. Re-resolve the
19
+ // target immediately before replacement to detect link/reparse-point swaps.
20
+ const patchResult = applyPatchOperations(resolvedPath, input.operations, {
21
+ expectedSha256: input.expected_sha256,
22
+ maxFileBytes: config.directMaxFileBytes,
23
+ revalidatePath: () => guardDirectWritePath(input.path, session.resolved_repo_path, config.workspaceRoot),
24
+ });
25
+ const beforeSha256 = patchResult.before_sha256;
26
+ // Re-read the file from disk to compute the authoritative after_sha256
27
+ const finalPath = guardDirectWritePath(input.path, session.resolved_repo_path, config.workspaceRoot);
28
+ const afterSha256 = computeFileSha256(finalPath);
29
+ if (afterSha256 !== patchResult.after_sha256) {
30
+ throw new PatchWardenError("direct_target_changed_after_write", `Patch target "${input.path}" changed before the operation could be recorded.`, "Inspect the file and retry only after concurrent filesystem changes stop.", true, { path: input.path, expected_sha256: patchResult.after_sha256, actual_sha256: afterSha256 });
31
+ }
32
+ // 6. Append operation record to session
33
+ appendDirectSessionOperation(input.session_id, {
34
+ index: 0,
35
+ timestamp: new Date().toISOString(),
36
+ path: input.path,
37
+ operation_type: "patch",
38
+ before_sha256: beforeSha256,
39
+ after_sha256: afterSha256,
40
+ operations_applied: patchResult.operations_applied,
41
+ bytes_changed: patchResult.bytes_changed,
42
+ });
43
+ // 7. Return result
44
+ return {
45
+ path: input.path,
46
+ before_sha256: beforeSha256,
47
+ after_sha256: afterSha256,
48
+ operations_applied: patchResult.operations_applied,
49
+ bytes_changed: patchResult.bytes_changed,
50
+ next_action: "Call run_verification to test the changes, or apply_patch for more edits.",
51
+ };
52
+ }, config);
53
+ }
@@ -4,9 +4,9 @@
4
4
  *
5
5
  * 参考 src/tools/auditTask.ts 的导出/返回风格。
6
6
  */
7
- import { getConfig } from "../config.js";
8
- import { guardWorkspacePath } from "../security/pathGuard.js";
9
- import { discardWorktree } from "../goal/worktreeManager.js";
7
+ import { getConfig } from "../../config.js";
8
+ import { guardWorkspacePath } from "../../security/pathGuard.js";
9
+ import { discardWorktree } from "../../goal/worktreeManager.js";
10
10
  /**
11
11
  * 丢弃指定 worktree:移除 worktree 目录、删除临时 branch、归档状态。
12
12
  *
@@ -0,0 +1,10 @@
1
+ export * from "./androidDoctor.js";
2
+ export * from "./applyPatch.js";
3
+ export * from "./discardWorktree.js";
4
+ export * from "./listAgents.js";
5
+ export * from "./listWorkspace.js";
6
+ export * from "./mergeWorktree.js";
7
+ export * from "./readWorkspaceFile.js";
8
+ export * from "./recommendAgentForTask.js";
9
+ export * from "./searchWorkspace.js";
10
+ export * from "./syncFile.js";
@@ -0,0 +1,10 @@
1
+ export * from "./androidDoctor.js";
2
+ export * from "./applyPatch.js";
3
+ export * from "./discardWorktree.js";
4
+ export * from "./listAgents.js";
5
+ export * from "./listWorkspace.js";
6
+ export * from "./mergeWorktree.js";
7
+ export * from "./readWorkspaceFile.js";
8
+ export * from "./recommendAgentForTask.js";
9
+ export * from "./searchWorkspace.js";
10
+ export * from "./syncFile.js";
@@ -5,6 +5,11 @@ export interface AgentAvailability {
5
5
  command: string;
6
6
  reason: string | null;
7
7
  checked_at: string;
8
+ adapter: string | null;
9
+ model: string | null;
10
+ capabilities: {
11
+ model_override: boolean;
12
+ };
8
13
  }
9
14
  export declare function listAgents(): {
10
15
  agents: AgentAvailability[];
@@ -1,13 +1,14 @@
1
1
  import { existsSync, statSync } from "node:fs";
2
2
  import { basename, delimiter, extname, isAbsolute, join } from "node:path";
3
- import { getConfig } from "../config.js";
3
+ import { getConfig } from "../../config.js";
4
+ import { sanitizeTrustedPath } from "../../runner/processSecurity.js";
4
5
  export function listAgents() {
5
6
  const config = getConfig();
6
7
  const checkedAt = new Date().toISOString();
7
8
  const agents = Object.entries(config.agents)
8
9
  .sort(([left], [right]) => left.localeCompare(right))
9
10
  .map(([name, agent]) => {
10
- const available = commandExists(agent.command);
11
+ const available = commandExists(agent.command, config.workspaceRoot);
11
12
  return {
12
13
  name,
13
14
  configured: true,
@@ -15,15 +16,18 @@ export function listAgents() {
15
16
  command: basename(agent.command),
16
17
  reason: available ? null : "Configured executable was not found on disk or PATH.",
17
18
  checked_at: checkedAt,
19
+ adapter: typeof agent.adapter === "string" ? agent.adapter : ["codex", "opencode"].includes(name) ? name : null,
20
+ model: typeof agent.model === "string" ? agent.model : null,
21
+ capabilities: { model_override: typeof agent.adapter === "string" || ["codex", "opencode"].includes(name) },
18
22
  };
19
23
  });
20
24
  return { agents, total: agents.length };
21
25
  }
22
- function commandExists(command) {
26
+ function commandExists(command, workspaceRoot) {
23
27
  if (isAbsolute(command) || command.includes("/") || command.includes("\\")) {
24
28
  return isFile(command);
25
29
  }
26
- const pathEntries = (process.env.PATH || "").split(delimiter).filter(Boolean);
30
+ const pathEntries = sanitizeTrustedPath(process.env.PATH || "", workspaceRoot).split(delimiter).filter(Boolean);
27
31
  const extensions = process.platform === "win32"
28
32
  ? (process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";")
29
33
  : [""];
@@ -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
  const MAX_LIST_FILES = 200;
7
7
  export function listWorkspace(relativePath) {
8
8
  const config = getConfig();
@@ -4,9 +4,9 @@
4
4
  *
5
5
  * 参考 src/tools/auditTask.ts 的导出/返回风格。
6
6
  */
7
- import { getConfig } from "../config.js";
8
- import { guardWorkspacePath } from "../security/pathGuard.js";
9
- import { mergeWorktree } from "../goal/worktreeManager.js";
7
+ import { getConfig } from "../../config.js";
8
+ import { guardWorkspacePath } from "../../security/pathGuard.js";
9
+ import { mergeWorktree } from "../../goal/worktreeManager.js";
10
10
  /**
11
11
  * 合并指定 worktree 的 branch 回 repo_path(主工作区)。
12
12
  *
@@ -1,13 +1,13 @@
1
1
  import { readFileSync, statSync, existsSync } from "node:fs";
2
2
  import { createHash } from "node:crypto";
3
- import { getConfig } from "../config.js";
4
- import { guardReadPath } from "../security/pathGuard.js";
5
- import { guardSensitivePath } from "../security/sensitiveGuard.js";
6
- import { redactSensitiveContent } from "../security/contentRedaction.js";
7
- import { PatchWardenError } from "../errors.js";
8
- import { readDirectSession } from "../direct/directSessionStore.js";
9
- import { guardDirectSessionActive, guardDirectReadPath, isBinaryFile, guardDirectFileSize } from "../direct/directGuards.js";
10
- import { resolveToolProfile } from "./toolCatalog.js";
3
+ import { getConfig } from "../../config.js";
4
+ import { guardReadPath } from "../../security/pathGuard.js";
5
+ import { guardSensitivePath } from "../../security/sensitiveGuard.js";
6
+ import { redactSensitiveContent } from "../../security/contentRedaction.js";
7
+ import { PatchWardenError } from "../../errors.js";
8
+ import { readDirectSession } from "../../direct/directSessionStore.js";
9
+ import { guardDirectSessionActive, guardDirectReadPath, isBinaryFile, guardDirectFileSize } from "../../direct/directGuards.js";
10
+ import { resolveToolProfile } from "../catalog/toolCatalog.js";
11
11
  export function readWorkspaceFile(input) {
12
12
  const config = getConfig();
13
13
  // Support both old string API and new object API
@@ -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
  export function recommendAgentForTask(input) {
5
5
  const config = getConfig();
6
6
  const repoPath = String(input.repo_path || "").trim();
@@ -1,10 +1,10 @@
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";
6
- import { validateDirectSessionFreshness, } from "../direct/directSessionStore.js";
7
- import { guardDirectSessionActive } from "../direct/directGuards.js";
3
+ import { getConfig } from "../../config.js";
4
+ import { PatchWardenError } from "../../errors.js";
5
+ import { isSensitivePath } from "../../security/sensitiveGuard.js";
6
+ import { validateDirectSessionFreshness, } from "../../direct/directSessionStore.js";
7
+ import { guardDirectSessionActive } from "../../direct/directGuards.js";
8
8
  // ── Constants ──────────────────────────────────────────────────────
9
9
  const SKIP_DIRECTORIES = new Set([
10
10
  ".git",
@@ -1,4 +1,4 @@
1
- import { type PatchWardenConfig } from "../config.js";
1
+ import { type PatchWardenConfig } from "../../config.js";
2
2
  export interface SyncFileResult {
3
3
  source_path: string;
4
4
  target_path: string;
@@ -0,0 +1,134 @@
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 } from "../../config.js";
6
+ import { guardDirectReadPath, guardDirectFileSize, guardDirectSessionActive, guardDirectWritePath, } from "../../direct/directGuards.js";
7
+ import { appendDirectSessionOperation, readDirectSession, withDirectSessionMutationLock, } from "../../direct/directSessionStore.js";
8
+ import { computeFileSha256 } from "../../direct/directPatch.js";
9
+ import { atomicWriteFileSync } from "../../utils/atomicFile.js";
10
+ import { redactSensitiveContent } from "../../security/contentRedaction.js";
11
+ /**
12
+ * Copy a file from source to target within the same session repo.
13
+ * Both source and target must be inside the session's repo_path.
14
+ */
15
+ export function syncFile(sessionId, sourcePath, targetPath, options, config) {
16
+ const cfg = config || getConfig();
17
+ return withDirectSessionMutationLock(sessionId, () => {
18
+ const session = readDirectSession(sessionId, cfg);
19
+ guardDirectSessionActive(session);
20
+ const repoPath = session.resolved_repo_path;
21
+ const workspaceRoot = cfg.workspaceRoot;
22
+ // Guard source path — must be inside repo and not internal/sensitive/binary.
23
+ const resolvedSource = guardDirectReadPath(sourcePath, repoPath, workspaceRoot);
24
+ if (!existsSync(resolvedSource)) {
25
+ throw new PatchWardenError("source_file_not_found", `Source file does not exist: "${sourcePath}".`, "Ensure the source path is correct.", true, { source_path: sourcePath });
26
+ }
27
+ // Guard target path — must be inside repo, not in blocked dirs
28
+ const resolvedTarget = guardDirectWritePath(targetPath, repoPath, workspaceRoot);
29
+ const sourceMetadata = statSync(resolvedSource);
30
+ if (!sourceMetadata.isFile()) {
31
+ throw new PatchWardenError("direct_source_not_file", `Source path is not a regular file: "${sourcePath}".`, "Choose an existing text file inside the Direct session repository.", true, { source_path: sourcePath });
32
+ }
33
+ guardDirectFileSize(sourceMetadata.size, cfg);
34
+ // Read once, then hash the exact bytes that will be copied.
35
+ const sourceContent = readFileSync(resolvedSource);
36
+ guardDirectFileSize(sourceContent.length, cfg);
37
+ const sourceText = sourceContent.toString("utf-8");
38
+ if (!Buffer.from(sourceText, "utf-8").equals(sourceContent)) {
39
+ throw new PatchWardenError("unsupported_text_encoding", `Source file "${sourcePath}" is not valid UTF-8 text.`, "Use a normal task or convert the file to UTF-8 before Direct sync.", true, { source_path: sourcePath });
40
+ }
41
+ const sensitive = redactSensitiveContent(sourceText);
42
+ if (sensitive.redacted) {
43
+ throw new PatchWardenError("sensitive_content_blocked", `Source file contains credential-like content (${sensitive.redaction_categories.join(", ")}).`, "Remove the sensitive value and retry with placeholders or environment-variable references.", true, { source_path: sourcePath, redaction_categories: sensitive.redaction_categories });
44
+ }
45
+ const sourceSha256 = createHash("sha256").update(sourceContent).digest("hex");
46
+ if (options?.expected_source_sha256 && options.expected_source_sha256 !== sourceSha256) {
47
+ throw new PatchWardenError("source_hash_mismatch", `Source file hash mismatch. Expected "${options.expected_source_sha256}" but got "${sourceSha256}".`, "Re-read the source file to get the current sha256.", true, { expected_sha256: options.expected_source_sha256, actual_sha256: sourceSha256 });
48
+ }
49
+ // Get target sha256 before copy
50
+ let beforeTargetSha256 = null;
51
+ if (existsSync(resolvedTarget)) {
52
+ const targetMetadata = statSync(resolvedTarget);
53
+ if (!targetMetadata.isFile()) {
54
+ throw new PatchWardenError("direct_target_not_file", `Target path is not a regular file: "${targetPath}".`, "Choose a text-file target inside the Direct session repository.", true, { target_path: targetPath });
55
+ }
56
+ guardDirectFileSize(targetMetadata.size, cfg);
57
+ beforeTargetSha256 = computeFileSha256(resolvedTarget);
58
+ // Verify target sha256 if provided
59
+ if (options?.expected_target_sha256 && options.expected_target_sha256 !== beforeTargetSha256) {
60
+ throw new PatchWardenError("target_hash_mismatch", `Target file hash mismatch. Expected "${options.expected_target_sha256}" but got "${beforeTargetSha256}".`, "Re-read the target file to get the current sha256.", true, { expected_sha256: options.expected_target_sha256, actual_sha256: beforeTargetSha256 });
61
+ }
62
+ }
63
+ const copiedBytes = sourceContent.length;
64
+ if (beforeTargetSha256 === sourceSha256) {
65
+ assertSourceUnchanged(sourcePath, resolvedSource, sourceSha256, repoPath, workspaceRoot);
66
+ appendSyncOperation(sessionId, sourcePath, targetPath, beforeTargetSha256, sourceSha256, 0, 0, cfg);
67
+ return buildSyncResult(sourcePath, targetPath, beforeTargetSha256, sourceSha256, copiedBytes, false);
68
+ }
69
+ // Create target directory if needed
70
+ mkdirSync(dirname(resolvedTarget), { recursive: true });
71
+ // Re-resolve after directory creation so a newly exposed link/reparse point
72
+ // cannot reuse the earlier lexical decision. Replace atomically in the
73
+ // validated target directory and preserve an existing file's permissions.
74
+ const revalidatedTarget = guardDirectWritePath(resolvedTarget, repoPath, workspaceRoot);
75
+ const currentTargetSha256 = existsSync(revalidatedTarget)
76
+ ? computeFileSha256(revalidatedTarget)
77
+ : null;
78
+ if (currentTargetSha256 !== beforeTargetSha256) {
79
+ throw new PatchWardenError("direct_target_changed_during_sync", `Target file "${targetPath}" changed while sync_file was preparing the copy.`, "Re-read the target file and retry after concurrent filesystem changes stop.", true, {
80
+ path: targetPath,
81
+ before_sha256: beforeTargetSha256,
82
+ current_sha256: currentTargetSha256,
83
+ });
84
+ }
85
+ assertSourceUnchanged(sourcePath, resolvedSource, sourceSha256, repoPath, workspaceRoot);
86
+ const existingMode = existsSync(revalidatedTarget) ? statSync(revalidatedTarget).mode : undefined;
87
+ atomicWriteFileSync(revalidatedTarget, sourceContent, { mode: existingMode });
88
+ const finalTarget = guardDirectWritePath(revalidatedTarget, repoPath, workspaceRoot);
89
+ if (finalTarget !== revalidatedTarget) {
90
+ throw new PatchWardenError("direct_path_changed_during_write", `Target path "${targetPath}" changed while sync_file was writing it.`, "Inspect the repository path and retry only after concurrent filesystem changes stop.", true, { path: targetPath, operation: "sync_file" });
91
+ }
92
+ // Compute after hash
93
+ const afterTargetSha256 = computeFileSha256(finalTarget);
94
+ const changed = beforeTargetSha256 !== afterTargetSha256;
95
+ appendSyncOperation(sessionId, sourcePath, targetPath, beforeTargetSha256, afterTargetSha256, 1, copiedBytes, cfg);
96
+ return buildSyncResult(sourcePath, targetPath, beforeTargetSha256, afterTargetSha256, copiedBytes, changed);
97
+ }, cfg);
98
+ }
99
+ function assertSourceUnchanged(sourcePath, resolvedSource, expectedSha256, repoPath, workspaceRoot) {
100
+ const currentPath = guardDirectReadPath(sourcePath, repoPath, workspaceRoot);
101
+ const samePath = process.platform === "win32"
102
+ ? currentPath.toLowerCase() === resolvedSource.toLowerCase()
103
+ : currentPath === resolvedSource;
104
+ const currentSha256 = samePath && existsSync(currentPath)
105
+ ? computeFileSha256(currentPath)
106
+ : null;
107
+ if (!samePath || currentSha256 !== expectedSha256) {
108
+ throw new PatchWardenError("direct_source_changed_during_sync", `Source file "${sourcePath}" changed while sync_file was preparing the copy.`, "Re-read the source file and retry after concurrent filesystem changes stop.", true, { source_path: sourcePath, expected_sha256: expectedSha256, actual_sha256: currentSha256 });
109
+ }
110
+ }
111
+ function appendSyncOperation(sessionId, sourcePath, targetPath, beforeSha256, afterSha256, operationsApplied, bytesChanged, config) {
112
+ appendDirectSessionOperation(sessionId, {
113
+ index: 0,
114
+ timestamp: new Date().toISOString(),
115
+ path: targetPath,
116
+ source_path: sourcePath,
117
+ operation_type: "sync",
118
+ before_sha256: beforeSha256,
119
+ after_sha256: afterSha256,
120
+ operations_applied: operationsApplied,
121
+ bytes_changed: bytesChanged,
122
+ }, config);
123
+ }
124
+ function buildSyncResult(sourcePath, targetPath, beforeSha256, afterSha256, copiedBytes, changed) {
125
+ return {
126
+ source_path: sourcePath,
127
+ target_path: targetPath,
128
+ before_target_sha256: beforeSha256,
129
+ after_target_sha256: afterSha256,
130
+ source_sha256: afterSha256,
131
+ copied_bytes: copiedBytes,
132
+ changed,
133
+ };
134
+ }
@@ -0,0 +1,7 @@
1
+ export interface AtomicWriteOptions {
2
+ encoding?: BufferEncoding;
3
+ mode?: number;
4
+ }
5
+ /** Replace a file from a complete same-directory temporary file. */
6
+ export declare function atomicWriteFileSync(path: string, content: string | NodeJS.ArrayBufferView, options?: AtomicWriteOptions): void;
7
+ export declare function atomicWriteJsonFileSync(path: string, value: unknown, mode?: number): void;
@@ -0,0 +1,48 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { renameSync, rmSync, writeFileSync } from "node:fs";
3
+ const transientWindowsRenameErrors = new Set(["EACCES", "EBUSY", "EPERM"]);
4
+ const renameRetryDeadlineMs = 1_000;
5
+ const renameRetryDelayMs = 10;
6
+ const waitArray = new Int32Array(new SharedArrayBuffer(4));
7
+ /** Replace a file from a complete same-directory temporary file. */
8
+ export function atomicWriteFileSync(path, content, options = {}) {
9
+ const temporaryPath = `${path}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
10
+ try {
11
+ writeFileSync(temporaryPath, content, {
12
+ encoding: options.encoding ?? "utf-8",
13
+ flag: "wx",
14
+ ...(options.mode === undefined ? {} : { mode: options.mode }),
15
+ });
16
+ replaceFileSync(temporaryPath, path);
17
+ }
18
+ catch (error) {
19
+ try {
20
+ rmSync(temporaryPath, { force: true });
21
+ }
22
+ catch { /* best effort */ }
23
+ throw error;
24
+ }
25
+ }
26
+ function replaceFileSync(temporaryPath, path) {
27
+ const deadline = Date.now() + renameRetryDeadlineMs;
28
+ while (true) {
29
+ try {
30
+ renameSync(temporaryPath, path);
31
+ return;
32
+ }
33
+ catch (error) {
34
+ const code = error && typeof error === "object" && "code" in error
35
+ ? String(error.code || "")
36
+ : "";
37
+ if (process.platform !== "win32" ||
38
+ !transientWindowsRenameErrors.has(code) ||
39
+ Date.now() >= deadline) {
40
+ throw error;
41
+ }
42
+ Atomics.wait(waitArray, 0, 0, renameRetryDelayMs);
43
+ }
44
+ }
45
+ }
46
+ export function atomicWriteJsonFileSync(path, value, mode = 0o600) {
47
+ atomicWriteFileSync(path, `${JSON.stringify(value, null, 2)}\n`, { mode });
48
+ }
@@ -0,0 +1,8 @@
1
+ export interface BoundedTextRead {
2
+ content: string;
3
+ totalBytes: number;
4
+ truncated: boolean;
5
+ }
6
+ export declare function readTextFilePrefixSync(path: string, maxBytes: number): BoundedTextRead;
7
+ export declare function readTextFileTailLinesSync(path: string, lines: number, maxBytes?: number): string;
8
+ export declare function appendBoundedTextFileSync(path: string, content: string, maxBytes?: number): void;