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,20 +1,33 @@
1
1
  import {
2
2
  mkdirSync,
3
- writeFileSync,
4
3
  existsSync,
5
4
  readFileSync,
6
- renameSync,
7
5
  } from "node:fs";
8
6
  import { createHash, randomBytes } from "node:crypto";
9
7
  import { resolve, join } from "node:path";
10
8
  import { getAssessmentsDir, getConfig, getRepoAllowedTestCommands, PatchWardenConfig } from "../config.js";
11
9
  import { guardPath } from "../security/pathGuard.js";
12
10
  import { PatchWardenError } from "../errors.js";
11
+ import { redactSensitiveContent } from "../security/contentRedaction.js";
12
+ import { atomicWriteJsonFileSync } from "../utils/atomicFile.js";
13
+ import { withFileLockSync } from "../utils/lockedJsonFile.js";
13
14
  import { TOOL_SCHEMA_EPOCH } from "../version.js";
14
- import { getLastToolCatalogSnapshot } from "../tools/toolCatalog.js";
15
+ import {
16
+ buildToolCatalogSnapshot,
17
+ resolveToolProfile,
18
+ } from "../tools/catalog/toolCatalog.js";
19
+ import { getToolDefs } from "../tools/definitions/toolDefs.js";
15
20
  import { captureRepoSnapshot, type RepoSnapshot } from "../runner/changeCapture.js";
16
21
  import type { RiskAssessmentResult } from "../security/riskEngine.js";
17
22
  import type { TaskTemplateName, ChangePolicy } from "../tools/taskTemplates.js";
23
+ import { getProjectPolicySummary, type ProjectPolicy } from "../policy/projectPolicy.js";
24
+ import {
25
+ ASSESSMENT_SECURITY_SNAPSHOT_VERSION,
26
+ buildAssessmentSecuritySnapshot,
27
+ getAssessmentSecuritySnapshotComponentHashes,
28
+ hashAssessmentSecuritySnapshot,
29
+ type SecuritySnapshotCategory,
30
+ } from "./securitySnapshot.js";
18
31
 
19
32
  const SENSITIVE_PATH_RULES_SIGNATURE = "v1";
20
33
  const ARTIFACT_RULES_SIGNATURE = "v1";
