patchwarden 1.5.1 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (573) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +152 -25
  3. package/README.md +120 -23
  4. package/dist/assessments/agentAssessor.js +21 -12
  5. package/dist/assessments/assessmentStore.js +42 -34
  6. package/dist/config.d.ts +15 -0
  7. package/dist/config.js +66 -13
  8. package/dist/control/fileManager.d.ts +2 -0
  9. package/dist/control/fileManager.js +39 -0
  10. package/dist/control/middleware/auth.d.ts +6 -0
  11. package/dist/control/middleware/auth.js +14 -5
  12. package/dist/control/middleware/static.js +16 -3
  13. package/dist/control/routeTable.d.ts +24 -0
  14. package/dist/control/routeTable.js +263 -0
  15. package/dist/control/routes/audit.js +17 -7
  16. package/dist/control/routes/evidence.js +1 -1
  17. package/dist/control/routes/lineage.js +1 -1
  18. package/dist/control/routes/process.d.ts +7 -0
  19. package/dist/control/routes/process.js +329 -42
  20. package/dist/control/routes/sessions.d.ts +9 -1
  21. package/dist/control/routes/sessions.js +84 -21
  22. package/dist/control/routes/status.d.ts +46 -1
  23. package/dist/control/routes/status.js +72 -15
  24. package/dist/control/routes/taskActions.js +24 -60
  25. package/dist/control/routes/tasks.d.ts +4 -0
  26. package/dist/control/routes/tasks.js +61 -62
  27. package/dist/control/routes/workspace.js +50 -23
  28. package/dist/control/runtime.d.ts +3 -2
  29. package/dist/control/runtime.js +63 -30
  30. package/dist/control/server.js +36 -365
  31. package/dist/control/shared.d.ts +25 -1
  32. package/dist/control/shared.js +64 -11
  33. package/dist/direct/directAudit.js +234 -225
  34. package/dist/direct/directGuards.d.ts +2 -1
  35. package/dist/direct/directGuards.js +42 -29
  36. package/dist/direct/directPatch.d.ts +6 -1
  37. package/dist/direct/directPatch.js +55 -8
  38. package/dist/direct/directSessionStore.d.ts +16 -6
  39. package/dist/direct/directSessionStore.js +127 -35
  40. package/dist/direct/directVerification.js +8 -16
  41. package/dist/doctor.js +62 -52
  42. package/dist/goal/acceptanceEngine.d.ts +1 -1
  43. package/dist/goal/goalProgress.js +75 -69
  44. package/dist/goal/goalReport.js +5 -12
  45. package/dist/goal/goalStatus.d.ts +2 -0
  46. package/dist/goal/goalStatus.js +10 -3
  47. package/dist/goal/goalStore.d.ts +9 -2
  48. package/dist/goal/goalStore.js +152 -45
  49. package/dist/goal/handoffExport.js +6 -6
  50. package/dist/goal/specKitImport.js +97 -96
  51. package/dist/goal/subgoalSync.js +11 -9
  52. package/dist/goal/worktreeManager.d.ts +2 -0
  53. package/dist/goal/worktreeManager.js +209 -136
  54. package/dist/httpServer.js +111 -32
  55. package/dist/logging.js +5 -3
  56. package/dist/policy/projectPolicy.js +5 -2
  57. package/dist/release/releaseGate.d.ts +1 -1
  58. package/dist/release/releaseGate.js +78 -25
  59. package/dist/runner/agentInvocation.d.ts +14 -0
  60. package/dist/runner/agentInvocation.js +96 -4
  61. package/dist/runner/changeCapture.d.ts +13 -5
  62. package/dist/runner/changeCapture.js +243 -61
  63. package/dist/runner/postTaskCleanup.js +48 -19
  64. package/dist/runner/processSecurity.d.ts +56 -0
  65. package/dist/runner/processSecurity.js +373 -0
  66. package/dist/runner/runTask.d.ts +1 -1
  67. package/dist/runner/runTask.js +115 -87
  68. package/dist/runner/simpleProcess.d.ts +3 -0
  69. package/dist/runner/simpleProcess.js +50 -36
  70. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  71. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  72. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
  73. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  74. package/dist/runner/taskStatusStore.d.ts +11 -0
  75. package/dist/runner/taskStatusStore.js +24 -0
  76. package/dist/runner/watch.d.ts +14 -1
  77. package/dist/runner/watch.js +256 -48
  78. package/dist/security/contentRedaction.js +2 -2
  79. package/dist/security/discoveryTokenStore.d.ts +3 -2
  80. package/dist/security/discoveryTokenStore.js +17 -2
  81. package/dist/security/loopbackHost.d.ts +2 -0
  82. package/dist/security/loopbackHost.js +16 -0
  83. package/dist/security/pathGuard.js +32 -17
  84. package/dist/security/planGuard.js +2 -1
  85. package/dist/security/runtimeGuard.js +9 -6
  86. package/dist/security/secretComparison.d.ts +3 -0
  87. package/dist/security/secretComparison.js +11 -0
  88. package/dist/security/sensitiveGuard.d.ts +5 -0
  89. package/dist/security/sensitiveGuard.js +27 -8
  90. package/dist/security/toolInvocationGuard.d.ts +2 -2
  91. package/dist/security/toolInvocationGuard.js +1 -1
  92. package/dist/security/workspaceRootGuard.d.ts +8 -0
  93. package/dist/security/workspaceRootGuard.js +47 -0
  94. package/dist/smoke-test.js +24 -23
  95. package/dist/tools/catalog/index.d.ts +4 -0
  96. package/dist/tools/catalog/index.js +4 -0
  97. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  98. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  99. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +6 -15
  100. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  101. package/dist/tools/definitions/toolDefs.js +1177 -0
  102. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  103. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  104. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +1 -1
  105. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +99 -29
  106. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
  107. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +7 -7
  108. package/dist/tools/diagnostics/index.d.ts +6 -0
  109. package/dist/tools/diagnostics/index.js +6 -0
  110. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  111. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  112. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +12 -8
  113. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +23 -13
  114. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  115. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  116. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +6 -6
  117. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +1 -1
  118. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  119. package/dist/tools/direct/index.d.ts +3 -0
  120. package/dist/tools/direct/index.js +3 -0
  121. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  122. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  123. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  124. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  125. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  126. package/dist/tools/discovery/index.d.ts +3 -0
  127. package/dist/tools/discovery/index.js +3 -0
  128. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  129. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  130. package/dist/tools/dispatch/coreDispatch.js +32 -31
  131. package/dist/tools/dispatch/diagnosticDispatch.d.ts +3 -5
  132. package/dist/tools/dispatch/diagnosticDispatch.js +70 -68
  133. package/dist/tools/dispatch/directDispatch.js +11 -10
  134. package/dist/tools/dispatch/goalDispatch.js +5 -4
  135. package/dist/tools/dispatch/releaseDispatch.js +3 -2
  136. package/dist/tools/dispatch/validation.d.ts +7 -0
  137. package/dist/tools/dispatch/validation.js +71 -0
  138. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  139. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +1 -1
  140. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  141. package/dist/tools/goals/index.d.ts +3 -0
  142. package/dist/tools/goals/index.js +3 -0
  143. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  144. package/dist/tools/registry.d.ts +7 -11
  145. package/dist/tools/registry.js +17 -1178
  146. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  147. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  148. package/dist/tools/release/index.d.ts +2 -0
  149. package/dist/tools/release/index.js +2 -0
  150. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  151. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  152. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  153. package/dist/tools/tasks/cancelTask.js +85 -0
  154. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +10 -2
  155. package/dist/tools/{createTask.js → tasks/createTask.js} +42 -27
  156. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  157. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
  158. package/dist/tools/{evidencePack.js → tasks/evidencePack.js} +34 -22
  159. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  160. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  161. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
  162. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  163. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
  164. package/dist/tools/tasks/index.d.ts +18 -0
  165. package/dist/tools/tasks/index.js +18 -0
  166. package/dist/tools/tasks/killTask.d.ts +1 -0
  167. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  168. package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
  169. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
  170. package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
  171. package/dist/tools/tasks/retryTask.js +60 -0
  172. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  173. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +58 -30
  174. package/dist/tools/tasks/runVerification.js +38 -0
  175. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  176. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  177. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  178. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  179. package/dist/tools/workspace/applyPatch.js +53 -0
  180. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  181. package/dist/tools/workspace/index.d.ts +10 -0
  182. package/dist/tools/workspace/index.js +10 -0
  183. package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
  184. package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
  185. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  186. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  187. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  188. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  189. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  190. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  191. package/dist/tools/workspace/syncFile.js +134 -0
  192. package/dist/utils/atomicFile.d.ts +7 -0
  193. package/dist/utils/atomicFile.js +48 -0
  194. package/dist/utils/boundedFile.d.ts +8 -0
  195. package/dist/utils/boundedFile.js +93 -0
  196. package/dist/utils/lockedJsonFile.d.ts +15 -0
  197. package/dist/utils/lockedJsonFile.js +224 -0
  198. package/dist/utils/platform.d.ts +11 -0
  199. package/dist/utils/platform.js +26 -0
  200. package/dist/utils/stableJson.d.ts +2 -0
  201. package/dist/utils/stableJson.js +12 -0
  202. package/dist/version.d.ts +3 -2
  203. package/dist/version.js +3 -2
  204. package/dist/watcherStatus.d.ts +2 -2
  205. package/dist/watcherStatus.js +18 -0
  206. package/docs/CODE_WIKI.md +808 -143
  207. package/docs/architecture.md +15 -55
  208. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  209. package/docs/control-center/README.md +4 -4
  210. package/docs/dashboard-overview.md +4 -4
  211. package/docs/desktop-app.md +175 -0
  212. package/docs/evidence-pack-schema.md +3 -3
  213. package/docs/open-source-application.md +17 -11
  214. package/docs/release-checklist.md +1 -1
  215. package/docs/release-evidence.md +39 -29
  216. package/docs/task-safe-review-workflow.md +5 -5
  217. package/docs/threat-model.md +19 -1
  218. package/examples/config.example.json +4 -2
  219. package/package.json +27 -10
  220. package/scripts/README.md +15 -6
  221. package/scripts/build.js +11 -0
  222. package/scripts/checks/brand-check.js +3 -0
  223. package/scripts/checks/build-output-check.js +37 -0
  224. package/scripts/checks/control-center-smoke.js +91 -6
  225. package/scripts/checks/control-smoke.js +19 -8
  226. package/scripts/checks/http-mcp-smoke.js +82 -4
  227. package/scripts/checks/lifecycle-smoke.js +10 -10
  228. package/scripts/checks/mcp-manifest-check.js +1 -1
  229. package/scripts/checks/mcp-smoke.js +27 -3
  230. package/scripts/checks/package-manifest-check.js +20 -7
  231. package/scripts/checks/unit-tests.js +3 -5
  232. package/scripts/checks/watcher-supervisor-smoke.js +9 -0
  233. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  234. package/scripts/control/manage-patchwarden.ps1 +69 -4
  235. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  236. package/scripts/control/run-background-supervisor.ps1 +41 -0
  237. package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
  238. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  239. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  240. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  241. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  242. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  243. package/scripts/lib/clean-generated-output.js +11 -0
  244. package/scripts/lib/file-discovery.js +12 -0
  245. package/scripts/release/desktop-preflight.js +211 -0
  246. package/scripts/release/pack-clean.js +59 -14
  247. package/src/assessments/agentAssessor.ts +26 -12
  248. package/src/assessments/assessmentStore.ts +52 -40
  249. package/src/config.ts +70 -13
  250. package/src/control/fileManager.ts +40 -0
  251. package/src/control/middleware/auth.ts +22 -4
  252. package/src/control/middleware/static.ts +16 -3
  253. package/src/control/routeTable.ts +301 -0
  254. package/src/control/routes/audit.ts +14 -6
  255. package/src/control/routes/evidence.ts +1 -1
  256. package/src/control/routes/lineage.ts +1 -1
  257. package/src/control/routes/process.ts +341 -40
  258. package/src/control/routes/sessions.ts +88 -25
  259. package/src/control/routes/status.ts +97 -17
  260. package/src/control/routes/taskActions.ts +24 -58
  261. package/src/control/routes/tasks.ts +66 -58
  262. package/src/control/routes/workspace.ts +56 -25
  263. package/src/control/runtime.ts +69 -33
  264. package/src/control/server.ts +36 -342
  265. package/src/control/shared.ts +77 -11
  266. package/src/direct/directAudit.ts +17 -9
  267. package/src/direct/directGuards.ts +72 -44
  268. package/src/direct/directPatch.ts +95 -8
  269. package/src/direct/directSessionStore.ts +211 -55
  270. package/src/direct/directVerification.ts +9 -23
  271. package/src/doctor.ts +73 -59
  272. package/src/goal/acceptanceEngine.ts +1 -1
  273. package/src/goal/goalProgress.ts +17 -13
  274. package/src/goal/goalReport.ts +5 -13
  275. package/src/goal/goalStatus.ts +19 -3
  276. package/src/goal/goalStore.ts +241 -54
  277. package/src/goal/handoffExport.ts +6 -6
  278. package/src/goal/specKitImport.ts +12 -12
  279. package/src/goal/subgoalSync.ts +11 -8
  280. package/src/goal/worktreeManager.ts +139 -22
  281. package/src/httpServer.ts +113 -30
  282. package/src/logging.ts +10 -3
  283. package/src/policy/projectPolicy.ts +5 -2
  284. package/src/release/releaseGate.ts +74 -27
  285. package/src/runner/agentInvocation.ts +126 -4
  286. package/src/runner/changeCapture.ts +291 -57
  287. package/src/runner/postTaskCleanup.ts +41 -17
  288. package/src/runner/processSecurity.ts +433 -0
  289. package/src/runner/runTask.ts +138 -91
  290. package/src/runner/simpleProcess.ts +66 -31
  291. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  292. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
  293. package/src/runner/taskStatusStore.ts +54 -0
  294. package/src/runner/watch.ts +281 -51
  295. package/src/security/contentRedaction.ts +2 -2
  296. package/src/security/discoveryTokenStore.ts +18 -4
  297. package/src/security/loopbackHost.ts +19 -0
  298. package/src/security/pathGuard.ts +35 -16
  299. package/src/security/planGuard.ts +2 -1
  300. package/src/security/runtimeGuard.ts +9 -6
  301. package/src/security/secretComparison.ts +13 -0
  302. package/src/security/sensitiveGuard.ts +27 -8
  303. package/src/security/toolInvocationGuard.ts +2 -2
  304. package/src/security/workspaceRootGuard.ts +53 -0
  305. package/src/smoke-test.ts +25 -24
  306. package/src/tools/catalog/index.ts +4 -0
  307. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  308. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +7 -16
  309. package/src/tools/definitions/toolDefs.ts +1264 -0
  310. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  311. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +99 -32
  312. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +8 -8
  313. package/src/tools/diagnostics/index.ts +6 -0
  314. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  315. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +27 -17
  316. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  317. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +6 -6
  318. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +7 -5
  319. package/src/tools/direct/index.ts +3 -0
  320. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  321. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  322. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  323. package/src/tools/discovery/index.ts +3 -0
  324. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  325. package/src/tools/dispatch/coreDispatch.ts +36 -31
  326. package/src/tools/dispatch/diagnosticDispatch.ts +26 -21
  327. package/src/tools/dispatch/directDispatch.ts +11 -10
  328. package/src/tools/dispatch/goalDispatch.ts +5 -4
  329. package/src/tools/dispatch/releaseDispatch.ts +3 -2
  330. package/src/tools/dispatch/validation.ts +78 -0
  331. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  332. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +41 -26
  333. package/src/tools/goals/index.ts +3 -0
  334. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  335. package/src/tools/registry.ts +17 -1261
  336. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  337. package/src/tools/release/index.ts +2 -0
  338. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  339. package/src/tools/tasks/cancelTask.ts +109 -0
  340. package/src/tools/{createTask.ts → tasks/createTask.ts} +49 -26
  341. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
  342. package/src/tools/{evidencePack.ts → tasks/evidencePack.ts} +36 -22
  343. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  344. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
  345. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  346. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
  347. package/src/tools/tasks/index.ts +18 -0
  348. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
  349. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
  350. package/src/tools/tasks/retryTask.ts +69 -0
  351. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +69 -34
  352. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  353. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  354. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  355. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  356. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  357. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  358. package/src/tools/workspace/index.ts +10 -0
  359. package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
  360. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  361. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  362. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  363. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  364. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  365. package/src/tools/workspace/syncFile.ts +292 -0
  366. package/src/utils/atomicFile.ts +58 -0
  367. package/src/utils/boundedFile.ts +116 -0
  368. package/src/utils/lockedJsonFile.ts +251 -0
  369. package/src/utils/platform.ts +31 -0
  370. package/src/utils/stableJson.ts +12 -0
  371. package/src/version.ts +3 -2
  372. package/src/watcherStatus.ts +19 -1
  373. package/ui/desktop-bootstrap.js +11 -0
  374. package/ui/desktop-bridge.js +36 -0
  375. package/ui/desktop.css +160 -0
  376. package/ui/getting-started.js +93 -0
  377. package/ui/i18n.js +484 -0
  378. package/ui/log-parser.js +30 -0
  379. package/ui/pages/audit.html +12 -0
  380. package/ui/pages/dashboard.html +90 -147
  381. package/ui/pages/direct-sessions.html +123 -26
  382. package/ui/pages/getting-started.html +43 -0
  383. package/ui/pages/logs.html +105 -22
  384. package/ui/pages/settings.html +101 -0
  385. package/ui/pages/task-detail.html +12 -0
  386. package/ui/pages/tasks.html +54 -8
  387. package/ui/pages/workspace.html +38 -1
  388. package/ui/settings.js +245 -0
  389. package/PatchWarden-Control.cmd +0 -6
  390. package/Restart-PatchWarden-Control.cmd +0 -6
  391. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  392. package/dist/test/unit/acceptance-engine.test.js +0 -228
  393. package/dist/test/unit/agent-router.test.d.ts +0 -1
  394. package/dist/test/unit/agent-router.test.js +0 -287
  395. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  396. package/dist/test/unit/android-doctor.test.js +0 -118
  397. package/dist/test/unit/apply-patch.test.d.ts +0 -1
  398. package/dist/test/unit/apply-patch.test.js +0 -225
  399. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  400. package/dist/test/unit/audit-checks.test.js +0 -350
  401. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  402. package/dist/test/unit/chinese-path.test.js +0 -91
  403. package/dist/test/unit/command-guard.test.d.ts +0 -1
  404. package/dist/test/unit/command-guard.test.js +0 -160
  405. package/dist/test/unit/create-task.test.d.ts +0 -1
  406. package/dist/test/unit/create-task.test.js +0 -197
  407. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  408. package/dist/test/unit/diagnose-task.test.js +0 -457
  409. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  410. package/dist/test/unit/direct-guards.test.js +0 -328
  411. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  412. package/dist/test/unit/discovery-token-store.test.js +0 -139
  413. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  414. package/dist/test/unit/evidence-pack.test.js +0 -224
  415. package/dist/test/unit/get-task-status.test.d.ts +0 -1
  416. package/dist/test/unit/get-task-status.test.js +0 -174
  417. package/dist/test/unit/get-task-summary.test.d.ts +0 -1
  418. package/dist/test/unit/get-task-summary.test.js +0 -146
  419. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  420. package/dist/test/unit/goal-graph.test.js +0 -298
  421. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  422. package/dist/test/unit/goal-progress.test.js +0 -381
  423. package/dist/test/unit/goal-report.test.d.ts +0 -1
  424. package/dist/test/unit/goal-report.test.js +0 -159
  425. package/dist/test/unit/goal-status.test.d.ts +0 -1
  426. package/dist/test/unit/goal-status.test.js +0 -215
  427. package/dist/test/unit/goal-store.test.d.ts +0 -1
  428. package/dist/test/unit/goal-store.test.js +0 -253
  429. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  430. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  431. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  432. package/dist/test/unit/goal-tools-registry.test.js +0 -192
  433. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  434. package/dist/test/unit/handoff-export.test.js +0 -263
  435. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  436. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  437. package/dist/test/unit/logging.test.d.ts +0 -1
  438. package/dist/test/unit/logging.test.js +0 -397
  439. package/dist/test/unit/path-guard.test.d.ts +0 -1
  440. package/dist/test/unit/path-guard.test.js +0 -133
  441. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  442. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  443. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  444. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  445. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  446. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  447. package/dist/test/unit/release-gate.test.d.ts +0 -1
  448. package/dist/test/unit/release-gate.test.js +0 -242
  449. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  450. package/dist/test/unit/run-task-loop.test.js +0 -380
  451. package/dist/test/unit/safe-status.test.d.ts +0 -1
  452. package/dist/test/unit/safe-status.test.js +0 -165
  453. package/dist/test/unit/safe-views.test.d.ts +0 -1
  454. package/dist/test/unit/safe-views.test.js +0 -171
  455. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  456. package/dist/test/unit/schema-drift-check.test.js +0 -176
  457. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  458. package/dist/test/unit/sensitive-guard.test.js +0 -104
  459. package/dist/test/unit/spec-kit-import.test.d.ts +0 -1
  460. package/dist/test/unit/spec-kit-import.test.js +0 -341
  461. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  462. package/dist/test/unit/subgoal-sync.test.js +0 -183
  463. package/dist/test/unit/sync-file.test.d.ts +0 -1
  464. package/dist/test/unit/sync-file.test.js +0 -154
  465. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  466. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  467. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  468. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  469. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  470. package/dist/test/unit/toolSearch.test.js +0 -571
  471. package/dist/test/unit/wait-for-task.test.d.ts +0 -1
  472. package/dist/test/unit/wait-for-task.test.js +0 -144
  473. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  474. package/dist/test/unit/watcher-status.test.js +0 -169
  475. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  476. package/dist/test/unit/worktree-manager.test.js +0 -176
  477. package/dist/tools/applyPatch.js +0 -41
  478. package/dist/tools/cancelTask.d.ts +0 -30
  479. package/dist/tools/cancelTask.js +0 -64
  480. package/dist/tools/finalizeDirectSession.js +0 -84
  481. package/dist/tools/goalSubgoalTask.js +0 -110
  482. package/dist/tools/killTask.d.ts +0 -15
  483. package/dist/tools/retryTask.js +0 -45
  484. package/dist/tools/runVerification.js +0 -36
  485. package/dist/tools/syncFile.js +0 -65
  486. package/docs/control-center/control-center-daily-driver.md +0 -211
  487. package/docs/control-center/control-center-mvp.md +0 -205
  488. package/docs/control-center/control-center-phase2.md +0 -159
  489. package/docs/execution-plan-2026-07-09.md +0 -315
  490. package/docs/release-v0.6.0.md +0 -71
  491. package/docs/release-v0.6.1.md +0 -75
  492. package/docs/release-v0.6.4.md +0 -45
  493. package/docs/roadmap-execution-and-acceptance.md +0 -365
  494. package/src/test/unit/acceptance-engine.test.ts +0 -261
  495. package/src/test/unit/agent-router.test.ts +0 -342
  496. package/src/test/unit/android-doctor.test.ts +0 -158
  497. package/src/test/unit/apply-patch.test.ts +0 -293
  498. package/src/test/unit/audit-checks.test.ts +0 -567
  499. package/src/test/unit/chinese-path.test.ts +0 -106
  500. package/src/test/unit/command-guard.test.ts +0 -221
  501. package/src/test/unit/create-task.test.ts +0 -255
  502. package/src/test/unit/diagnose-task.test.ts +0 -544
  503. package/src/test/unit/direct-guards.test.ts +0 -467
  504. package/src/test/unit/discovery-token-store.test.ts +0 -181
  505. package/src/test/unit/evidence-pack.test.ts +0 -251
  506. package/src/test/unit/get-task-status.test.ts +0 -203
  507. package/src/test/unit/get-task-summary.test.ts +0 -173
  508. package/src/test/unit/goal-graph.test.ts +0 -347
  509. package/src/test/unit/goal-progress.test.ts +0 -538
  510. package/src/test/unit/goal-report.test.ts +0 -189
  511. package/src/test/unit/goal-status.test.ts +0 -270
  512. package/src/test/unit/goal-store.test.ts +0 -318
  513. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  514. package/src/test/unit/goal-tools-registry.test.ts +0 -245
  515. package/src/test/unit/handoff-export.test.ts +0 -295
  516. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  517. package/src/test/unit/logging.test.ts +0 -497
  518. package/src/test/unit/path-guard.test.ts +0 -180
  519. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  520. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  521. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  522. package/src/test/unit/release-gate.test.ts +0 -314
  523. package/src/test/unit/run-task-loop.test.ts +0 -425
  524. package/src/test/unit/safe-status.test.ts +0 -187
  525. package/src/test/unit/safe-views.test.ts +0 -184
  526. package/src/test/unit/schema-drift-check.test.ts +0 -260
  527. package/src/test/unit/sensitive-guard.test.ts +0 -124
  528. package/src/test/unit/spec-kit-import.test.ts +0 -429
  529. package/src/test/unit/subgoal-sync.test.ts +0 -236
  530. package/src/test/unit/sync-file.test.ts +0 -231
  531. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  532. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  533. package/src/test/unit/toolSearch.test.ts +0 -756
  534. package/src/test/unit/wait-for-task.test.ts +0 -176
  535. package/src/test/unit/watcher-status.test.ts +0 -190
  536. package/src/test/unit/worktree-manager.test.ts +0 -247
  537. package/src/tools/cancelTask.ts +0 -70
  538. package/src/tools/retryTask.ts +0 -53
  539. package/src/tools/syncFile.ts +0 -122
  540. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  541. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  542. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  543. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  544. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  545. /package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +0 -0
  546. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  547. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  548. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  549. /package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +0 -0
  550. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  551. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  552. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  553. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  554. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  555. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  556. /package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +0 -0
  557. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  558. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  559. /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
  560. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  561. /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
  562. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  563. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  564. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  565. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  566. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  567. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  568. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  569. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  570. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  571. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  572. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
  573. /package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +0 -0
