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,12 +1,18 @@
1
- import { mkdirSync, writeFileSync, existsSync, readFileSync, renameSync, } from "node:fs";
1
+ import { mkdirSync, existsSync, readFileSync, } from "node:fs";
2
2
  import { createHash, randomBytes } from "node:crypto";
3
3
  import { resolve, join } from "node:path";
4
4
  import { getAssessmentsDir, getConfig, getRepoAllowedTestCommands } from "../config.js";
5
5
  import { guardPath } from "../security/pathGuard.js";
6
6
  import { PatchWardenError } from "../errors.js";
7
+ import { redactSensitiveContent } from "../security/contentRedaction.js";
8
+ import { atomicWriteJsonFileSync } from "../utils/atomicFile.js";
9
+ import { withFileLockSync } from "../utils/lockedJsonFile.js";
7
10
  import { TOOL_SCHEMA_EPOCH } from "../version.js";
8
- import { getLastToolCatalogSnapshot } from "../tools/toolCatalog.js";
11
+ import { buildToolCatalogSnapshot, resolveToolProfile, } from "../tools/catalog/toolCatalog.js";
12
+ import { getToolDefs } from "../tools/definitions/toolDefs.js";
9
13
  import { captureRepoSnapshot } from "../runner/changeCapture.js";
14
+ import { getProjectPolicySummary } from "../policy/projectPolicy.js";
15
+ import { ASSESSMENT_SECURITY_SNAPSHOT_VERSION, buildAssessmentSecuritySnapshot, getAssessmentSecuritySnapshotComponentHashes, hashAssessmentSecuritySnapshot, } from "./securitySnapshot.js";
10
16
  const SENSITIVE_PATH_RULES_SIGNATURE = "v1";
11
17
  const ARTIFACT_RULES_SIGNATURE = "v1";