@@ -64,6 +77,12 @@ export interface AssessmentRecord {
64
77
  plan_id: string | null;
65
78
  policy_hash: string;
66
79
  tool_manifest_sha256: string;
80
+ execution_config_hash?: string;
81
+ execution_config_component_hashes?: Partial<Record<ExecutionConfigChangeCategory, string>>;
82
+ assessment_security_snapshot_version?: string;
83
+ assessment_security_snapshot_sha256?: string;
84
+ assessment_security_snapshot_component_hashes?: Partial<Record<ExecutionConfigChangeCategory, string>>;
85
+ assessment_schema_epoch?: string;
67
86
  workspace_fingerprint: string;
68
87
  workspace_snapshot_summary: {
69
88
  head: string | null;
@@ -82,10 +101,15 @@ export interface AssessmentRecord {
82
101
  agent: string;
83
102
  timeout_seconds?: number;
84
103
  change_policy?: ChangePolicy;
104
+ scope?: string[];
105
+ forbidden?: string[];
106
+ verification?: string[];
107
+ done_evidence?: string[];
85
108
  requires_confirm: boolean;
86
109
  confirmed: boolean;
87
110
  confirmed_at: string | null;
88
111
  confirm_code: string | null;
112
+ used_at?: string | null;
89
113
  agent_assessment_summary?: AgentAssessmentSummary | null;
90
114
  }
91
115
 
@@ -106,6 +130,10 @@ export interface AssessmentCreateInput {
106
130
  agent: string;
107
131
  timeout_seconds?: number;
108
132
  change_policy?: ChangePolicy;
133
+ scope?: string[];
134
+ forbidden?: string[];
135
+ verification?: string[];
136
+ done_evidence?: string[];
109
137
  snapshot: RepoSnapshot;
110
138
  assessment_id?: string;
111
139
  assessment_dir?: string;
@@ -116,8 +144,13 @@ export interface AssessmentValidationResult {
116
144
  valid: boolean;
117
145
  failure_reason: string | null;
118
146
  assessment: AssessmentRecord | null;
147
+ expected_hash?: string;
148
+ actual_hash?: string;
149
+ config_change_categories?: ExecutionConfigChangeCategory[];
119
150
  }
120
151
 
152
+ export type ExecutionConfigChangeCategory = SecuritySnapshotCategory | "allowed_test_commands" | "execution_config";
153
+
121
154
  export interface AssessmentConfirmationResult {
122
155
  assessment_id: string;
123
156
  decision: "needs_confirm";
@@ -160,7 +193,7 @@ export function createAssessment(input: AssessmentCreateInput): AssessmentRecord
160
193
  ? createHash("sha256").update(input.plan_content).digest("hex")
161
194
  : null;
162
195
 
163
- const toolManifest = getLastToolCatalogSnapshot()?.tool_manifest_sha256 || computeFallbackManifestHash(config);
196
+ const toolManifest = getCurrentToolManifest(config);
164
197
 
165
198
  const workspaceFingerprint = computeWorkspaceFingerprint(input.snapshot);
166
199
  const snapshotTruncated = input.snapshot.warnings.some((w) => w.includes("snapshot limited"));
@@ -175,6 +208,21 @@ export function createAssessment(input: AssessmentCreateInput): AssessmentRecord
175
208
  artifact_rules: ARTIFACT_RULES_SIGNATURE,
176
209
  schema_epoch: TOOL_SCHEMA_EPOCH,
177
210
  });
211
+ const executionConfig = computeExecutionConfigFingerprint(config, {
212
+ agent: input.agent,
213
+ tool_manifest_sha256: toolManifest,
214
+ tool_profile: resolveToolProfile(config.toolProfile),
215
+ repo_path: input.resolved_repo_path,
216
+ change_policy: input.change_policy || "repo_scoped_changes",
217
+ template: input.template || null,
218
+ verify_commands: input.verify_commands || [],
219
+ test_command: input.test_command || null,
220
+ task_timeout_seconds: input.timeout_seconds,
221
+ scope: input.scope,
222
+ forbidden: input.forbidden,
223
+ verification: input.verification,
224
+ done_evidence: input.done_evidence,
225
+ });
178
226
 
179
227
  const now = new Date();
180
228
  const expiresAt = new Date(now.getTime() + config.assessmentTtlSeconds * 1000).toISOString();
@@ -191,6 +239,12 @@ export function createAssessment(input: AssessmentCreateInput): AssessmentRecord
191
239
  plan_id: input.plan_id,
192
240
  policy_hash: policyHash,
193
241
  tool_manifest_sha256: toolManifest,
242
+ execution_config_hash: executionConfig.hash,
243
+ execution_config_component_hashes: executionConfig.components,
244
+ assessment_security_snapshot_version: ASSESSMENT_SECURITY_SNAPSHOT_VERSION,
245
+ assessment_security_snapshot_sha256: executionConfig.hash,
246
+ assessment_security_snapshot_component_hashes: executionConfig.components,
247
+ assessment_schema_epoch: TOOL_SCHEMA_EPOCH,
194
248
  workspace_fingerprint: workspaceFingerprint,
195
249
  workspace_snapshot_summary: {
196
250
  head: input.snapshot.head,
@@ -203,25 +257,37 @@ export function createAssessment(input: AssessmentCreateInput): AssessmentRecord
203
257
  repo_path: input.repo_path,
204
258
  resolved_repo_path: input.resolved_repo_path,
205
259
  template: input.template || null,
206
- goal: input.goal || null,
260
+ goal: input.goal ? redactSensitiveContent(input.goal).content : null,
207
261
  test_command: input.test_command || null,
208
262
  verify_commands: input.verify_commands || [],
209
263
  agent: input.agent,
210
264
  timeout_seconds: input.timeout_seconds,
211
265
  change_policy: input.change_policy || "repo_scoped_changes",
266
+ scope: input.scope || [],
267
+ forbidden: input.forbidden || [],
268
+ verification: input.verification || [],
269
+ done_evidence: input.done_evidence || [],
212
270
  requires_confirm: input.decision === "needs_confirm",
213
271
  confirmed: false,
214
272
  confirmed_at: null,
215
273
  confirm_code: null,
274
+ used_at: null,
216
275
  agent_assessment_summary: input.agent_assessment_summary || null,
217
276
  };
218
277
 
219
- writeFileSync(join(dir, "assessment.json"), JSON.stringify(record, null, 2), "utf-8");
278
+ atomicWriteJsonFileSync(join(dir, "assessment.json"), record);
220
279
 
221
280
  return record;
222
281
  }
223
282
 
224
283
  export function readAssessment(assessmentId: string): AssessmentRecord {
284
+ if (!/^assessment_\d{8}_\d{6}_[0-9a-f]{32}$/.test(assessmentId)) {
285
+ throw new PatchWardenError(
286
+ "assessment_id_invalid",
287
+ "A full assessment_id with 32 hexadecimal random characters is required.",
288
+ "Copy the complete assessment_id from the assess_only response; assessment_short_id is display-only.",
289
+ );
290
+ }
225
291
  const config = getConfig();
226
292
  const assessmentsDir = getAssessmentsDir(config);
227
293
  const dir = resolve(assessmentsDir, assessmentId);
@@ -236,7 +302,43 @@ export function readAssessment(assessmentId: string): AssessmentRecord {
236
302
  { assessment_id: assessmentId }
237
303
  );
238
304
  }
239
- return JSON.parse(readFileSync(file, "utf-8")) as AssessmentRecord;
305
+ try {
306
+ const parsed = JSON.parse(readFileSync(file, "utf-8")) as AssessmentRecord;
307
+ if (!parsed || typeof parsed !== "object" || parsed.assessment_id !== assessmentId) {
308
+ throw new Error("record identity mismatch");
309
+ }
310
+ return parsed;
311
+ } catch (error) {
312
+ throw new PatchWardenError(
313
+ "assessment_corrupted",
314
+ `Assessment "${assessmentId}" cannot be read as a valid record.`,
315
+ "Run create_task with execution_mode=assess_only again.",
316
+ true,
317
+ { assessment_id: assessmentId, failure_category: "invalid_assessment_record" },
318
+ );
319
+ }
320
+ }
321
+
322
+ /** Atomically consume an Assessment before creating its one allowed task. */
323
+ export function markAssessmentUsed(assessmentId: string): AssessmentRecord {
324
+ const config = getConfig();
325
+ const assessmentFile = resolve(getAssessmentsDir(config), assessmentId, "assessment.json");
326
+ guardPath(assessmentFile, config.workspaceRoot, config.assessmentsDir);
327
+ return withFileLockSync(assessmentFile, () => {
328
+ const assessment = readAssessment(assessmentId);
329
+ if (assessment.used_at) {
330
+ throw new PatchWardenError(
331
+ "assessment_used",
332
+ `Assessment "${assessmentId}" has already been used.`,
333
+ "Run create_task with execution_mode=assess_only again to obtain a new assessment_id.",
334
+ true,
335
+ { assessment_id: assessmentId, used_at: assessment.used_at },
336
+ );
337
+ }
338
+ const consumed: AssessmentRecord = { ...assessment, used_at: new Date().toISOString() };
339
+ atomicWriteJsonFileSync(assessmentFile, consumed);
340
+ return consumed;
341
+ });
240
342
  }