@@ -20,13 +20,13 @@ const RULES = [
20
20
  {
21
21
  category: "credential_assignment",
22
22
  reason: "matched credential assignment",
23
- pattern: /\b((?:access[_ -]?token|api[_ -]?key|secret|password|credential|token)\s*[:=]\s*)(["']?)([^\s,"'\]}]{8,})(["']?)/gi,
23
+ pattern: /\b((?:(?:[A-Za-z][A-Za-z0-9]*[_ -])*(?:access[_ -]?token|refresh[_ -]?token|api[_ -]?key|secret(?:[_ -]?access)?[_ -]?key|client[_ -]?secret|private[_ -]?key|secret|password|credential|token))\s*[:=]\s*)(["']?)([^\s,"'\]}]{8,})(["']?)/gi,
24
24
  replace: "$1$2[REDACTED]$4",
25
25
  },
26
26
  {
27
27
  category: "known_token_format",
28
28
  reason: "matched known token format",
29
- pattern: /\b(?:sk|ghp|github_pat)_[A-Za-z0-9_-]{16,}\b/g,
29
+ pattern: /\b(?:sk-(?:proj-|ant-)?[A-Za-z0-9_-]{16,}|sk_[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9_-]{16,}|github_pat_[A-Za-z0-9_-]{16,}|glpat-[A-Za-z0-9_-]{16,}|xox[baprs]-[A-Za-z0-9-]{16,}|(?:hf|npm)_[A-Za-z0-9_-]{16,}|(?:sk|rk)_live_[A-Za-z0-9_-]{16,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35}|eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b/g,
30
30
  replace: "[REDACTED TOKEN]",
31
31
  },
32
32
  ];
@@ -9,8 +9,8 @@
9
9
  * - 不持久化到磁盘(内存 Map,进程内共享,单例)
10
10
  * - token 单次使用语义(consumeToken 后即删除)
11
11
  */
12
- import type { ToolRisk } from "../tools/toolRegistry.js";
13
- import type { ToolProfile } from "../tools/toolCatalog.js";
12
+ import type { ToolRisk } from "../tools/catalog/toolRegistry.js";
13
+ import type { ToolProfile } from "../tools/catalog/toolCatalog.js";
14
14
  export interface DiscoveryTokenRecord {
15
15
  token: string;
16
16
  toolName: string;
@@ -31,6 +31,7 @@ export interface IssueTokenInput {
31
31
  allowedScope?: string[];
32
32
  ttlMs?: number;
33
33
  }
34
+ export declare const MAX_ACTIVE_DISCOVERY_TOKENS = 1024;
34
35
  /**
35
36
  * 生成并保存 discovery token,返回 token id。
36
37
  * token id 格式:dst_{YYYYMMDD}_{randomHex12}
@@ -13,6 +13,7 @@ import { randomBytes } from "node:crypto";
13
13
  import { PatchWardenError } from "../errors.js";
14
14
  // ── 常量 ──────────────────────────────────────────────────────────
15
15
  const DEFAULT_TTL_MS = 10 * 60 * 1000; // 10 分钟
16
+ export const MAX_ACTIVE_DISCOVERY_TOKENS = 1024;
16
17
  // ── 模块级 token store(单例,进程内共享) ───────────────────────
17
18
  const tokenStore = new Map();
18
19
  // ── 内部工具 ──────────────────────────────────────────────────────
@@ -27,15 +28,29 @@ function generateTokenId() {
27
28
  const randomPart = randomBytes(6).toString("hex"); // 12 位 hex
28
29
  return `dst_${datePart}_${randomPart}`;
29
30
  }
31
+ function pruneTokenStore(nowMs) {
32
+ for (const [token, record] of tokenStore) {
33
+ if (Date.parse(record.expiresAt) <= nowMs)
34
+ tokenStore.delete(token);
35
+ }
36
+ while (tokenStore.size >= MAX_ACTIVE_DISCOVERY_TOKENS) {
37
+ const oldest = tokenStore.keys().next().value;
38
+ if (!oldest)
39
+ break;
40
+ tokenStore.delete(oldest);
41
+ }
42
+ }
30
43
  // ── 公共 API ──────────────────────────────────────────────────────
31
44
  /**
32
45
  * 生成并保存 discovery token,返回 token id。
33
46
  * token id 格式:dst_{YYYYMMDD}_{randomHex12}
34
47
  */
35
48
  export function issueToken(input) {
36
- const now = new Date();
49
+ const nowMs = Date.now();
50
+ pruneTokenStore(nowMs);
51
+ const now = new Date(nowMs);
37
52
  const issuedAt = now.toISOString();
38
- const expiresAt = new Date(Date.now() + (input.ttlMs ?? DEFAULT_TTL_MS)).toISOString();
53
+ const expiresAt = new Date(nowMs + (input.ttlMs ?? DEFAULT_TTL_MS)).toISOString();
39
54
  const token = generateTokenId();
40
55
  const record = {
41
56
  token,
@@ -0,0 +1,2 @@
1
+ /** Validate an HTTP Host header for a service bound to IPv4 loopback. */
2
+ export declare function isTrustedLoopbackHostHeader(value: string | string[] | undefined, expectedPort: number): boolean;
@@ -0,0 +1,16 @@
1
+ import { firstHeaderValue } from "./secretComparison.js";
2
+ const LOOPBACK_HOST_RE = /^(127\.0\.0\.1|localhost)(?::([0-9]{1,5}))?$/i;
3
+ /** Validate an HTTP Host header for a service bound to IPv4 loopback. */
4
+ export function isTrustedLoopbackHostHeader(value, expectedPort) {
5
+ const raw = firstHeaderValue(value).trim();
6
+ const match = LOOPBACK_HOST_RE.exec(raw);
7
+ if (!match)
8
+ return false;
9
+ if (match[2] === undefined)
10
+ return true;
11
+ const declaredPort = Number(match[2]);
12
+ return Number.isInteger(declaredPort)
13
+ && declaredPort >= 1
14
+ && declaredPort <= 65_535
15
+ && declaredPort === expectedPort;
16
+ }
@@ -1,5 +1,5 @@
1
1
  import { basename, dirname, isAbsolute, normalize, parse, resolve, sep, } from "node:path";
2
- import { realpathSync } from "node:fs";
2
+ import { lstatSync, readlinkSync, realpathSync } from "node:fs";
3
3
  import { PatchWardenError } from "../errors.js";
4
4
  export function guardPath(requestedPath, workspaceRoot, allowedPrefix) {
5
5
  const ws = normalize(resolve(workspaceRoot));
@@ -55,7 +55,7 @@ function realPathOrSelf(value) {
55
55
  return normalize(value);
56
56
  }
57
57
  }
58
- function realPathOrExistingPrefix(candidate) {
58
+ function realPathOrExistingPrefix(candidate, seenLinks = new Set()) {
59
59
  const normalized = normalize(candidate);
60
60
  const root = parse(normalized).root;
61
61
  const suffix = [];
@@ -64,7 +64,33 @@ function realPathOrExistingPrefix(candidate) {
64
64
  try {
65
65
  return resolve(realpathSync(current), ...suffix);
66
66
  }
67
- catch {
67
+ catch (error) {
68
+ try {
69
+ const metadata = lstatSync(current);
70
+ if (metadata.isSymbolicLink()) {
71
+ const linkKey = process.platform === "win32" ? current.toLowerCase() : current;
72
+ if (seenLinks.has(linkKey)) {
73
+ throw new PatchWardenError("workspace_path_escape", `Symbolic link cycle detected while resolving "${candidate}"`, "Remove the cyclic link and use a path inside the configured workspace.", true, { path: candidate, operation: "path_access", safe_alternative: "Use a non-link path inside workspaceRoot." });
74
+ }
75
+ const nextSeen = new Set(seenLinks);
76
+ nextSeen.add(linkKey);
77
+ const linkTarget = readlinkSync(current);
78
+ const resolvedTarget = isAbsolute(linkTarget)
79
+ ? resolve(linkTarget)
80
+ : resolve(dirname(current), linkTarget);
81
+ return resolve(realPathOrExistingPrefix(resolvedTarget, nextSeen), ...suffix);
82
+ }
83
+ }
84
+ catch (linkError) {
85
+ if (linkError instanceof PatchWardenError)
86
+ throw linkError;
87
+ const linkCode = linkError.code;
88
+ if (linkCode !== "ENOENT" && linkCode !== "ENOTDIR")
89
+ throw linkError;
90
+ }
91
+ const code = error.code;
92
+ if (code !== "ENOENT" && code !== "ENOTDIR")
93
+ throw error;
68
94
  suffix.unshift(basename(current));
69
95
  const parent = dirname(current);
70
96
  if (parent === current)
@@ -72,24 +98,13 @@ function realPathOrExistingPrefix(candidate) {
72
98
  current = parent;
73
99
  }
74
100
  }
75
- try {
76
- return resolve(realpathSync(root || current), ...suffix);
77
- }
78
- catch {
79
- return normalized;
80
- }
101
+ return resolve(realpathSync(root || current), ...suffix);
81
102
  }
82
103
  function assertInside(candidate, root, requestedPath, message) {
83
104
  const normalizedRoot = normalize(root);
84
105
  const normalizedCandidate = normalize(candidate);
85
- const checkRoot = process.platform === "win32"
86
- ? normalizedRoot.toLowerCase()
87
- : normalizedRoot;
88
- const checkCandidate = process.platform === "win32"
89
- ? normalizedCandidate.toLowerCase()
90
- : normalizedCandidate;
91
- if (checkCandidate !== checkRoot &&
92
- !checkCandidate.startsWith(checkRoot + sep)) {
106
+ if (normalizedCandidate !== normalizedRoot &&
107
+ !normalizedCandidate.startsWith(normalizedRoot + sep)) {
93
108
  if (message.startsWith("Path escapes workspace")) {
94
109
  throw new PatchWardenError("workspace_path_escape", `Path escapes workspace: "${requestedPath}" resolves to "${normalizedCandidate}" which is outside "${normalizedRoot}"`, "Use a path inside the configured workspace and allowed prefix.", true, { path: requestedPath, operation: "path_access", safe_alternative: "Read a path inside the configured workspace and allowed prefix." });
95
110
  }
@@ -1,4 +1,5 @@
1
1
  import { PatchWardenError } from "../errors.js";
2
+ import { redactSensitiveContent } from "./contentRedaction.js";
2
3
  const PLAN_RULES = [
3
4
  {
4
5
  id: "plan_secret_access",
@@ -35,7 +36,7 @@ export function guardPlanContent(title, content) {
35
36
  throw new PatchWardenError(rule.id, `save_plan blocked content in category "${rule.category}".`, rule.suggestion, true, {
36
37
  operation: "save_plan",
37
38
  matched_category: rule.category,
38
- matched_text: match[0].slice(0, 160),
39
+ matched_text: redactSensitiveContent(match[0]).content.slice(0, 160),
39
40
  });
40
41
  }
41
42
  }
@@ -1,6 +1,7 @@
1
1
  import { resolve, join, dirname } from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { PatchWardenError } from "../errors.js";
4
+ import { isPathChildOf, isSamePath } from "../utils/platform.js";
4
5
  const CRITICAL_RUNTIME_DIRS = ["dist", "src", "scripts", "release"];
5
6
  /**
6
7
  * Refuse operations against the active PatchWarden runtime directory or its
@@ -8,15 +9,17 @@ const CRITICAL_RUNTIME_DIRS = ["dist", "src", "scripts", "release"];
8
9
  * and direct-session flows share the same protection.
9
10
  */
10
11
  export function guardRuntimeSelfModification(resolvedRepoPath) {
11
- const runtimeRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
12
- if (resolvedRepoPath === runtimeRoot ||
13
- resolvedRepoPath.startsWith(runtimeRoot + resolve("/")[0])) {
12
+ // runtimeGuard lives at <package-root>/{src,dist}/security/runtimeGuard.*.
13
+ const runtimeRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
14
+ const isRuntimeRoot = isSamePath(resolvedRepoPath, runtimeRoot);
15
+ if (isRuntimeRoot ||
16
+ isPathChildOf(resolvedRepoPath, runtimeRoot)) {
14
17
  const isCritical = CRITICAL_RUNTIME_DIRS.some((dir) => {
15
18
  const full = join(runtimeRoot, dir);
16
- return (resolvedRepoPath === full ||
17
- resolvedRepoPath.startsWith(full + resolve("/")[0]));
19
+ return (isSamePath(resolvedRepoPath, full) ||
20
+ isPathChildOf(resolvedRepoPath, full));
18
21
  });
19
- if (resolvedRepoPath === runtimeRoot || isCritical) {
22
+ if (isRuntimeRoot || isCritical) {
20
23
  throw new PatchWardenError("runtime_self_modification_blocked", `repo_path points to the active PatchWarden runtime or its critical subdirectories.`, "Use a dev copy or git worktree for PatchWarden development. The running MCP server must not be modified by a task.", true, {
21
24
  operation: "runtime_guard",
22
25
  path: resolvedRepoPath,
@@ -0,0 +1,3 @@
1
+ /** Compare secret text without content-dependent byte comparison. */
2
+ export declare function timingSafeStringEqual(actual: string, expected: string): boolean;
3
+ export declare function firstHeaderValue(value: string | string[] | undefined): string;
@@ -0,0 +1,11 @@
1
+ import { timingSafeEqual } from "node:crypto";
2
+ /** Compare secret text without content-dependent byte comparison. */
3
+ export function timingSafeStringEqual(actual, expected) {
4
+ const actualBytes = Buffer.from(actual, "utf-8");
5
+ const expectedBytes = Buffer.from(expected, "utf-8");
6
+ return actualBytes.length === expectedBytes.length
7
+ && timingSafeEqual(actualBytes, expectedBytes);
8
+ }
9
+ export function firstHeaderValue(value) {
10
+ return Array.isArray(value) ? (value[0] || "") : (value || "");
11
+ }
@@ -1,2 +1,7 @@
1
+ /**
2
+ * Detect a Windows NTFS alternate data stream suffix. The colon in an
3
+ * absolute drive prefix (for example, C:\\) is the only permitted colon.
4
+ */
5
+ export declare function hasWindowsAlternateDataStream(filePath: string): boolean;
1
6
  export declare function isSensitivePath(filePath: string): boolean;
2
7
  export declare function guardSensitivePath(filePath: string): void;
@@ -7,22 +7,28 @@ const SENSITIVE_PATTERNS = [
7
7
  // Exact filenames
8
8
  /(?:^|[\\/])\.env$/i,
9
9
  /(?:^|[\\/])\.env\..+$/i,
10
+ /(?:^|[\\/])\.envrc$/i,
10
11
  /(?:^|[\\/])id_rsa$/i,
12
+ /(?:^|[\\/])id_dsa$/i,
11
13
  /(?:^|[\\/])id_ed25519$/i,
12
14
  /(?:^|[\\/])id_ecdsa$/i,
13
15
  /(?:^|[\\/])\.ssh[\\/]/i,
14
16
  // Credentials / tokens
15
- /(?:^|[\\/])credentials/i,
17
+ /(?:^|[\\/])credentials(?:\.(?:txt|json|ya?ml|toml|ini|cfg|db))?$/i,
16
18
  /(?:^|[\\/])\.?aws[\\/]credentials/i,
17
19
  /(?:^|[\\/])\.netrc$/i,
18
20
  /(?:^|[\\/])\.npmrc$/i,
19
21
  /(?:^|[\\/])\.pypirc$/i,
20
- /(?:^|[\\/])token/i,
22
+ /(?:^|[\\/])token$/i,
23
+ /(?:^|[\\/])tokens?\.(?:txt|json|ya?ml|toml|ini|cfg|db)$/i,
24
+ /(?:^|[\\/])token[-_](?:store|cache|data|secret|auth|credentials|backup|local|prod|production|dev|development)(?:\.[^\\/]+)?$/i,
25
+ /(?:^|[\\/])(?:access[_-]?token|refresh[_-]?token|api[_-]?key|client[_-]?secret)(?:\.(?:txt|json|ya?ml|ini|cfg))?$/i,
21
26
  // Private keys
22
27
  /(?:^|[\\/])[^.]+\.pem$/i,
23
28
  /(?:^|[\\/])[^.]+\.key$/i,
24
29
  /(?:^|[\\/])[^.]+\.pfx$/i,
25
30
  /(?:^|[\\/])[^.]+\.p12$/i,
31
+ /(?:^|[\\/])[^.]+\.ppk$/i,
26
32
  // Browser data
27
33
  /(?:^|[\\/])cookies/i,
28
34
  /(?:^|[\\/])web data$/i,
@@ -30,17 +36,30 @@ const SENSITIVE_PATTERNS = [
30
36
  /(?:^|[\\/])local state$/i,
31
37
  // Other sensitive files
32
38
  /(?:^|[\\/])\.git-credentials$/i,
39
+ /(?:^|[\\/])\.git[\\/]config$/i,
33
40
  /(?:^|[\\/])\.docker[\\/]config\.json$/i,
34
41
  /(?:^|[\\/])\.kube[\\/]config$/i,
42
+ /(?:^|[\\/])kubeconfig$/i,
43
+ /(?:^|[\\/])application_default_credentials\.json$/i,
44
+ /(?:^|[\\/])service[-_]?account(?:[-_][^\\/]+)?\.json$/i,
35
45
  /(?:^|[\\/])config\.json$/i, // generic config files often contain local tokens or service credentials
36
46
  ];
37
- // Files specifically inside .patchwarden are always allowed
38
- const SAFE_PREFIX = ".patchwarden";
47
+ /**
48
+ * Detect a Windows NTFS alternate data stream suffix. The colon in an
49
+ * absolute drive prefix (for example, C:\\) is the only permitted colon.
50
+ */
51
+ export function hasWindowsAlternateDataStream(filePath) {
52
+ const withoutDrivePrefix = /^[A-Za-z]:[\\/]/.test(filePath)
53
+ ? filePath.slice(2)
54
+ : filePath;
55
+ return withoutDrivePrefix.includes(":");
56
+ }
39
57
  export function isSensitivePath(filePath) {
40
- // Files inside .patchwarden are always safe
41
- const normalized = filePath.replace(/\\/g, "/");
42
- if (normalized.includes(`${SAFE_PREFIX}/`) || normalized.endsWith(SAFE_PREFIX)) {
43
- return false;
58
+ if (filePath.includes("\0"))
59
+ return true;
60
+ // ADS can hide content behind an otherwise safe-looking filename.
61
+ if (hasWindowsAlternateDataStream(filePath)) {
62
+ return true;
44
63
  }
45
64
  for (const pattern of SENSITIVE_PATTERNS) {
46
65
  if (pattern.test(filePath)) {
@@ -14,8 +14,8 @@
14
14
  *
15
15
  * 加上 consumeToken 内的 token_not_found / token_expired,共 10 项调用前强制校验。
16
16
  */
17
- import { type PatchWardenToolMeta } from "../tools/toolRegistry.js";
18
- import type { ToolProfile } from "../tools/toolCatalog.js";
17
+ import { type PatchWardenToolMeta } from "../tools/catalog/toolRegistry.js";
18
+ import type { ToolProfile } from "../tools/catalog/toolCatalog.js";
19
19
  import type { DiscoveryTokenRecord } from "./discoveryTokenStore.js";
20
20
  export interface GuardInput {
21
21
  toolName: string;
@@ -16,7 +16,7 @@
16
16
  */
17
17
  import { PatchWardenError } from "../errors.js";
18
18
  import { guardSensitivePath } from "./sensitiveGuard.js";
19
- import { TOOL_RISK_RANK } from "../tools/toolRegistry.js";
19
+ import { TOOL_RISK_RANK } from "../tools/catalog/toolRegistry.js";
20
20
  // ── 内部常量 ──────────────────────────────────────────────────────
21
21
  /**
22
22
  * 常见路径参数字段名,用于 workspace_read_sensitive 校验时扫描 args。
@@ -0,0 +1,8 @@
1
+ export interface WorkspaceRootValidation {
2
+ ok: boolean;
3
+ path: string;
4
+ reason: string | null;
5
+ category: "valid" | "missing" | "not_directory" | "unsafe_root";
6
+ }
7
+ export declare function unsafeWorkspaceRootLabel(value: string, userHome?: string): string | null;
8
+ export declare function validateWorkspaceRoot(value: string, userHome?: string): WorkspaceRootValidation;
@@ -0,0 +1,47 @@
1
+ import { existsSync, realpathSync, statSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { basename, normalize, resolve, win32 } from "node:path";
4
+ function normalizeInput(value) {
5
+ return /^[a-z]:[\\/]/i.test(value) ? win32.normalize(value) : normalize(resolve(value));
6
+ }
7
+ function comparable(value) {
8
+ return normalizeInput(value).replace(/[\\/]+$/, "").toLowerCase();
9
+ }
10
+ export function unsafeWorkspaceRootLabel(value, userHome = homedir()) {
11
+ const resolved = normalizeInput(value);
12
+ const trimmed = resolved.replace(/[\\/]+$/, "");
13
+ const windowsStyle = /^[a-z]:[\\/]/i.test(value) || /^\\\\/.test(value);
14
+ const leaf = (windowsStyle ? win32.basename(trimmed) : basename(trimmed)).toLowerCase();
15
+ if (/^[a-z]:$/i.test(trimmed) || trimmed === "" || trimmed === "/")
16
+ return "drive root";
17
+ if (comparable(trimmed) === comparable(userHome))
18
+ return "user home directory";
19
+ if (leaf === "desktop")
20
+ return "Desktop";
21
+ if (leaf === "downloads")
22
+ return "Downloads";
23
+ if (leaf === "documents")
24
+ return "Documents";
25
+ return null;
26
+ }
27
+ export function validateWorkspaceRoot(value, userHome = homedir()) {
28
+ const lexicalPath = normalize(resolve(value || "."));
29
+ if (!existsSync(lexicalPath)) {
30
+ return { ok: false, path: lexicalPath, reason: "workspaceRoot does not exist", category: "missing" };
31
+ }
32
+ let resolved;
33
+ try {
34
+ resolved = normalize(realpathSync(lexicalPath));
35
+ if (!statSync(resolved).isDirectory()) {
36
+ return { ok: false, path: resolved, reason: "workspaceRoot is not a directory", category: "not_directory" };
37
+ }
38
+ }
39
+ catch {
40
+ return { ok: false, path: lexicalPath, reason: "workspaceRoot is not accessible", category: "not_directory" };
41
+ }
42
+ const unsafeLabel = unsafeWorkspaceRootLabel(resolved, userHome);
43
+ if (unsafeLabel) {
44
+ return { ok: false, path: resolved, reason: `workspaceRoot cannot be ${unsafeLabel}`, category: "unsafe_root" };
45
+ }
46
+ return { ok: true, path: resolved, reason: null, category: "valid" };
47
+ }
@@ -18,32 +18,32 @@ import { tmpdir } from "node:os";
18
18
  import { fileURLToPath } from "node:url";
19
19
  import { spawnSync } from "node:child_process";
20
20
  import { loadConfig, getConfig, getTasksDir, reloadConfig } from "./config.js";
21
- import { savePlan } from "./tools/savePlan.js";
22
- import { getPlan } from "./tools/getPlan.js";
23
- import { createTask } from "./tools/createTask.js";
21
+ import { savePlan } from "./tools/goals/savePlan.js";
22
+ import { getPlan } from "./tools/goals/getPlan.js";
23
+ import { createTask } from "./tools/tasks/createTask.js";
24
24
  import { confirmAssessment, readAssessment, createAssessment } from "./assessments/assessmentStore.js";
25
25
  import { captureRepoSnapshot } from "./runner/changeCapture.js";
26
- import { getTaskStatus } from "./tools/getTaskStatus.js";
27
- import { getResult, getDiff, getTestLog } from "./tools/taskOutputs.js";
28
- import { listWorkspace } from "./tools/listWorkspace.js";
29
- import { readWorkspaceFile } from "./tools/readWorkspaceFile.js";
30
- import { listTasks } from "./tools/listTasks.js";
31
- import { cancelTask } from "./tools/cancelTask.js";
32
- import { retryTask } from "./tools/retryTask.js";
33
- import { getTaskStdoutTail } from "./tools/getTaskStdoutTail.js";
34
- import { auditTask } from "./tools/auditTask.js";
35
- import { getTaskSummary } from "./tools/getTaskSummary.js";
26
+ import { getTaskStatus } from "./tools/tasks/getTaskStatus.js";
27
+ import { getResult, getDiff, getTestLog } from "./tools/tasks/taskOutputs.js";
28
+ import { listWorkspace } from "./tools/workspace/listWorkspace.js";
29
+ import { readWorkspaceFile } from "./tools/workspace/readWorkspaceFile.js";
30
+ import { listTasks } from "./tools/tasks/listTasks.js";
31
+ import { cancelTask } from "./tools/tasks/cancelTask.js";
32
+ import { retryTask } from "./tools/tasks/retryTask.js";
33
+ import { getTaskStdoutTail } from "./tools/tasks/getTaskStdoutTail.js";
34
+ import { auditTask } from "./tools/diagnostics/auditTask.js";
35
+ import { getTaskSummary } from "./tools/tasks/getTaskSummary.js";
36
36
  import { guardAgentCommand } from "./security/commandGuard.js";
37
37
  import { getToolDefs } from "./tools/registry.js";
38
- import { buildToolCatalogSnapshot, CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES, selectToolsForProfile, } from "./tools/toolCatalog.js";
38
+ import { buildToolCatalogSnapshot, CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES, selectToolsForProfile, } from "./tools/catalog/toolCatalog.js";
39
39
  import { errorPayload } from "./errors.js";
40
40
  import { readWatcherStatus } from "./watcherStatus.js";
41
- import { createDirectSession } from "./tools/createDirectSession.js";
42
- import { searchWorkspace } from "./tools/searchWorkspace.js";
43
- import { applyPatch } from "./tools/applyPatch.js";
44
- import { runVerification } from "./tools/runVerification.js";
45
- import { finalizeDirectSession } from "./tools/finalizeDirectSession.js";
46
- import { auditSession } from "./tools/auditSession.js";
41
+ import { createDirectSession } from "./tools/direct/createDirectSession.js";
42
+ import { searchWorkspace } from "./tools/workspace/searchWorkspace.js";
43
+ import { applyPatch } from "./tools/workspace/applyPatch.js";
44
+ import { runVerification } from "./tools/tasks/runVerification.js";
45
+ import { finalizeDirectSession } from "./tools/direct/finalizeDirectSession.js";
46
+ import { auditSession } from "./tools/diagnostics/auditSession.js";
47
47
  import { readDirectSession, updateDirectSession } from "./direct/directSessionStore.js";
48
48
  import { createHash } from "node:crypto";
49
49
  // Resolve the actual node binary path (spawnSync needs it on WSL/Windows)
@@ -418,15 +418,16 @@ const sensitiveFiles = [
418
418
  "cookies.sqlite",
419
419
  ".git-credentials",
420
420
  "config.json",
421
+ ".patchwarden/.env",
422
+ ".patchwarden/credentials.json",
421
423
  ];
422
424
  for (const sf of sensitiveFiles) {
423
425
  await testReject(`C. readWorkspaceFile blocks "${sf}"`, () => {
424
426
  readWorkspaceFile(sf);
425
427
  });
426
428
  }
427
- // Files inside .patchwarden should always be allowed
428
- await test("C. readWorkspaceFile allows .patchwarden/plans/...", () => {
429
- // This should work because .patchwarden files are whitelisted
429
+ // Ordinary PatchWarden artifacts remain readable through their dedicated tool.
430
+ await test("C. getPlan reads ordinary .patchwarden/plans artifacts", () => {
430
431
  const plan = savePlan({ title: "Allowlist Test", content: "test" });
431
432
  const result = getPlan({ plan_id: plan.plan_id });
432
433
  if (!result.content.includes("test"))
@@ -0,0 +1,4 @@
1
+ export * from "./toolCatalog.js";
2
+ export * from "./toolRegistry.js";
3
+ export * from "./toolSearch.js";
4
+ export * from "./toolUsageStats.js";
@@ -0,0 +1,4 @@
1
+ export * from "./toolCatalog.js";
2
+ export * from "./toolRegistry.js";
3
+ export * from "./toolSearch.js";
4
+ export * from "./toolUsageStats.js";
@@ -1,5 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
- import { PATCHWARDEN_VERSION, TOOL_SCHEMA_EPOCH } from "../version.js";
2
+ import { PATCHWARDEN_VERSION, TOOL_SCHEMA_EPOCH } from "../../version.js";
3
+ import { stableJsonStringify } from "../../utils/stableJson.js";
3
4
  export const CHATGPT_CORE_TOOL_NAMES = [
4
5
  "health_check",
5
6
  "list_agents",
@@ -101,7 +102,7 @@ export function buildToolCatalogSnapshot(tools, profile) {
101
102
  inputSchema: tool.inputSchema,
102
103
  }));
103
104
  const tool_manifest_sha256 = createHash("sha256")
104
- .update(stableJson(manifestInput))
105
+ .update(stableJsonStringify(manifestInput))
105
106
  .digest("hex");
106
107
  lastSnapshot = {
107
108
  server_version: PATCHWARDEN_VERSION,
@@ -116,13 +117,3 @@ export function buildToolCatalogSnapshot(tools, profile) {
116
117
  export function getLastToolCatalogSnapshot() {
117
118
  return lastSnapshot ? { ...lastSnapshot, tool_names: [...lastSnapshot.tool_names] } : null;
118
119
  }
119
- function stableJson(value) {
120
- if (Array.isArray(value))
121
- return `[${value.map(stableJson).join(",")}]`;
122
- if (value && typeof value === "object") {
123
- const entries = Object.entries(value)
124
- .sort(([left], [right]) => left.localeCompare(right));
125
- return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`).join(",")}}`;
126
- }
127
- return JSON.stringify(value);
128
- }
@@ -5,8 +5,9 @@
5
5
  * schema digest 使用稳定字段顺序的 canonical JSON → sha256 计算,
6
6
  * 用于检测工具 schema 漂移、release check 校验、discover_tools 返回。
7
7
  */
8
+ import { stableJsonStringify } from "../../utils/stableJson.js";
8
9
  import type { ToolProfile } from "./toolCatalog.js";
9
- import type { ToolDef } from "./registry.js";
10
+ import type { ToolDef } from "../definitions/toolDefs.js";
10
11
  export type ToolRisk = "readonly" | "workspace_read_sensitive" | "workspace_write" | "command" | "release" | "credential_sensitive";
11
12
  export declare const TOOL_RISK_RANK: Record<ToolRisk, number>;
12
13
  export type ToolMode = "delegate" | "direct" | "audit" | "release" | "diagnostic";
@@ -42,7 +43,7 @@ interface StaticToolMeta {
42
43
  /**
43
44
  * 稳定 JSON 序列化:按字段名排序,避免 key 顺序导致 digest 漂移。
44
45
  */
45
- export declare function stableJsonStringify(value: unknown): string;
46
+ export { stableJsonStringify };
46
47
  /**
47
48
  * 计算 inputSchema 的 sha256 digest。
48
49
  * 返回格式:"sha256:<hex>"
@@ -58,4 +59,3 @@ export declare function buildToolRegistry(tools: ToolDef[]): PatchWardenToolMeta
58
59
  * 获取单个工具的元数据(不含 schema digest,digest 需从 registry 获取)。
59
60
  */
60
61
  export declare function getStaticToolMeta(name: string): StaticToolMeta | undefined;
61
- export {};
@@ -6,6 +6,7 @@
6
6
  * 用于检测工具 schema 漂移、release check 校验、discover_tools 返回。
7
7
  */
8
8
  import { createHash } from "node:crypto";
9
+ import { stableJsonStringify } from "../../utils/stableJson.js";
9
10
  export const TOOL_RISK_RANK = {
10
11
  readonly: 0,
11
12
  workspace_read_sensitive: 1,
@@ -56,7 +57,7 @@ const STATIC_TOOL_META = {
56
57
  summary: "列出已配置的本地 agent 及其可用性",
57
58
  profiles: ["full", "chatgpt_core"],
58
59
  modes: ["delegate", "diagnostic"],
59
- tags: ["agent", "list", "config", "codex", "opencode"],
60
+ tags: ["agent", "list", "config", "local-cli", "model"],
60
61
  aliases: ["agents", "list_agent"],
61
62
  risk: "readonly",
62
63
  requiresConfirmation: false,
@@ -201,7 +202,7 @@ const STATIC_TOOL_META = {
201
202
  modes: ["delegate", "audit"],
202
203
  tags: ["safe", "audit", "checks", "acceptance", "task"],
203
204
  aliases: ["safe_audit", "audit_summary"],
204
- risk: "readonly",
205
+ risk: "workspace_write",
205
206
  requiresConfirmation: false,
206
207
  relatedTools: ["audit_task", "safe_result"],
207
208
  },
@@ -545,7 +546,7 @@ const STATIC_TOOL_META = {
545
546
  modes: ["direct", "audit"],
546
547
  tags: ["safe", "direct", "finalize", "summary"],
547
548
  aliases: ["safe_finalize", "finalize_summary"],
548
- risk: "readonly",
549
+ risk: "workspace_write",
549
550
  requiresConfirmation: false,
550
551
  relatedTools: ["finalize_direct_session", "safe_direct_summary"],
551
552
  },
@@ -556,7 +557,7 @@ const STATIC_TOOL_META = {
556
557
  modes: ["direct", "audit"],
557
558
  tags: ["safe", "direct", "audit", "session"],
558
559
  aliases: ["safe_direct_audit", "audit_direct_summary"],
559
- risk: "readonly",
560
+ risk: "workspace_write",
560
561
  requiresConfirmation: false,
561
562
  relatedTools: ["audit_session", "safe_finalize_direct_session"],
562
563
  },
@@ -798,17 +799,7 @@ const STATIC_TOOL_META = {
798
799
  /**
799
800
  * 稳定 JSON 序列化:按字段名排序,避免 key 顺序导致 digest 漂移。
800
801
  */
801
- export function stableJsonStringify(value) {
802
- if (Array.isArray(value)) {
803
- return `[${value.map(stableJsonStringify).join(",")}]`;
804
- }
805
- if (value !== null && typeof value === "object") {
806
- const entries = Object.entries(value)
807
- .sort(([left], [right]) => left.localeCompare(right));
808
- return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableJsonStringify(entry)}`).join(",")}}`;
809
- }
810
- return JSON.stringify(value);
811
- }
802
+ export { stableJsonStringify };
812
803
  /**
813
804
  * 计算 inputSchema 的 sha256 digest。
814
805
  * 返回格式:"sha256:<hex>"
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Tool definitions for PatchWarden MCP server.
3
+ *
4
+ * Extracted from registry.ts to keep the registry focused on dispatch
5
+ * wiring. getToolDefs() builds the active tool list based on config
6
+ * (test/direct allow-lists, agent names, profile selection) and refreshes
7
+ * the tool catalog snapshot as a side effect.
8
+ */
9
+ export interface ToolDef {
10
+ name: string;
11
+ description: string;
12
+ inputSchema: {
13
+ type: "object";
14
+ properties: Record<string, unknown>;
15
+ required?: string[];
16
+ };
17
+ }
18
+ export declare function getToolDefs(): ToolDef[];