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
@@ -1,245 +1,254 @@
1
- import { writeFileSync } from "node:fs";
2
1
  import { join, resolve, relative, isAbsolute } from "node:path";
3
- import { getConfig } from "../config.js";
4
2
  import { isSensitivePath } from "../security/sensitiveGuard.js";
5
- import { readDirectSession, getDirectSessionDir, } from "./directSessionStore.js";
3
+ import { readDirectSession, getDirectSessionDir, updateDirectSession, withDirectSessionMutationLock, } from "./directSessionStore.js";
4
+ import { atomicWriteFileSync, atomicWriteJsonFileSync } from "../utils/atomicFile.js";
6
5
  // ── Main audit function ────────────────────────────────────────────
7
6
  export function auditDirectSession(sessionId) {
8
- const config = getConfig();
9
- const session = readDirectSession(sessionId);
10
- const sessionDir = getDirectSessionDir(sessionId);
11
- const checks = [];
12
- const reasonCodes = [];
13
- const blockingFindings = [];
14
- const warnings = [];
15
- // Check 1: session finalized
16
- checks.push({
17
- name: "session_finalized",
18
- result: session.finalized ? "pass" : "fail",
19
- detail: session.finalized
20
- ? "Session has been finalized."
21
- : "Session has not been finalized. Call finalize_direct_session first.",
22
- reason_code: session.finalized ? undefined : "session_not_finalized",
23
- });
24
- // Get change artifacts if available
25
- const artifacts = session.change_artifacts;
26
- // Check 2: diff empty
27
- const changedFilesTotal = artifacts?.changed_files?.length ?? 0;
28
- checks.push({
29
- name: "diff_empty",
30
- result: changedFilesTotal === 0 ? "warn" : "pass",
31
- detail: changedFilesTotal === 0
32
- ? "No file changes detected in this session."
33
- : `${changedFilesTotal} file(s) changed.`,
34
- reason_code: changedFilesTotal === 0 ? "empty_diff" : undefined,
35
- });
36
- if (artifacts) {
37
- // Check 3: out-of-scope changes
38
- const outOfScope = findOutOfScopeChanges(artifacts, session.resolved_repo_path);
39
- checks.push({
40
- name: "out_of_scope_changes",
41
- result: outOfScope.length === 0 ? "pass" : "fail",
42
- detail: outOfScope.length === 0
43
- ? "All changes are within the session repo_path."
44
- : `${outOfScope.length} file(s) modified outside session repo_path: ${outOfScope.join(", ")}`,
45
- reason_code: outOfScope.length > 0 ? "out_of_scope_changes" : undefined,
46
- });
47
- // Check 4: sensitive files
48
- const sensitiveFiles = findSensitiveChanges(artifacts);
7
+ return withDirectSessionMutationLock(sessionId, () => {
8
+ const session = readDirectSession(sessionId);
9
+ const sessionDir = getDirectSessionDir(sessionId);
10
+ const checks = [];
11
+ const reasonCodes = [];
12
+ const blockingFindings = [];
13
+ const warnings = [];
14
+ // Check 1: session finalized
49
15
  checks.push({
50
- name: "sensitive_file_access",
51
- result: sensitiveFiles.length === 0 ? "pass" : "fail",
52
- detail: sensitiveFiles.length === 0
53
- ? "No sensitive files modified."
54
- : `Sensitive files modified: ${sensitiveFiles.join(", ")}`,
55
- reason_code: sensitiveFiles.length > 0 ? "sensitive_file_modified" : undefined,
16
+ name: "session_finalized",
17
+ result: session.finalized ? "pass" : "fail",
18
+ detail: session.finalized
19
+ ? "Session has been finalized."
20
+ : "Session has not been finalized. Call finalize_direct_session first.",
21
+ reason_code: session.finalized ? undefined : "session_not_finalized",
56
22
  });
57
- // Check 5: node_modules modification
58
- const nodeModulesChanges = findPathChanges(artifacts, "node_modules");
23
+ // Get change artifacts if available
24
+ const artifacts = session.change_artifacts;
25
+ // Check 2: diff empty
26
+ const changedFilesTotal = artifacts?.changed_files?.length ?? 0;
59
27
  checks.push({
60
- name: "node_modules_modified",
61
- result: nodeModulesChanges.length === 0 ? "pass" : "fail",
62
- detail: nodeModulesChanges.length === 0
63
- ? "No node_modules modifications."
64
- : `node_modules modified: ${nodeModulesChanges.join(", ")}`,
65
- reason_code: nodeModulesChanges.length > 0 ? "node_modules_modified" : undefined,
28
+ name: "diff_empty",
29
+ result: changedFilesTotal === 0 ? "warn" : "pass",
30
+ detail: changedFilesTotal === 0
31
+ ? "No file changes detected in this session."
32
+ : `${changedFilesTotal} file(s) changed.`,
33
+ reason_code: changedFilesTotal === 0 ? "empty_diff" : undefined,
66
34
  });
67
- // Check 6: release/dist modification
68
- const releaseChanges = findPathChanges(artifacts, "release");
69
- const distChanges = findPathChanges(artifacts, "dist");
70
- const artifactDirChanges = [...releaseChanges, ...distChanges];
71
- const hasBuildArtifacts = artifacts.artifact_hygiene.counts.tracked_build_artifacts > 0;
35
+ if (artifacts) {
36
+ // Check 3: out-of-scope changes
37
+ const outOfScope = findOutOfScopeChanges(artifacts, session.resolved_repo_path);
38
+ checks.push({
39
+ name: "out_of_scope_changes",
40
+ result: outOfScope.length === 0 ? "pass" : "fail",
41
+ detail: outOfScope.length === 0
42
+ ? "All changes are within the session repo_path."
43
+ : `${outOfScope.length} file(s) modified outside session repo_path: ${outOfScope.join(", ")}`,
44
+ reason_code: outOfScope.length > 0 ? "out_of_scope_changes" : undefined,
45
+ });
46
+ // Check 4: sensitive files
47
+ const sensitiveFiles = findSensitiveChanges(artifacts);
48
+ checks.push({
49
+ name: "sensitive_file_access",
50
+ result: sensitiveFiles.length === 0 ? "pass" : "fail",
51
+ detail: sensitiveFiles.length === 0
52
+ ? "No sensitive files modified."
53
+ : `Sensitive files modified: ${sensitiveFiles.join(", ")}`,
54
+ reason_code: sensitiveFiles.length > 0 ? "sensitive_file_modified" : undefined,
55
+ });
56
+ checks.push({
57
+ name: "sensitive_content",
58
+ result: artifacts.diff_redacted ? "fail" : "pass",
59
+ detail: artifacts.diff_redacted
60
+ ? `Credential-like content was redacted from diff evidence (${(artifacts.diff_redaction_categories ?? []).join(", ") || "sensitive_content"}).`
61
+ : "No credential-like content was detected in diff evidence.",
62
+ reason_code: artifacts.diff_redacted ? "sensitive_content_detected" : undefined,
63
+ });
64
+ // Check 5: node_modules modification
65
+ const nodeModulesChanges = findPathChanges(artifacts, "node_modules");
66
+ checks.push({
67
+ name: "node_modules_modified",
68
+ result: nodeModulesChanges.length === 0 ? "pass" : "fail",
69
+ detail: nodeModulesChanges.length === 0
70
+ ? "No node_modules modifications."
71
+ : `node_modules modified: ${nodeModulesChanges.join(", ")}`,
72
+ reason_code: nodeModulesChanges.length > 0 ? "node_modules_modified" : undefined,
73
+ });
74
+ // Check 6: release/dist modification
75
+ const releaseChanges = findPathChanges(artifacts, "release");
76
+ const distChanges = findPathChanges(artifacts, "dist");
77
+ const artifactDirChanges = [...releaseChanges, ...distChanges];
78
+ const hasBuildArtifacts = artifacts.artifact_hygiene.counts.tracked_build_artifacts > 0;
79
+ checks.push({
80
+ name: "release_dist_modified",
81
+ result: artifactDirChanges.length === 0
82
+ ? "pass"
83
+ : hasBuildArtifacts
84
+ ? "warn"
85
+ : "fail",
86
+ detail: artifactDirChanges.length === 0
87
+ ? "No release/dist modifications."
88
+ : `release/dist modified: ${artifactDirChanges.join(", ")}${hasBuildArtifacts ? " (build-generated)" : " (not build-generated)"}`,
89
+ reason_code: artifactDirChanges.length > 0
90
+ ? hasBuildArtifacts
91
+ ? "build_artifact_modified"
92
+ : "release_dist_manually_modified"
93
+ : undefined,
94
+ });
95
+ // Check 7: file deletion
96
+ const deletedFiles = artifacts.changed_files.filter((f) => f.change === "deleted");
97
+ checks.push({
98
+ name: "file_deletion",
99
+ result: deletedFiles.length === 0 ? "pass" : "fail",
100
+ detail: deletedFiles.length === 0
101
+ ? "No files deleted."
102
+ : `Files deleted: ${deletedFiles.map((f) => f.path).join(", ")}`,
103
+ reason_code: deletedFiles.length > 0 ? "file_deleted" : undefined,
104
+ });
105
+ // Check 8: file rename
106
+ const renamedFiles = artifacts.changed_files.filter((f) => f.change === "renamed");
107
+ checks.push({
108
+ name: "file_rename",
109
+ result: renamedFiles.length === 0 ? "pass" : "fail",
110
+ detail: renamedFiles.length === 0
111
+ ? "No files renamed."
112
+ : `Files renamed: ${renamedFiles.map((f) => `${f.old_path} → ${f.path}`).join(", ")}`,
113
+ reason_code: renamedFiles.length > 0 ? "file_renamed" : undefined,
114
+ });
115
+ // Check 9: package-lock / dependency changes
116
+ const packageLockChanges = artifacts.changed_files.filter((f) => f.path === "package-lock.json" ||
117
+ f.path === "package.json" ||
118
+ f.path === "yarn.lock" ||
119
+ f.path === "pnpm-lock.yaml");
120
+ checks.push({
121
+ name: "dependency_changes",
122
+ result: packageLockChanges.length === 0 ? "pass" : "warn",
123
+ detail: packageLockChanges.length === 0
124
+ ? "No dependency file changes."
125
+ : `Dependency files changed: ${packageLockChanges.map((f) => f.path).join(", ")}`,
126
+ reason_code: packageLockChanges.length > 0 ? "dependency_file_changed" : undefined,
127
+ });
128
+ // Check 13: title vs changed files consistency (warning only)
129
+ if (session.title && changedFilesTotal > 0) {
130
+ checks.push({
131
+ name: "title_change_consistency",
132
+ result: "pass",
133
+ detail: `Session title: "${session.title}", ${changedFilesTotal} file(s) changed. (Semantic check not performed - deterministic audit only.)`,
134
+ reason_code: undefined,
135
+ });
136
+ }
137
+ else {
138
+ checks.push({
139
+ name: "title_change_consistency",
140
+ result: "pass",
141
+ detail: "No title provided or no changes to compare.",
142
+ });
143
+ }
144
+ // Check 14: suspicious changes
145
+ const suspiciousCount = artifacts.artifact_hygiene.counts.suspicious_changes;
146
+ checks.push({
147
+ name: "suspicious_changes",
148
+ result: suspiciousCount === 0 ? "pass" : "warn",
149
+ detail: suspiciousCount === 0
150
+ ? "No suspicious changes detected."
151
+ : `${suspiciousCount} suspicious change(s) detected.`,
152
+ reason_code: suspiciousCount > 0 ? "suspicious_changes" : undefined,
153
+ });
154
+ // Check 15: runtime generated files
155
+ const runtimeCount = artifacts.artifact_hygiene.counts.runtime_generated_files;
156
+ checks.push({
157
+ name: "runtime_generated_files",
158
+ result: runtimeCount === 0 ? "pass" : "warn",
159
+ detail: runtimeCount === 0
160
+ ? "No runtime-generated files detected."
161
+ : `${runtimeCount} runtime-generated file(s) detected.`,
162
+ reason_code: runtimeCount > 0 ? "runtime_generated_files" : undefined,
163
+ });
164
+ // Check 16: tracked build artifacts
165
+ const buildArtifactCount = artifacts.artifact_hygiene.counts.tracked_build_artifacts;
166
+ checks.push({
167
+ name: "tracked_build_artifacts",
168
+ result: buildArtifactCount === 0 ? "pass" : "warn",
169
+ detail: buildArtifactCount === 0
170
+ ? "No tracked build artifacts detected."
171
+ : `${buildArtifactCount} tracked build artifact(s) detected.`,
172
+ reason_code: buildArtifactCount > 0 ? "tracked_build_artifacts" : undefined,
173
+ });
174
+ }
175
+ // Check 10: at least one verification command run
176
+ const verificationRuns = session.verification_runs || [];
177
+ const hasVerification = verificationRuns.length > 0;
178
+ const hasSourceChanges = artifacts
179
+ ? artifacts.changed_files.some((f) => f.kind === "source")
180
+ : false;
72
181
  checks.push({
73
- name: "release_dist_modified",
74
- result: artifactDirChanges.length === 0
182
+ name: "verification_run",
183
+ result: hasVerification
75
184
  ? "pass"
76
- : hasBuildArtifacts
77
- ? "warn"
78
- : "fail",
79
- detail: artifactDirChanges.length === 0
80
- ? "No release/dist modifications."
81
- : `release/dist modified: ${artifactDirChanges.join(", ")}${hasBuildArtifacts ? " (build-generated)" : " (not build-generated)"}`,
82
- reason_code: artifactDirChanges.length > 0
83
- ? hasBuildArtifacts
84
- ? "build_artifact_modified"
85
- : "release_dist_manually_modified"
185
+ : hasSourceChanges
186
+ ? "fail"
187
+ : "warn",
188
+ detail: hasVerification
189
+ ? `${verificationRuns.length} verification command(s) run.`
190
+ : hasSourceChanges
191
+ ? "Source files were modified but no verification commands were run."
192
+ : "No verification commands were run (no source changes detected).",
193
+ reason_code: !hasVerification
194
+ ? hasSourceChanges
195
+ ? "source_changes_without_verification"
196
+ : "no_verification_run"
86
197
  : undefined,
87
198
  });
88
- // Check 7: file deletion
89
- const deletedFiles = artifacts.changed_files.filter((f) => f.change === "deleted");
90
- checks.push({
91
- name: "file_deletion",
92
- result: deletedFiles.length === 0 ? "pass" : "fail",
93
- detail: deletedFiles.length === 0
94
- ? "No files deleted."
95
- : `Files deleted: ${deletedFiles.map((f) => f.path).join(", ")}`,
96
- reason_code: deletedFiles.length > 0 ? "file_deleted" : undefined,
97
- });
98
- // Check 8: file rename
99
- const renamedFiles = artifacts.changed_files.filter((f) => f.change === "renamed");
100
- checks.push({
101
- name: "file_rename",
102
- result: renamedFiles.length === 0 ? "pass" : "fail",
103
- detail: renamedFiles.length === 0
104
- ? "No files renamed."
105
- : `Files renamed: ${renamedFiles.map((f) => `${f.old_path} → ${f.path}`).join(", ")}`,
106
- reason_code: renamedFiles.length > 0 ? "file_renamed" : undefined,
107
- });
108
- // Check 9: package-lock / dependency changes
109
- const packageLockChanges = artifacts.changed_files.filter((f) => f.path === "package-lock.json" ||
110
- f.path === "package.json" ||
111
- f.path === "yarn.lock" ||
112
- f.path === "pnpm-lock.yaml");
113
- checks.push({
114
- name: "dependency_changes",
115
- result: packageLockChanges.length === 0 ? "pass" : "warn",
116
- detail: packageLockChanges.length === 0
117
- ? "No dependency file changes."
118
- : `Dependency files changed: ${packageLockChanges.map((f) => f.path).join(", ")}`,
119
- reason_code: packageLockChanges.length > 0 ? "dependency_file_changed" : undefined,
120
- });
121
- // Check 13: title vs changed files consistency (warning only)
122
- if (session.title && changedFilesTotal > 0) {
199
+ // Check 11: verification commands passed
200
+ if (hasVerification) {
201
+ const allPassed = verificationRuns.every((r) => r.passed);
123
202
  checks.push({
124
- name: "title_change_consistency",
125
- result: "pass",
126
- detail: `Session title: "${session.title}", ${changedFilesTotal} file(s) changed. (Semantic check not performed - deterministic audit only.)`,
127
- reason_code: undefined,
203
+ name: "verification_passed",
204
+ result: allPassed ? "pass" : "fail",
205
+ detail: allPassed
206
+ ? "All verification commands passed."
207
+ : `${verificationRuns.filter((r) => !r.passed).length} verification command(s) failed.`,
208
+ reason_code: !allPassed ? "verification_failed" : undefined,
128
209
  });
129
210
  }
130
- else {
131
- checks.push({
132
- name: "title_change_consistency",
133
- result: "pass",
134
- detail: "No title provided or no changes to compare.",
135
- });
211
+ // Collect results
212
+ for (const check of checks) {
213
+ if (check.reason_code)
214
+ reasonCodes.push(check.reason_code);
215
+ if (check.result === "fail")
216
+ blockingFindings.push(`${check.name}: ${check.detail}`);
217
+ if (check.result === "warn")
218
+ warnings.push(`${check.name}: ${check.detail}`);
136
219
  }
137
- // Check 14: suspicious changes
138
- const suspiciousCount = artifacts.artifact_hygiene.counts.suspicious_changes;
139
- checks.push({
140
- name: "suspicious_changes",
141
- result: suspiciousCount === 0 ? "pass" : "warn",
142
- detail: suspiciousCount === 0
143
- ? "No suspicious changes detected."
144
- : `${suspiciousCount} suspicious change(s) detected.`,
145
- reason_code: suspiciousCount > 0 ? "suspicious_changes" : undefined,
146
- });
147
- // Check 15: runtime generated files
148
- const runtimeCount = artifacts.artifact_hygiene.counts.runtime_generated_files;
149
- checks.push({
150
- name: "runtime_generated_files",
151
- result: runtimeCount === 0 ? "pass" : "warn",
152
- detail: runtimeCount === 0
153
- ? "No runtime-generated files detected."
154
- : `${runtimeCount} runtime-generated file(s) detected.`,
155
- reason_code: runtimeCount > 0 ? "runtime_generated_files" : undefined,
156
- });
157
- // Check 16: tracked build artifacts
158
- const buildArtifactCount = artifacts.artifact_hygiene.counts.tracked_build_artifacts;
159
- checks.push({
160
- name: "tracked_build_artifacts",
161
- result: buildArtifactCount === 0 ? "pass" : "warn",
162
- detail: buildArtifactCount === 0
163
- ? "No tracked build artifacts detected."
164
- : `${buildArtifactCount} tracked build artifact(s) detected.`,
165
- reason_code: buildArtifactCount > 0 ? "tracked_build_artifacts" : undefined,
166
- });
167
- }
168
- // Check 10: at least one verification command run
169
- const verificationRuns = session.verification_runs || [];
170
- const hasVerification = verificationRuns.length > 0;
171
- const hasSourceChanges = artifacts
172
- ? artifacts.changed_files.some((f) => f.kind === "source")
173
- : false;
174
- checks.push({
175
- name: "verification_run",
176
- result: hasVerification
177
- ? "pass"
178
- : hasSourceChanges
179
- ? "fail"
180
- : "warn",
181
- detail: hasVerification
182
- ? `${verificationRuns.length} verification command(s) run.`
183
- : hasSourceChanges
184
- ? "Source files were modified but no verification commands were run."
185
- : "No verification commands were run (no source changes detected).",
186
- reason_code: !hasVerification
187
- ? hasSourceChanges
188
- ? "source_changes_without_verification"
189
- : "no_verification_run"
190
- : undefined,
220
+ // Determine decision
221
+ const hasFail = checks.some((c) => c.result === "fail");
222
+ const hasWarn = checks.some((c) => c.result === "warn");
223
+ const decision = hasFail ? "fail" : hasWarn ? "warn" : "pass";
224
+ const diffPath = join(sessionDir, "diff.patch");
225
+ const summaryPath = join(sessionDir, "summary.md");
226
+ const auditPath = join(sessionDir, "audit.md");
227
+ const output = {
228
+ session_id: sessionId,
229
+ decision,
230
+ reason_codes: reasonCodes,
231
+ blocking_findings: blockingFindings,
232
+ warnings,
233
+ evidence: {
234
+ changed_files_total: changedFilesTotal,
235
+ verification_runs: verificationRuns,
236
+ diff_path: diffPath,
237
+ summary_path: summaryPath,
238
+ audit_path: auditPath,
239
+ },
240
+ next_action: decision === "pass"
241
+ ? "Audit passed. Changes are safe to accept."
242
+ : decision === "warn"
243
+ ? "Audit completed with warnings. Review the warnings before accepting changes."
244
+ : "Audit failed. Review the blocking findings and create a new session to fix issues.",
245
+ };
246
+ // Write audit.json and audit.md
247
+ atomicWriteJsonFileSync(join(sessionDir, "audit.json"), output);
248
+ atomicWriteFileSync(auditPath, formatAuditMd(output, checks, session));
249
+ updateDirectSession(sessionId, { audited: true });
250
+ return output;
191
251
  });
192
- // Check 11: verification commands passed
193
- if (hasVerification) {
194
- const allPassed = verificationRuns.every((r) => r.passed);
195
- checks.push({
196
- name: "verification_passed",
197
- result: allPassed ? "pass" : "fail",
198
- detail: allPassed
199
- ? "All verification commands passed."
200
- : `${verificationRuns.filter((r) => !r.passed).length} verification command(s) failed.`,
201
- reason_code: !allPassed ? "verification_failed" : undefined,
202
- });
203
- }
204
- // Collect results
205
- for (const check of checks) {
206
- if (check.reason_code)
207
- reasonCodes.push(check.reason_code);
208
- if (check.result === "fail")
209
- blockingFindings.push(`${check.name}: ${check.detail}`);
210
- if (check.result === "warn")
211
- warnings.push(`${check.name}: ${check.detail}`);
212
- }
213
- // Determine decision
214
- const hasFail = checks.some((c) => c.result === "fail");
215
- const hasWarn = checks.some((c) => c.result === "warn");
216
- const decision = hasFail ? "fail" : hasWarn ? "warn" : "pass";
217
- const diffPath = join(sessionDir, "diff.patch");
218
- const summaryPath = join(sessionDir, "summary.md");
219
- const auditPath = join(sessionDir, "audit.md");
220
- const output = {
221
- session_id: sessionId,
222
- decision,
223
- reason_codes: reasonCodes,
224
- blocking_findings: blockingFindings,
225
- warnings,
226
- evidence: {
227
- changed_files_total: changedFilesTotal,
228
- verification_runs: verificationRuns,
229
- diff_path: diffPath,
230
- summary_path: summaryPath,
231
- audit_path: auditPath,
232
- },
233
- next_action: decision === "pass"
234
- ? "Audit passed. Changes are safe to accept."
235
- : decision === "warn"
236
- ? "Audit completed with warnings. Review the warnings before accepting changes."
237
- : "Audit failed. Review the blocking findings and create a new session to fix issues.",
238
- };
239
- // Write audit.json and audit.md
240
- writeFileSync(join(sessionDir, "audit.json"), JSON.stringify(output, null, 2), "utf-8");
241
- writeFileSync(auditPath, formatAuditMd(output, checks, session), "utf-8");
242
- return output;
243
252
  }
244
253
  // ── Helper functions ───────────────────────────────────────────────
245
254
  function findOutOfScopeChanges(artifacts, resolvedRepoPath) {
@@ -1,3 +1,4 @@
1
+ import { type PatchWardenConfig } from "../config.js";
1
2
  import type { DirectSessionRecord } from "./directSessionStore.js";
2
3
  export declare function guardDirectSessionActive(session: DirectSessionRecord): void;
3
4
  export declare function guardDirectSessionFinalized(session: DirectSessionRecord): void;
@@ -16,5 +17,5 @@ export declare function guardDirectReadPath(filePath: string, resolvedRepoPath:
16
17
  */
17
18
  export declare function guardDirectWritePath(filePath: string, resolvedRepoPath: string, workspaceRoot: string): string;
18
19
  export declare function guardDirectPatchSize(patchBytes: number): void;
19
- export declare function guardDirectFileSize(fileBytes: number): void;
20
+ export declare function guardDirectFileSize(fileBytes: number, config?: PatchWardenConfig): void;
20
21
  export declare function isBinaryFile(filePath: string): boolean;
@@ -1,7 +1,8 @@
1
- import { resolve, relative, isAbsolute } from "node:path";
1
+ import { relative } from "node:path";
2
2
  import { existsSync, statSync, openSync, readSync, closeSync } from "node:fs";
3
3
  import { PatchWardenError } from "../errors.js";
4
- import { isSensitivePath } from "../security/sensitiveGuard.js";
4
+ import { hasWindowsAlternateDataStream, isSensitivePath, } from "../security/sensitiveGuard.js";
5
+ import { guardPath, guardWorkspacePath } from "../security/pathGuard.js";
5
6
  import { getConfig } from "../config.js";
6
7
  // ── Session state guards ───────────────────────────────────────────
7
8
  export function guardDirectSessionActive(session) {
@@ -23,34 +24,34 @@ export function guardDirectSessionFinalized(session) {
23
24
  * inside both the repo and the workspace root.
24
25
  */
25
26
  export function guardDirectPath(filePath, resolvedRepoPath, workspaceRoot) {
26
- const config = getConfig();
27
- const resolved = resolve(resolvedRepoPath, filePath);
28
- const normalizedRepo = resolve(resolvedRepoPath);
29
- const normalizedWs = resolve(workspaceRoot);
30
- // Check path is inside workspace root
31
- const relToWs = relative(normalizedWs, resolved);
32
- if (isAbsolute(relToWs) || relToWs.startsWith("..")) {
33
- throw new PatchWardenError("path_outside_repo", `Path "${filePath}" resolves outside the session repo.`, "Use a relative path inside the session's repo_path.", true, { path: filePath, operation: "direct_path_access" });
34
- }
35
- // Check path is inside repo_path
36
- const relToRepo = relative(normalizedRepo, resolved);
37
- if (isAbsolute(relToRepo) || relToRepo.startsWith("..")) {
38
- throw new PatchWardenError("path_outside_repo", `Path "${filePath}" is outside the session repo_path "${resolvedRepoPath}".`, "Use a relative path inside the session's repo_path.", true, { path: filePath, operation: "direct_path_access" });
27
+ if (hasWindowsAlternateDataStream(filePath)) {
28
+ throw new PatchWardenError("windows_ads_path_blocked", `Path "${filePath}" contains a Windows alternate data stream suffix.`, "Use a normal relative file path without a colon or NTFS stream name.", true, { path: filePath, operation: "direct_path_access" });
29
+ }
30
+ // Resolve the repo first so a repo-level symlink/junction cannot escape the
31
+ // workspace. guardPath then resolves the candidate or its nearest existing
32
+ // parent, closing intermediate symlink/junction escapes for new files too.
33
+ try {
34
+ const guardedRepo = guardWorkspacePath(resolvedRepoPath, workspaceRoot);
35
+ return guardPath(filePath, guardedRepo);
36
+ }
37
+ catch (error) {
38
+ if (error instanceof PatchWardenError && error.reason === "workspace_path_escape") {
39
+ throw new PatchWardenError("path_outside_repo", `Path "${filePath}" resolves outside the session repo.`, "Use a relative path inside the session's repo_path.", true, { path: filePath, operation: "direct_path_access" });
40
+ }
41
+ throw error;
39
42
  }
40
- return resolved;
41
43
  }
42
44
  /**
43
45
  * Guard a read path: must be inside repo, not sensitive, not binary.
44
46
  */
45
47
  export function guardDirectReadPath(filePath, resolvedRepoPath, workspaceRoot) {
46
48
  const resolved = guardDirectPath(filePath, resolvedRepoPath, workspaceRoot);
47
- const normalized = filePath.replace(/\\/g, "/");
49
+ const policyPaths = directPolicyPaths(filePath, resolved, resolvedRepoPath, workspaceRoot);
48
50
  // Block internal PatchWarden paths (sessions, tasks, plans, assessments)
49
- if (normalized.startsWith(".patchwarden/") ||
50
- normalized.includes("/.patchwarden/")) {
51
+ if (policyPaths.some((path) => hasPathSegment(path, ".patchwarden"))) {
51
52
  throw new PatchWardenError("internal_patchwarden_path_blocked", `Access denied: "${filePath}" is inside the internal .patchwarden directory.`, "Internal PatchWarden files cannot be accessed through Direct mode.", true, { path: filePath, operation: "direct_read" });
52
53
  }
53
- if (isSensitivePath(filePath)) {
54
+ if (policyPaths.some((path) => isSensitivePath(path))) {
54
55
  throw new PatchWardenError("sensitive_path_blocked", `Access denied: "${filePath}" matches a sensitive file pattern.`, "Read only non-sensitive files within the session repo.", true, { path: filePath, operation: "direct_read" });
55
56
  }
56
57
  if (existsSync(resolved) && statSync(resolved).isFile() && isBinaryFile(resolved)) {
@@ -64,25 +65,24 @@ export function guardDirectReadPath(filePath, resolvedRepoPath, workspaceRoot) {
64
65
  */
65
66
  export function guardDirectWritePath(filePath, resolvedRepoPath, workspaceRoot) {
66
67
  const resolved = guardDirectPath(filePath, resolvedRepoPath, workspaceRoot);
67
- const normalized = filePath.replace(/\\/g, "/");
68
+ const policyPaths = directPolicyPaths(filePath, resolved, resolvedRepoPath, workspaceRoot);
68
69
  // Block internal PatchWarden paths
69
- if (normalized.startsWith(".patchwarden/") ||
70
- normalized.includes("/.patchwarden/")) {
70
+ if (policyPaths.some((path) => hasPathSegment(path, ".patchwarden"))) {
71
71
  throw new PatchWardenError("internal_patchwarden_path_blocked", `Modification denied: "${filePath}" is inside the internal .patchwarden directory.`, "Internal PatchWarden files cannot be modified through Direct mode.", true, { path: filePath, operation: "direct_write" });
72
72
  }
73
- if (isSensitivePath(normalized)) {
73
+ if (policyPaths.some((path) => isSensitivePath(path))) {
74
74
  throw new PatchWardenError("sensitive_path_blocked", `Modification denied: "${filePath}" matches a sensitive file pattern.`, "Do not modify .env, credentials, keys, or other sensitive files.", true, { path: filePath, operation: "direct_write" });
75
75
  }
76
76
  // Block node_modules
77
- if (normalized.startsWith("node_modules/") || normalized.includes("/node_modules/")) {
77
+ if (policyPaths.some((path) => hasPathSegment(path, "node_modules"))) {
78
78
  throw new PatchWardenError("blocked_artifact_path", `Modification denied: "${filePath}" is inside node_modules.`, "Do not modify dependencies directly. Use package management tools instead.", true, { path: filePath, operation: "direct_write" });
79
79
  }
80
80
  // Block release/
81
- if (normalized.startsWith("release/") || normalized.includes("/release/")) {
81
+ if (policyPaths.some((path) => hasPathSegment(path, "release"))) {
82
82
  throw new PatchWardenError("blocked_artifact_path", `Modification denied: "${filePath}" is inside the release directory.`, "Do not manually modify release artifacts.", true, { path: filePath, operation: "direct_write" });
83
83
  }
84
84
  // Block dist/
85
- if (normalized.startsWith("dist/") || normalized.includes("/dist/")) {
85
+ if (policyPaths.some((path) => hasPathSegment(path, "dist"))) {
86
86
  throw new PatchWardenError("blocked_artifact_path", `Modification denied: "${filePath}" is inside the dist directory.`, "Do not manually modify build output. Modify source files instead.", true, { path: filePath, operation: "direct_write" });
87
87
  }
88
88
  if (existsSync(resolved) && statSync(resolved).isFile() && isBinaryFile(resolved)) {
@@ -90,6 +90,20 @@ export function guardDirectWritePath(filePath, resolvedRepoPath, workspaceRoot)
90
90
  }
91
91
  return resolved;
92
92
  }
93
+ function directPolicyPaths(requestedPath, resolvedPath, resolvedRepoPath, workspaceRoot) {
94
+ const guardedRepo = guardWorkspacePath(resolvedRepoPath, workspaceRoot);
95
+ return [
96
+ requestedPath.replace(/\\/g, "/"),
97
+ relative(guardedRepo, resolvedPath).replace(/\\/g, "/"),
98
+ ];
99
+ }
100
+ function hasPathSegment(filePath, expectedSegment) {
101
+ const expected = expectedSegment.toLowerCase();
102
+ return filePath
103
+ .split("/")
104
+ .filter(Boolean)
105
+ .some((segment) => segment.toLowerCase() === expected);
106
+ }
93
107
  // ── Size guards ────────────────────────────────────────────────────
94
108
  export function guardDirectPatchSize(patchBytes) {
95
109
  const config = getConfig();
@@ -97,8 +111,7 @@ export function guardDirectPatchSize(patchBytes) {
97
111
  throw new PatchWardenError("patch_too_large", `Patch size ${patchBytes} bytes exceeds maximum ${config.directMaxPatchBytes} bytes.`, "Split the patch into smaller operations or reduce the patch size.", true, { patch_bytes: patchBytes, max_bytes: config.directMaxPatchBytes });
98
112
  }
99
113
  }
100
- export function guardDirectFileSize(fileBytes) {
101
- const config = getConfig();
114
+ export function guardDirectFileSize(fileBytes, config = getConfig()) {
102
115
  if (fileBytes > config.directMaxFileBytes) {
103
116
  throw new PatchWardenError("file_too_large", `File size ${fileBytes} bytes exceeds maximum ${config.directMaxFileBytes} bytes.`, "Use search_workspace to find specific content, or split the read into smaller portions.", true, { file_bytes: fileBytes, max_bytes: config.directMaxFileBytes });
104
117
  }
@@ -13,5 +13,10 @@ export interface ApplyPatchResult {
13
13
  }
14
14
  export declare function computeFileSha256(filePath: string): string;
15
15
  export declare function computeContentSha256(content: string): string;
16
+ export interface ApplyPatchOptions {
17
+ expectedSha256?: string;
18
+ maxFileBytes?: number;
19
+ revalidatePath?: () => string;
20
+ }
16
21
  export declare function validateExpectedSha256(filePath: string, expectedSha256: string): string;
17
- export declare function applyPatchOperations(filePath: string, operations: PatchOperation[]): ApplyPatchResult;
22
+ export declare function applyPatchOperations(filePath: string, operations: PatchOperation[], options?: ApplyPatchOptions): ApplyPatchResult;