12
18
  export function generateAssessmentId() {
@@ -32,7 +38,7 @@ export function createAssessment(input) {
32
38
  const planHash = input.plan_content
33
39
  ? createHash("sha256").update(input.plan_content).digest("hex")
34
40
  : null;
35
- const toolManifest = getLastToolCatalogSnapshot()?.tool_manifest_sha256 || computeFallbackManifestHash(config);
41
+ const toolManifest = getCurrentToolManifest(config);
36
42
  const workspaceFingerprint = computeWorkspaceFingerprint(input.snapshot);
37
43
  const snapshotTruncated = input.snapshot.warnings.some((w) => w.includes("snapshot limited"));
38
44
  const policyHash = computePolicyHash({
@@ -45,6 +51,21 @@ export function createAssessment(input) {
45
51
  artifact_rules: ARTIFACT_RULES_SIGNATURE,
46
52
  schema_epoch: TOOL_SCHEMA_EPOCH,
47
53
  });
54
+ const executionConfig = computeExecutionConfigFingerprint(config, {
55
+ agent: input.agent,
56
+ tool_manifest_sha256: toolManifest,
57
+ tool_profile: resolveToolProfile(config.toolProfile),
58
+ repo_path: input.resolved_repo_path,
59
+ change_policy: input.change_policy || "repo_scoped_changes",
60
+ template: input.template || null,
61
+ verify_commands: input.verify_commands || [],
62
+ test_command: input.test_command || null,
63
+ task_timeout_seconds: input.timeout_seconds,
64
+ scope: input.scope,
65
+ forbidden: input.forbidden,
66
+ verification: input.verification,
67
+ done_evidence: input.done_evidence,
68
+ });
48
69
  const now = new Date();
49
70
  const expiresAt = new Date(now.getTime() + config.assessmentTtlSeconds * 1000).toISOString();
50
71
  const record = {
@@ -59,6 +80,12 @@ export function createAssessment(input) {
59
80
  plan_id: input.plan_id,
60
81
  policy_hash: policyHash,
61
82
  tool_manifest_sha256: toolManifest,
83
+ execution_config_hash: executionConfig.hash,
84
+ execution_config_component_hashes: executionConfig.components,
85
+ assessment_security_snapshot_version: ASSESSMENT_SECURITY_SNAPSHOT_VERSION,
86
+ assessment_security_snapshot_sha256: executionConfig.hash,
87
+ assessment_security_snapshot_component_hashes: executionConfig.components,
88
+ assessment_schema_epoch: TOOL_SCHEMA_EPOCH,
62
89
  workspace_fingerprint: workspaceFingerprint,
63
90
  workspace_snapshot_summary: {
64
91
  head: input.snapshot.head,
@@ -71,22 +98,30 @@ export function createAssessment(input) {
71
98
  repo_path: input.repo_path,
72
99
  resolved_repo_path: input.resolved_repo_path,
73
100
  template: input.template || null,
74
- goal: input.goal || null,
101
+ goal: input.goal ? redactSensitiveContent(input.goal).content : null,
75
102
  test_command: input.test_command || null,
76
103
  verify_commands: input.verify_commands || [],
77
104
  agent: input.agent,
78
105
  timeout_seconds: input.timeout_seconds,
79
106
  change_policy: input.change_policy || "repo_scoped_changes",
107
+ scope: input.scope || [],
108
+ forbidden: input.forbidden || [],
109
+ verification: input.verification || [],
110
+ done_evidence: input.done_evidence || [],
80
111
  requires_confirm: input.decision === "needs_confirm",
81
112
  confirmed: false,
82
113
  confirmed_at: null,
83
114
  confirm_code: null,
115
+ used_at: null,
84
116
  agent_assessment_summary: input.agent_assessment_summary || null,
85
117
  };
86
- writeFileSync(join(dir, "assessment.json"), JSON.stringify(record, null, 2), "utf-8");
118
+ atomicWriteJsonFileSync(join(dir, "assessment.json"), record);
87
119
  return record;
88
120
  }
89
121
  export function readAssessment(assessmentId) {
122
+ if (!/^assessment_\d{8}_\d{6}_[0-9a-f]{32}$/.test(assessmentId)) {
123
+ throw new PatchWardenError("assessment_id_invalid", "A full assessment_id with 32 hexadecimal random characters is required.", "Copy the complete assessment_id from the assess_only response; assessment_short_id is display-only.");
124
+ }
90
125
  const config = getConfig();
91
126
  const assessmentsDir = getAssessmentsDir(config);
92
127
  const dir = resolve(assessmentsDir, assessmentId);
@@ -95,9 +130,36 @@ export function readAssessment(assessmentId) {
95
130
  if (!existsSync(file)) {
96
131
  throw new PatchWardenError("assessment_not_found", `Assessment "${assessmentId}" not found.`, "Call create_task with execution_mode=assess_only first to get a valid assessment_id.", true, { assessment_id: assessmentId });
97
132
  }
98
- return JSON.parse(readFileSync(file, "utf-8"));
133
+ try {
134
+ const parsed = JSON.parse(readFileSync(file, "utf-8"));
135
+ if (!parsed || typeof parsed !== "object" || parsed.assessment_id !== assessmentId) {
136
+ throw new Error("record identity mismatch");
137
+ }
138
+ return parsed;
139
+ }
140
+ catch (error) {
141
+ throw new PatchWardenError("assessment_corrupted", `Assessment "${assessmentId}" cannot be read as a valid record.`, "Run create_task with execution_mode=assess_only again.", true, { assessment_id: assessmentId, failure_category: "invalid_assessment_record" });
142
+ }
143
+ }
144
+ /** Atomically consume an Assessment before creating its one allowed task. */
145
+ export function markAssessmentUsed(assessmentId) {
146
+ const config = getConfig();
147
+ const assessmentFile = resolve(getAssessmentsDir(config), assessmentId, "assessment.json");
148
+ guardPath(assessmentFile, config.workspaceRoot, config.assessmentsDir);
149
+ return withFileLockSync(assessmentFile, () => {
150
+ const assessment = readAssessment(assessmentId);
151
+ if (assessment.used_at) {
152
+ throw new PatchWardenError("assessment_used", `Assessment "${assessmentId}" has already been used.`, "Run create_task with execution_mode=assess_only again to obtain a new assessment_id.", true, { assessment_id: assessmentId, used_at: assessment.used_at });
153
+ }
154
+ const consumed = { ...assessment, used_at: new Date().toISOString() };
155
+ atomicWriteJsonFileSync(assessmentFile, consumed);
156
+ return consumed;
157
+ });
99
158
  }
100
159
  export function validateAssessmentFreshness(assessmentId, currentSnapshot, options = {}) {
160
+ if (!/^assessment_\d{8}_\d{6}_[0-9a-f]{32}$/.test(assessmentId)) {
161
+ return { valid: false, failure_reason: "assessment_id_invalid", assessment: null };
162
+ }
101
163
  let assessment;
102
164
  try {
103
165
  assessment = readAssessment(assessmentId);
@@ -121,18 +183,70 @@ export function validateAssessmentFreshness(assessmentId, currentSnapshot, optio
121
183
  return { valid: false, failure_reason: "assessment_stale_plan", assessment };
122
184
  }
123
185
  }
186
+ const config = getConfig();
187
+ const currentManifest = getCurrentToolManifest(config);
124
188
  if (!options.skip_tool_manifest) {
125
- const currentManifest = getLastToolCatalogSnapshot()?.tool_manifest_sha256 ||
126
- computeFallbackManifestHash(getConfig());
127
- if (currentManifest !== assessment.tool_manifest_sha256) {
128
- return { valid: false, failure_reason: "assessment_stale_config", assessment };
189
+ // Legacy records need the standalone manifest check. Versioned security
190
+ // snapshots compare the manifest together with the source config, so the
191
+ // diagnostic can report both "tool_manifest" and e.g. "allowed_commands".
192
+ if (currentManifest !== assessment.tool_manifest_sha256 && !assessment.assessment_security_snapshot_sha256) {
193
+ return {
194
+ valid: false,
195
+ failure_reason: "assessment_stale_config",
196
+ assessment,
197
+ expected_hash: assessment.tool_manifest_sha256,
198
+ actual_hash: currentManifest,
199
+ config_change_categories: ["tool_manifest"],
200
+ };
129
201
  }
130
202
  }
131
203
  const currentFingerprint = computeWorkspaceFingerprint(currentSnapshot);
132
204
  if (currentFingerprint !== assessment.workspace_fingerprint) {
133
205
  return { valid: false, failure_reason: "assessment_workspace_changed", assessment };
134
206
  }
135
- const config = getConfig();
207
+ if (assessment.execution_config_hash) {
208
+ const currentExecutionConfig = computeExecutionConfigFingerprint(config, {
209
+ agent: assessment.agent,
210
+ tool_manifest_sha256: currentManifest,
211
+ tool_profile: resolveToolProfile(config.toolProfile),
212
+ repo_path: assessment.resolved_repo_path,
213
+ change_policy: assessment.change_policy || "repo_scoped_changes",
214
+ template: assessment.template || null,
215
+ verify_commands: assessment.verify_commands || [],
216
+ test_command: assessment.test_command || null,
217
+ task_timeout_seconds: assessment.timeout_seconds,
218
+ scope: assessment.scope,
219
+ forbidden: assessment.forbidden,
220
+ verification: assessment.verification,
221
+ done_evidence: assessment.done_evidence,
222
+ });
223
+ if (assessment.assessment_security_snapshot_version
224
+ && assessment.assessment_security_snapshot_version !== ASSESSMENT_SECURITY_SNAPSHOT_VERSION) {
225
+ return {
226
+ valid: false,
227
+ failure_reason: "assessment_snapshot_version_incompatible",
228
+ assessment,
229
+ expected_hash: assessment.execution_config_hash,
230
+ actual_hash: currentExecutionConfig.hash,
231
+ config_change_categories: ["schema_epoch"],
232
+ };
233
+ }
234
+ if (currentExecutionConfig.hash !== assessment.execution_config_hash) {
235
+ const previousComponents = assessment.execution_config_component_hashes;
236
+ const changedCategories = previousComponents
237
+ ? Object.keys(currentExecutionConfig.components)
238
+ .filter((category) => previousComponents[category] !== currentExecutionConfig.components[category])
239
+ : ["execution_config"];
240
+ return {
241
+ valid: false,
242
+ failure_reason: "assessment_stale_config",
243
+ assessment,
244
+ expected_hash: assessment.execution_config_hash,
245
+ actual_hash: currentExecutionConfig.hash,
246
+ config_change_categories: changedCategories.length > 0 ? changedCategories : ["execution_config"],
247
+ };
248
+ }
249
+ }
136
250
  const currentPolicyHash = computePolicyHash({
137
251
  change_policy: assessment.change_policy || "repo_scoped_changes",
138
252
  template: assessment.template || null,
@@ -160,6 +274,9 @@ export async function confirmAssessment(assessmentId) {
160
274
  if (!/^assessment_\d{8}_\d{6}_[0-9a-f]{32}$/.test(assessmentId)) {
161
275
  throw new PatchWardenError("assessment_id_invalid", "Local confirmation requires the full assessment_id (32 hexadecimal random characters).", "Copy the complete assessment_id from the assess_only response; assessment_short_id is display-only.");
162
276
  }
277
+ const config = getConfig();
278
+ const assessmentFile = resolve(getAssessmentsDir(config), assessmentId, "assessment.json");
279
+ guardPath(assessmentFile, config.workspaceRoot, config.assessmentsDir);
163
280
  const assessment = readAssessment(assessmentId);
164
281
  if (assessment.decision !== "needs_confirm" || !assessment.requires_confirm) {
165
282
  throw new PatchWardenError("assessment_confirmation_not_allowed", `Assessment "${assessmentId}" has decision "${assessment.decision}" and cannot be locally confirmed.`, assessment.decision === "blocked"
@@ -167,37 +284,39 @@ export async function confirmAssessment(assessmentId) {
167
284
  : "This assessment does not require confirmation; execute it with the minimal assessment_id call.");
168
285
  }
169
286
  const snapshot = await captureRepoSnapshot(assessment.resolved_repo_path);
170
- const validation = validateAssessmentFreshness(assessmentId, snapshot, {
171
- allow_unconfirmed: true,
172
- // A standalone CLI has no active MCP profile snapshot. The execute path
173
- // still performs the full tool-manifest check before creating the task.
174
- skip_tool_manifest: true,
287
+ // Re-read and write while holding one cross-process lock. Concurrent local
288
+ // confirmers may compute snapshots in parallel, but they cannot replace one
289
+ // another's confirmation metadata from a stale assessment record.
290
+ return withFileLockSync(assessmentFile, () => {
291
+ const validation = validateAssessmentFreshness(assessmentId, snapshot, {
292
+ allow_unconfirmed: true,
293
+ // A standalone CLI has no active MCP profile snapshot. The execute path
294
+ // still performs the full tool-manifest check before creating the task.
295
+ skip_tool_manifest: true,
296
+ });
297
+ if (!validation.valid || !validation.assessment) {
298
+ throw new PatchWardenError(validation.failure_reason || "assessment_validation_failed", `Assessment "${assessmentId}" cannot be confirmed: ${validation.failure_reason || "validation_failed"}.`, "Run create_task with execution_mode=assess_only again, then confirm the new assessment locally.");
299
+ }
300
+ if (validation.assessment.decision !== "needs_confirm" || !validation.assessment.requires_confirm) {
301
+ throw new PatchWardenError("assessment_confirmation_not_allowed", `Assessment "${assessmentId}" is no longer eligible for local confirmation.`, "Run create_task with execution_mode=assess_only again.");
302
+ }
303
+ const confirmedAt = validation.assessment.confirmed_at || new Date().toISOString();
304
+ const confirmedRecord = {
305
+ ...validation.assessment,
306
+ confirmed: true,
307
+ confirmed_at: confirmedAt,
308
+ confirm_code: validation.assessment.confirm_code || randomBytes(8).toString("hex"),
309
+ };
310
+ atomicWriteJsonFileSync(assessmentFile, confirmedRecord);
311
+ return {
312
+ assessment_id: assessmentId,
313
+ decision: "needs_confirm",
314
+ confirmed: true,
315
+ confirmed_at: confirmedAt,
316
+ expires_at: confirmedRecord.expires_at,
317
+ next_action: "Call create_task with only execution_mode=execute and this full assessment_id.",
318
+ };
175
319
  });
176
- if (!validation.valid || !validation.assessment) {
177
- throw new PatchWardenError(validation.failure_reason || "assessment_validation_failed", `Assessment "${assessmentId}" cannot be confirmed: ${validation.failure_reason || "validation_failed"}.`, "Run create_task with execution_mode=assess_only again, then confirm the new assessment locally.");
178
- }
179
- const confirmedAt = validation.assessment.confirmed_at || new Date().toISOString();
180
- const confirmedRecord = {
181
- ...validation.assessment,
182
- confirmed: true,
183
- confirmed_at: confirmedAt,
184
- confirm_code: validation.assessment.confirm_code || randomBytes(8).toString("hex"),
185
- };
186
- const config = getConfig();
187
- const assessmentFile = resolve(getAssessmentsDir(config), assessmentId, "assessment.json");
188
- guardPath(assessmentFile, config.workspaceRoot, config.assessmentsDir);
189
- const temporaryFile = `${assessmentFile}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
190
- guardPath(temporaryFile, config.workspaceRoot, config.assessmentsDir);
191
- writeFileSync(temporaryFile, JSON.stringify(confirmedRecord, null, 2), "utf-8");
192
- renameSync(temporaryFile, assessmentFile);
193
- return {
194
- assessment_id: assessmentId,
195
- decision: "needs_confirm",
196
- confirmed: true,
197
- confirmed_at: confirmedAt,
198
- expires_at: confirmedRecord.expires_at,
199
- next_action: "Call create_task with only execution_mode=execute and this full assessment_id.",
200
- };
201
320
  }
202
321
  export function computeWorkspaceFingerprint(snapshot) {
203
322
  const fileHashes = Object.entries(snapshot.files)
@@ -224,10 +343,53 @@ export function computePolicyHash(input) {
224
343
  function getRepoAllowedCommands(config, repoPath) {
225
344
  return getRepoAllowedTestCommands(config, repoPath);
226
345
  }
227
- function computeFallbackManifestHash(config) {
228
- return createHash("sha256")
229
- .update(`${TOOL_SCHEMA_EPOCH}:${config.toolProfile || "full"}`)
230
- .digest("hex");
346
+ function getCurrentToolManifest(config) {
347
+ // The catalog cache is process-local. Rebuild the canonical catalog here so
348
+ // MCP and the independent Watcher derive the same manifest across processes.
349
+ const profile = resolveToolProfile(config.toolProfile);
350
+ return buildToolCatalogSnapshot(getToolDefs(), profile).tool_manifest_sha256;
351
+ }
352
+ export function computeExecutionConfigHash(config, input) {
353
+ return computeExecutionConfigFingerprint(config, input).hash;
354
+ }
355
+ function computeExecutionConfigFingerprint(config, input) {
356
+ let projectPolicy = null;
357
+ let projectPolicyValid = null;
358
+ let projectPolicyIssues = [];
359
+ try {
360
+ const summary = getProjectPolicySummary(input.repo_path);
361
+ projectPolicy = summary.effective_policy;
362
+ projectPolicyValid = summary.valid;
363
+ projectPolicyIssues = summary.issues.map(({ code, severity, field }) => ({ code, severity, field }));
364
+ }
365
+ catch {
366
+ // A workspace-root change can make the old repository inaccessible. The
367
+ // workspace_root component still fails closed without exposing the path.
368
+ }
369
+ const snapshot = buildAssessmentSecuritySnapshot({
370
+ config,
371
+ schemaEpoch: TOOL_SCHEMA_EPOCH,
372
+ toolProfile: input.tool_profile,
373
+ toolManifestSha256: input.tool_manifest_sha256,
374
+ agent: input.agent,
375
+ repoPath: input.repo_path,
376
+ changePolicy: input.change_policy,
377
+ template: input.template,
378
+ verifyCommands: input.verify_commands,
379
+ testCommand: input.test_command,
380
+ taskTimeoutSeconds: input.task_timeout_seconds,
381
+ scope: input.scope,
382
+ forbidden: input.forbidden,
383
+ verification: input.verification,
384
+ doneEvidence: input.done_evidence,
385
+ projectPolicy,
386
+ projectPolicyValid,
387
+ projectPolicyIssues,
388
+ });
389
+ return {
390
+ hash: hashAssessmentSecuritySnapshot(snapshot),
391
+ components: getAssessmentSecuritySnapshotComponentHashes(snapshot),
392
+ };
231
393
  }
232
394
  function extractReason(error) {
233
395
  if (error instanceof PatchWardenError)
@@ -0,0 +1,50 @@
1
+ import type { PatchWardenConfig } from "../config.js";
2
+ import type { ProjectPolicy } from "../policy/projectPolicy.js";
3
+ /**
4
+ * Version the security snapshot independently from the server/schema version.
5
+ * Changing the fields or their canonicalization should invalidate old records.
6
+ */
7
+ export declare const ASSESSMENT_SECURITY_SNAPSHOT_VERSION = "assessment-security-v2";
8
+ export type SecuritySnapshotCategory = "schema_epoch" | "tool_profile" | "tool_manifest" | "workspace_root" | "repo_boundary" | "agent_launch" | "allowed_commands" | "repo_allowed_test_commands" | "direct_allowed_commands" | "repo_direct_allowed_commands" | "sensitive_path_rules" | "protected_paths" | "project_policy" | "risk_rules" | "confirmation_policy" | "task_parameters" | "release_protection" | "direct_profile" | "assessment_ttl" | "timeout_policy";
9
+ export interface AssessmentSecuritySnapshotInput {
10
+ config: PatchWardenConfig;
11
+ schemaEpoch: string;
12
+ toolProfile: string;
13
+ toolManifestSha256: string;
14
+ agent: string;
15
+ repoPath: string;
16
+ changePolicy?: string | null;
17
+ template?: string | null;
18
+ verifyCommands?: string[];
19
+ testCommand?: string | null;
20
+ taskTimeoutSeconds?: number | null;
21
+ scope?: string[];
22
+ forbidden?: string[];
23
+ verification?: string[];
24
+ doneEvidence?: string[];
25
+ riskRulesVersion?: string;
26
+ projectPolicy?: ProjectPolicy | null;
27
+ projectPolicyValid?: boolean | null;
28
+ projectPolicyIssues?: Array<{
29
+ code: string;
30
+ severity: string;
31
+ field: string;
32
+ }>;
33
+ }
34
+ export interface AssessmentSecuritySnapshot {
35
+ assessment_security_snapshot_version: string;
36
+ components: Record<SecuritySnapshotCategory, unknown>;
37
+ }
38
+ export interface AssessmentSecuritySnapshotComparison {
39
+ equal: boolean;
40
+ expected_hash: string;
41
+ actual_hash: string;
42
+ changed_field_names: SecuritySnapshotCategory[];
43
+ }
44
+ /** Build only security-relevant configuration; runtime state is deliberately absent. */
45
+ export declare function buildAssessmentSecuritySnapshot(input: AssessmentSecuritySnapshotInput): AssessmentSecuritySnapshot;
46
+ /** Recursively sort object keys while preserving array order where it matters. */
47
+ export declare function canonicalizeAssessmentSecuritySnapshot(snapshot: AssessmentSecuritySnapshot): AssessmentSecuritySnapshot;
48
+ export declare function hashAssessmentSecuritySnapshot(snapshot: AssessmentSecuritySnapshot): string;
49
+ export declare function getAssessmentSecuritySnapshotComponentHashes(snapshot: AssessmentSecuritySnapshot): Record<SecuritySnapshotCategory, string>;
50
+ export declare function compareAssessmentSecuritySnapshots(expected: AssessmentSecuritySnapshot, actual: AssessmentSecuritySnapshot): AssessmentSecuritySnapshotComparison;
@@ -0,0 +1,158 @@
1
+ import { createHash } from "node:crypto";
2
+ import { resolve, win32 } from "node:path";
3
+ import { getRepoAllowedTestCommands, getRepoDirectAllowedCommands } from "../config.js";
4
+ import { stableJsonStringify } from "../utils/stableJson.js";
5
+ /**
6
+ * Version the security snapshot independently from the server/schema version.
7
+ * Changing the fields or their canonicalization should invalidate old records.
8
+ */
9
+ export const ASSESSMENT_SECURITY_SNAPSHOT_VERSION = "assessment-security-v2";
10
+ function canonicalPath(value) {
11
+ const rawValue = String(value);
12
+ if (/^[a-z]:[\\/]/i.test(rawValue)) {
13
+ return win32.normalize(rawValue).replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
14
+ }
15
+ const normalized = resolve(rawValue).replace(/\\/g, "/").replace(/\/+$/, "");
16
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
17
+ }
18
+ function sortedUnique(values) {
19
+ return [...new Set((values || []).map((value) => String(value).trim()))]
20
+ .filter(Boolean)
21
+ .sort((left, right) => left.localeCompare(right));
22
+ }
23
+ function sortedUniqueTaskPaths(values) {
24
+ return sortedUnique(values?.map((value) => {
25
+ const normalized = String(value).replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/{2,}/g, "/");
26
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
27
+ }));
28
+ }
29
+ function sortedAgentConfig(config) {
30
+ return Object.fromEntries(Object.entries(config.agents)
31
+ .sort(([left], [right]) => left.localeCompare(right))
32
+ .map(([name, agent]) => [name, {
33
+ command: agent.command,
34
+ args: [...agent.args],
35
+ adapter: agent.adapter || null,
36
+ model: agent.model || null,
37
+ envAllowlist: sortedUnique(agent.envAllowlist),
38
+ }]));
39
+ }
40
+ /** Build only security-relevant configuration; runtime state is deliberately absent. */
41
+ export function buildAssessmentSecuritySnapshot(input) {
42
+ const { config } = input;
43
+ const repoPath = canonicalPath(input.repoPath);
44
+ const components = {
45
+ schema_epoch: input.schemaEpoch,
46
+ tool_profile: input.toolProfile,
47
+ tool_manifest: input.toolManifestSha256,
48
+ workspace_root: canonicalPath(config.workspaceRoot),
49
+ repo_boundary: {
50
+ workspace_root: canonicalPath(config.workspaceRoot),
51
+ repo_path: repoPath,
52
+ repo_aliases: Object.fromEntries(Object.entries(config.repoAliases || {})
53
+ .sort(([left], [right]) => left.localeCompare(right))
54
+ .map(([key, value]) => [key, canonicalPath(resolve(config.workspaceRoot, value))])),
55
+ },
56
+ agent_launch: {
57
+ selected_agent: input.agent,
58
+ configured_agents: sortedAgentConfig(config),
59
+ },
60
+ allowed_commands: {
61
+ configured: sortedUnique(config.allowedTestCommands),
62
+ project: sortedUnique(input.projectPolicy?.allowed_commands),
63
+ },
64
+ repo_allowed_test_commands: sortedUnique(getRepoAllowedTestCommands(config, repoPath)),
65
+ direct_allowed_commands: sortedUnique(config.directAllowedCommands),
66
+ repo_direct_allowed_commands: sortedUnique(getRepoDirectAllowedCommands(config, repoPath)),
67
+ sensitive_path_rules: "sensitive-path-rules-v1",
68
+ protected_paths: sortedUnique(input.projectPolicy?.protected_paths || [
69
+ ".env", ".env.*", ".ssh", ".npmrc", ".pypirc", "patchwarden.config.json",
70
+ ]),
71
+ project_policy: {
72
+ valid: input.projectPolicyValid ?? null,
73
+ effective_policy: input.projectPolicy || null,
74
+ issues: [...(input.projectPolicyIssues || [])]
75
+ .map((issue) => ({ code: issue.code, severity: issue.severity, field: issue.field }))
76
+ .sort((left, right) => stableJsonStringify(left).localeCompare(stableJsonStringify(right))),
77
+ },
78
+ risk_rules: {
79
+ implementation: input.riskRulesVersion || "risk-engine-v1",
80
+ project_high_risk_commands: sortedUnique(input.projectPolicy?.high_risk_commands),
81
+ },
82
+ confirmation_policy: {
83
+ change_policy: input.changePolicy || "repo_scoped_changes",
84
+ template: input.template || null,
85
+ },
86
+ task_parameters: {
87
+ test_command: input.testCommand || null,
88
+ verify_commands: sortedUnique(input.verifyCommands),
89
+ timeout_seconds: input.taskTimeoutSeconds ?? config.defaultTaskTimeoutSeconds,
90
+ scope: sortedUniqueTaskPaths(input.scope),
91
+ forbidden: sortedUniqueTaskPaths(input.forbidden),
92
+ verification: sortedUnique(input.verification),
93
+ done_evidence: sortedUniqueTaskPaths(input.doneEvidence),
94
+ },
95
+ release_protection: {
96
+ high_risk_commands: sortedUnique(input.projectPolicy?.high_risk_commands || [
97
+ "npm publish", "git push", "git tag", "gh release create",
98
+ ]),
99
+ release_mode: input.projectPolicy?.release_mode || {
100
+ version_source: "package.json",
101
+ required_commands: ["npm run build", "npm test"],
102
+ },
103
+ artifact_rules: "artifact-rules-v1",
104
+ },
105
+ direct_profile: Boolean(config.enableDirectProfile),
106
+ assessment_ttl: config.assessmentTtlSeconds,
107
+ timeout_policy: {
108
+ default_task_timeout_seconds: config.defaultTaskTimeoutSeconds,
109
+ max_task_timeout_seconds: config.maxTaskTimeoutSeconds,
110
+ },
111
+ };
112
+ return {
113
+ assessment_security_snapshot_version: ASSESSMENT_SECURITY_SNAPSHOT_VERSION,
114
+ components,
115
+ };
116
+ }
117
+ /** Recursively sort object keys while preserving array order where it matters. */
118
+ export function canonicalizeAssessmentSecuritySnapshot(snapshot) {
119
+ const canonicalize = (value) => {
120
+ if (Array.isArray(value))
121
+ return value.map(canonicalize);
122
+ if (value !== null && typeof value === "object") {
123
+ return Object.fromEntries(Object.entries(value)
124
+ .sort(([left], [right]) => left.localeCompare(right))
125
+ .map(([key, entry]) => [key, canonicalize(entry)]));
126
+ }
127
+ return value;
128
+ };
129
+ return canonicalize(snapshot);
130
+ }
131
+ export function hashAssessmentSecuritySnapshot(snapshot) {
132
+ return createHash("sha256")
133
+ .update(stableJsonStringify(canonicalizeAssessmentSecuritySnapshot(snapshot)))
134
+ .digest("hex");
135
+ }
136
+ export function getAssessmentSecuritySnapshotComponentHashes(snapshot) {
137
+ return Object.fromEntries(Object.entries(snapshot.components).map(([name, value]) => [
138
+ name,
139
+ createHash("sha256").update(stableJsonStringify(value)).digest("hex"),
140
+ ]));
141
+ }
142
+ export function compareAssessmentSecuritySnapshots(expected, actual) {
143
+ const expectedHash = hashAssessmentSecuritySnapshot(expected);
144
+ const actualHash = hashAssessmentSecuritySnapshot(actual);
145
+ const expectedComponents = getAssessmentSecuritySnapshotComponentHashes(expected);
146
+ const actualComponents = getAssessmentSecuritySnapshotComponentHashes(actual);
147
+ const changed = Object.keys(actualComponents)
148
+ .filter((name) => expectedComponents[name] !== actualComponents[name]);
149
+ if (expected.assessment_security_snapshot_version !== actual.assessment_security_snapshot_version) {
150
+ changed.unshift("schema_epoch");
151
+ }
152
+ return {
153
+ equal: expectedHash === actualHash,
154
+ expected_hash: expectedHash,
155
+ actual_hash: actualHash,
156
+ changed_field_names: [...new Set(changed)],
157
+ };
158
+ }
package/dist/config.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  export interface AgentConfig {
2
2
  command: string;
3
3
  args: string[];
4
+ envAllowlist?: string[];
5
+ adapter?: string;
6
+ model?: string;
4
7
  }
5
8
  export interface PatchWardenConfig {
6
9
  workspaceRoot: string;
@@ -29,6 +32,18 @@ export interface PatchWardenConfig {
29
32
  agentAssessmentMaxOutputBytes?: number;
30
33
  agentAssessmentAgentName?: string;
31
34
  enableDirectProfile?: boolean;
35
+ tunnelClientPath?: string;
36
+ tunnelProxy?: {
37
+ scope: "shared" | "separate";
38
+ core: {
39
+ mode: "environment" | "none" | "manual";
40
+ url?: string;
41
+ };
42
+ direct: {
43
+ mode: "environment" | "none" | "manual";
44
+ url?: string;
45
+ };
46
+ };
32
47
  directSessionsDir: string;
33
48
  directSessionTtlSeconds: number;
34
49
  directMaxPatchBytes: number;