241
343
 
242
344
  export function validateAssessmentFreshness(
@@ -244,6 +346,9 @@ export function validateAssessmentFreshness(
244
346
  currentSnapshot: RepoSnapshot,
245
347
  options: AssessmentValidationOptions = {}
246
348
  ): AssessmentValidationResult {
349
+ if (!/^assessment_\d{8}_\d{6}_[0-9a-f]{32}$/.test(assessmentId)) {
350
+ return { valid: false, failure_reason: "assessment_id_invalid", assessment: null };
351
+ }
247
352
  let assessment: AssessmentRecord;
248
353
  try {
249
354
  assessment = readAssessment(assessmentId);
@@ -274,11 +379,21 @@ export function validateAssessmentFreshness(
274
379
  }
275
380
  }
276
381
 
382
+ const config = getConfig();
383
+ const currentManifest = getCurrentToolManifest(config);
277
384
  if (!options.skip_tool_manifest) {
278
- const currentManifest = getLastToolCatalogSnapshot()?.tool_manifest_sha256 ||
279
- computeFallbackManifestHash(getConfig());
280
- if (currentManifest !== assessment.tool_manifest_sha256) {
281
- return { valid: false, failure_reason: "assessment_stale_config", assessment };
385
+ // Legacy records need the standalone manifest check. Versioned security
386
+ // snapshots compare the manifest together with the source config, so the
387
+ // diagnostic can report both "tool_manifest" and e.g. "allowed_commands".
388
+ if (currentManifest !== assessment.tool_manifest_sha256 && !assessment.assessment_security_snapshot_sha256) {
389
+ return {
390
+ valid: false,
391
+ failure_reason: "assessment_stale_config",
392
+ assessment,
393
+ expected_hash: assessment.tool_manifest_sha256,
394
+ actual_hash: currentManifest,
395
+ config_change_categories: ["tool_manifest"],
396
+ };
282
397
  }
283
398
  }
284
399
 
@@ -287,7 +402,52 @@ export function validateAssessmentFreshness(
287
402
  return { valid: false, failure_reason: "assessment_workspace_changed", assessment };
288
403
  }
289
404
 
290
- const config = getConfig();
405
+ if (assessment.execution_config_hash) {
406
+ const currentExecutionConfig = computeExecutionConfigFingerprint(config, {
407
+ agent: assessment.agent,
408
+ tool_manifest_sha256: currentManifest,
409
+ tool_profile: resolveToolProfile(config.toolProfile),
410
+ repo_path: assessment.resolved_repo_path,
411
+ change_policy: assessment.change_policy || "repo_scoped_changes",
412
+ template: assessment.template || null,
413
+ verify_commands: assessment.verify_commands || [],
414
+ test_command: assessment.test_command || null,
415
+ task_timeout_seconds: assessment.timeout_seconds,
416
+ scope: assessment.scope,
417
+ forbidden: assessment.forbidden,
418
+ verification: assessment.verification,
419
+ done_evidence: assessment.done_evidence,
420
+ });
421
+ if (
422
+ assessment.assessment_security_snapshot_version
423
+ && assessment.assessment_security_snapshot_version !== ASSESSMENT_SECURITY_SNAPSHOT_VERSION
424
+ ) {
425
+ return {
426
+ valid: false,
427
+ failure_reason: "assessment_snapshot_version_incompatible",
428
+ assessment,
429
+ expected_hash: assessment.execution_config_hash,
430
+ actual_hash: currentExecutionConfig.hash,
431
+ config_change_categories: ["schema_epoch"],
432
+ };
433
+ }
434
+ if (currentExecutionConfig.hash !== assessment.execution_config_hash) {
435
+ const previousComponents = assessment.execution_config_component_hashes;
436
+ const changedCategories = previousComponents
437
+ ? (Object.keys(currentExecutionConfig.components) as ExecutionConfigChangeCategory[])
438
+ .filter((category) => previousComponents[category] !== currentExecutionConfig.components[category])
439
+ : ["execution_config" as const];
440
+ return {
441
+ valid: false,
442
+ failure_reason: "assessment_stale_config",
443
+ assessment,
444
+ expected_hash: assessment.execution_config_hash,
445
+ actual_hash: currentExecutionConfig.hash,
446
+ config_change_categories: changedCategories.length > 0 ? changedCategories : ["execution_config"],
447
+ };
448
+ }
449
+ }
450
+
291
451
  const currentPolicyHash = computePolicyHash({
292
452
  change_policy: assessment.change_policy || "repo_scoped_changes",
293
453
  template: assessment.template || null,
@@ -323,6 +483,10 @@ export async function confirmAssessment(assessmentId: string): Promise<Assessmen
323
483
  );
324
484
  }
325
485
 
486
+ const config = getConfig();
487
+ const assessmentFile = resolve(getAssessmentsDir(config), assessmentId, "assessment.json");
488
+ guardPath(assessmentFile, config.workspaceRoot, config.assessmentsDir);
489
+
326
490
  const assessment = readAssessment(assessmentId);
327
491
  if (assessment.decision !== "needs_confirm" || !assessment.requires_confirm) {
328
492
  throw new PatchWardenError(
@@ -335,43 +499,50 @@ export async function confirmAssessment(assessmentId: string): Promise<Assessmen
335
499
  }
336
500
 
337
501
  const snapshot = await captureRepoSnapshot(assessment.resolved_repo_path);
338
- const validation = validateAssessmentFreshness(assessmentId, snapshot, {
339
- allow_unconfirmed: true,
340
- // A standalone CLI has no active MCP profile snapshot. The execute path
341
- // still performs the full tool-manifest check before creating the task.
342
- skip_tool_manifest: true,
343
- });
344
- if (!validation.valid || !validation.assessment) {
345
- throw new PatchWardenError(
346
- validation.failure_reason || "assessment_validation_failed",
347
- `Assessment "${assessmentId}" cannot be confirmed: ${validation.failure_reason || "validation_failed"}.`,
348
- "Run create_task with execution_mode=assess_only again, then confirm the new assessment locally."
349
- );
350
- }
351
502
 
352
- const confirmedAt = validation.assessment.confirmed_at || new Date().toISOString();
353
- const confirmedRecord: AssessmentRecord = {
354
- ...validation.assessment,
355
- confirmed: true,
356
- confirmed_at: confirmedAt,
357
- confirm_code: validation.assessment.confirm_code || randomBytes(8).toString("hex"),
358
- };
359
- const config = getConfig();
360
- const assessmentFile = resolve(getAssessmentsDir(config), assessmentId, "assessment.json");
361
- guardPath(assessmentFile, config.workspaceRoot, config.assessmentsDir);
362
- const temporaryFile = `${assessmentFile}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
363
- guardPath(temporaryFile, config.workspaceRoot, config.assessmentsDir);
364
- writeFileSync(temporaryFile, JSON.stringify(confirmedRecord, null, 2), "utf-8");
365
- renameSync(temporaryFile, assessmentFile);
503
+ // Re-read and write while holding one cross-process lock. Concurrent local
504
+ // confirmers may compute snapshots in parallel, but they cannot replace one
505
+ // another's confirmation metadata from a stale assessment record.
506
+ return withFileLockSync(assessmentFile, () => {
507
+ const validation = validateAssessmentFreshness(assessmentId, snapshot, {
508
+ allow_unconfirmed: true,
509
+ // A standalone CLI has no active MCP profile snapshot. The execute path
510
+ // still performs the full tool-manifest check before creating the task.
511
+ skip_tool_manifest: true,
512
+ });
513
+ if (!validation.valid || !validation.assessment) {
514
+ throw new PatchWardenError(
515
+ validation.failure_reason || "assessment_validation_failed",
516
+ `Assessment "${assessmentId}" cannot be confirmed: ${validation.failure_reason || "validation_failed"}.`,
517
+ "Run create_task with execution_mode=assess_only again, then confirm the new assessment locally."
518
+ );
519
+ }
520
+ if (validation.assessment.decision !== "needs_confirm" || !validation.assessment.requires_confirm) {
521
+ throw new PatchWardenError(
522
+ "assessment_confirmation_not_allowed",
523
+ `Assessment "${assessmentId}" is no longer eligible for local confirmation.`,
524
+ "Run create_task with execution_mode=assess_only again."
525
+ );
526
+ }
366
527
 
367
- return {
368
- assessment_id: assessmentId,
369
- decision: "needs_confirm",
370
- confirmed: true,
371
- confirmed_at: confirmedAt,
372
- expires_at: confirmedRecord.expires_at,
373
- next_action: "Call create_task with only execution_mode=execute and this full assessment_id.",
374
- };
528
+ const confirmedAt = validation.assessment.confirmed_at || new Date().toISOString();
529
+ const confirmedRecord: AssessmentRecord = {
530
+ ...validation.assessment,
531
+ confirmed: true,
532
+ confirmed_at: confirmedAt,
533
+ confirm_code: validation.assessment.confirm_code || randomBytes(8).toString("hex"),
534
+ };
535
+ atomicWriteJsonFileSync(assessmentFile, confirmedRecord);
536
+
537
+ return {
538
+ assessment_id: assessmentId,
539
+ decision: "needs_confirm",
540
+ confirmed: true,
541
+ confirmed_at: confirmedAt,
542
+ expires_at: confirmedRecord.expires_at,
543
+ next_action: "Call create_task with only execution_mode=execute and this full assessment_id.",
544
+ };
545
+ });
375
546
  }
376
547
 
377
548
  export function computeWorkspaceFingerprint(snapshot: RepoSnapshot): string {
@@ -413,10 +584,76 @@ function getRepoAllowedCommands(config: PatchWardenConfig, repoPath: string): st
413
584
  return getRepoAllowedTestCommands(config, repoPath);
414
585
  }
415
586
 
416
- function computeFallbackManifestHash(config: PatchWardenConfig): string {
417
- return createHash("sha256")
418
- .update(`${TOOL_SCHEMA_EPOCH}:${config.toolProfile || "full"}`)
419
- .digest("hex");
587
+ function getCurrentToolManifest(config: PatchWardenConfig): string {
588
+ // The catalog cache is process-local. Rebuild the canonical catalog here so
589
+ // MCP and the independent Watcher derive the same manifest across processes.
590
+ const profile = resolveToolProfile(config.toolProfile);
591
+ return buildToolCatalogSnapshot(getToolDefs(), profile).tool_manifest_sha256;
592
+ }
593
+
594
+ interface ExecutionConfigHashInput {
595
+ agent: string;
596
+ tool_manifest_sha256: string;
597
+ tool_profile: string;
598
+ repo_path: string;
599
+ change_policy?: string | null;
600
+ template?: string | null;
601
+ verify_commands?: string[];
602
+ test_command?: string | null;
603
+ task_timeout_seconds?: number | null;
604
+ scope?: string[];
605
+ forbidden?: string[];
606
+ verification?: string[];
607
+ done_evidence?: string[];
608
+ }
609
+
610
+ export function computeExecutionConfigHash(
611
+ config: PatchWardenConfig,
612
+ input: ExecutionConfigHashInput,
613
+ ): string {
614
+ return computeExecutionConfigFingerprint(config, input).hash;
615
+ }
616
+
617
+ function computeExecutionConfigFingerprint(
618
+ config: PatchWardenConfig,
619
+ input: ExecutionConfigHashInput,
620
+ ): { hash: string; components: Partial<Record<ExecutionConfigChangeCategory, string>> } {
621
+ let projectPolicy: ProjectPolicy | null = null;
622
+ let projectPolicyValid: boolean | null = null;
623
+ let projectPolicyIssues: Array<{ code: string; severity: string; field: string }> = [];
624
+ try {
625
+ const summary = getProjectPolicySummary(input.repo_path);
626
+ projectPolicy = summary.effective_policy;
627
+ projectPolicyValid = summary.valid;
628
+ projectPolicyIssues = summary.issues.map(({ code, severity, field }) => ({ code, severity, field }));
629
+ } catch {
630
+ // A workspace-root change can make the old repository inaccessible. The
631
+ // workspace_root component still fails closed without exposing the path.
632
+ }
633
+ const snapshot = buildAssessmentSecuritySnapshot({
634
+ config,
635
+ schemaEpoch: TOOL_SCHEMA_EPOCH,
636
+ toolProfile: input.tool_profile,
637
+ toolManifestSha256: input.tool_manifest_sha256,
638
+ agent: input.agent,
639
+ repoPath: input.repo_path,
640
+ changePolicy: input.change_policy,
641
+ template: input.template,
642
+ verifyCommands: input.verify_commands,
643
+ testCommand: input.test_command,
644
+ taskTimeoutSeconds: input.task_timeout_seconds,
645
+ scope: input.scope,
646
+ forbidden: input.forbidden,
647
+ verification: input.verification,
648
+ doneEvidence: input.done_evidence,
649
+ projectPolicy,
650
+ projectPolicyValid,
651
+ projectPolicyIssues,
652
+ });
653
+ return {
654
+ hash: hashAssessmentSecuritySnapshot(snapshot),
655
+ components: getAssessmentSecuritySnapshotComponentHashes(snapshot),
656
+ };
420
657
  }
421
658
 
422
659
  function extractReason(error: unknown): string {