patchwarden 1.5.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (597) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +194 -26
  3. package/README.md +160 -22
  4. package/dist/assessments/agentAssessor.d.ts +1 -1
  5. package/dist/assessments/agentAssessor.js +24 -15
  6. package/dist/assessments/assessmentStore.d.ts +1 -1
  7. package/dist/assessments/assessmentStore.js +44 -36
  8. package/dist/assessments/confirmCli.js +5 -4
  9. package/dist/config.d.ts +22 -0
  10. package/dist/config.js +98 -11
  11. package/dist/control/fileManager.d.ts +2 -0
  12. package/dist/control/fileManager.js +39 -0
  13. package/dist/control/middleware/auth.d.ts +16 -0
  14. package/dist/control/middleware/auth.js +17 -0
  15. package/dist/control/middleware/static.d.ts +3 -0
  16. package/dist/control/middleware/static.js +78 -0
  17. package/dist/control/routeTable.d.ts +24 -0
  18. package/dist/control/routeTable.js +263 -0
  19. package/dist/control/routes/audit.d.ts +15 -0
  20. package/dist/control/routes/audit.js +287 -0
  21. package/dist/control/routes/evidence.d.ts +4 -0
  22. package/dist/control/routes/evidence.js +96 -0
  23. package/dist/control/routes/lineage.d.ts +3 -0
  24. package/dist/control/routes/lineage.js +71 -0
  25. package/dist/control/routes/policy.d.ts +3 -0
  26. package/dist/control/routes/policy.js +81 -0
  27. package/dist/control/routes/process.d.ts +12 -0
  28. package/dist/control/routes/process.js +487 -0
  29. package/dist/control/routes/sessions.d.ts +30 -0
  30. package/dist/control/routes/sessions.js +287 -0
  31. package/dist/control/routes/status.d.ts +51 -0
  32. package/dist/control/routes/status.js +307 -0
  33. package/dist/control/routes/taskActions.d.ts +21 -0
  34. package/dist/control/routes/taskActions.js +197 -0
  35. package/dist/control/routes/tasks.d.ts +24 -0
  36. package/dist/control/routes/tasks.js +309 -0
  37. package/dist/control/routes/workspace.d.ts +15 -0
  38. package/dist/control/routes/workspace.js +220 -0
  39. package/dist/control/runtime.d.ts +92 -0
  40. package/dist/control/runtime.js +425 -0
  41. package/dist/control/server.d.ts +13 -0
  42. package/dist/control/server.js +150 -0
  43. package/dist/control/shared.d.ts +59 -0
  44. package/dist/control/shared.js +341 -0
  45. package/dist/controlCenter.d.ts +6 -0
  46. package/dist/controlCenter.js +7 -2197
  47. package/dist/direct/directAudit.js +234 -225
  48. package/dist/direct/directGuards.d.ts +2 -1
  49. package/dist/direct/directGuards.js +72 -37
  50. package/dist/direct/directPatch.d.ts +6 -1
  51. package/dist/direct/directPatch.js +55 -8
  52. package/dist/direct/directSessionStore.d.ts +16 -6
  53. package/dist/direct/directSessionStore.js +127 -35
  54. package/dist/direct/directVerification.js +8 -16
  55. package/dist/doctor.d.ts +18 -1
  56. package/dist/doctor.js +614 -373
  57. package/dist/goal/acceptanceEngine.d.ts +1 -1
  58. package/dist/goal/goalProgress.js +75 -69
  59. package/dist/goal/goalReport.d.ts +54 -0
  60. package/dist/goal/goalReport.js +197 -0
  61. package/dist/goal/goalStatus.d.ts +8 -0
  62. package/dist/goal/goalStatus.js +10 -3
  63. package/dist/goal/goalStore.d.ts +9 -2
  64. package/dist/goal/goalStore.js +152 -45
  65. package/dist/goal/handoffExport.js +6 -6
  66. package/dist/goal/specKitImport.d.ts +63 -0
  67. package/dist/goal/specKitImport.js +221 -0
  68. package/dist/goal/subgoalSync.js +13 -10
  69. package/dist/goal/worktreeManager.d.ts +2 -0
  70. package/dist/goal/worktreeManager.js +209 -136
  71. package/dist/httpServer.js +126 -44
  72. package/dist/index.js +7 -4
  73. package/dist/logging.d.ts +7 -1
  74. package/dist/logging.js +13 -3
  75. package/dist/policy/projectPolicy.js +5 -2
  76. package/dist/release/releaseGate.d.ts +1 -1
  77. package/dist/release/releaseGate.js +78 -25
  78. package/dist/runner/agentInvocation.d.ts +14 -0
  79. package/dist/runner/agentInvocation.js +96 -4
  80. package/dist/runner/changeCapture.d.ts +14 -6
  81. package/dist/runner/changeCapture.js +287 -81
  82. package/dist/runner/cli.js +7 -6
  83. package/dist/runner/postTaskCleanup.js +60 -7
  84. package/dist/runner/processSecurity.d.ts +56 -0
  85. package/dist/runner/processSecurity.js +373 -0
  86. package/dist/runner/runTask.d.ts +1 -1
  87. package/dist/runner/runTask.js +339 -287
  88. package/dist/runner/simpleProcess.d.ts +3 -0
  89. package/dist/runner/simpleProcess.js +52 -38
  90. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  91. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  92. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
  93. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  94. package/dist/runner/taskStatusStore.d.ts +11 -0
  95. package/dist/runner/taskStatusStore.js +24 -0
  96. package/dist/runner/watch.d.ts +14 -1
  97. package/dist/runner/watch.js +265 -54
  98. package/dist/security/contentRedaction.d.ts +6 -0
  99. package/dist/security/contentRedaction.js +24 -2
  100. package/dist/security/discoveryTokenStore.d.ts +3 -2
  101. package/dist/security/discoveryTokenStore.js +17 -2
  102. package/dist/security/loopbackHost.d.ts +2 -0
  103. package/dist/security/loopbackHost.js +16 -0
  104. package/dist/security/pathGuard.js +32 -17
  105. package/dist/security/planGuard.js +2 -1
  106. package/dist/security/runtimeGuard.js +9 -6
  107. package/dist/security/secretComparison.d.ts +3 -0
  108. package/dist/security/secretComparison.js +11 -0
  109. package/dist/security/sensitiveGuard.d.ts +5 -0
  110. package/dist/security/sensitiveGuard.js +27 -8
  111. package/dist/security/toolInvocationGuard.d.ts +2 -2
  112. package/dist/security/toolInvocationGuard.js +1 -1
  113. package/dist/security/workspaceRootGuard.d.ts +8 -0
  114. package/dist/security/workspaceRootGuard.js +47 -0
  115. package/dist/smoke-test.js +280 -273
  116. package/dist/tools/catalog/index.d.ts +4 -0
  117. package/dist/tools/catalog/index.js +4 -0
  118. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  119. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  120. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +28 -15
  121. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  122. package/dist/tools/definitions/toolDefs.js +1177 -0
  123. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  124. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  125. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +8 -63
  126. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +111 -37
  127. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
  128. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +10 -10
  129. package/dist/tools/diagnostics/index.d.ts +6 -0
  130. package/dist/tools/diagnostics/index.js +6 -0
  131. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  132. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  133. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +14 -10
  134. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +25 -15
  135. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  136. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  137. package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +1 -1
  138. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +8 -8
  139. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +2 -2
  140. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  141. package/dist/tools/direct/index.d.ts +3 -0
  142. package/dist/tools/direct/index.js +3 -0
  143. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  144. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  145. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  146. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  147. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  148. package/dist/tools/discovery/index.d.ts +3 -0
  149. package/dist/tools/discovery/index.js +3 -0
  150. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  151. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  152. package/dist/tools/dispatch/coreDispatch.d.ts +9 -0
  153. package/dist/tools/dispatch/coreDispatch.js +283 -0
  154. package/dist/tools/dispatch/diagnosticDispatch.d.ts +12 -0
  155. package/dist/tools/dispatch/diagnosticDispatch.js +80 -0
  156. package/dist/tools/dispatch/directDispatch.d.ts +8 -0
  157. package/dist/tools/dispatch/directDispatch.js +116 -0
  158. package/dist/tools/dispatch/goalDispatch.d.ts +8 -0
  159. package/dist/tools/dispatch/goalDispatch.js +92 -0
  160. package/dist/tools/dispatch/releaseDispatch.d.ts +8 -0
  161. package/dist/tools/dispatch/releaseDispatch.js +46 -0
  162. package/dist/tools/dispatch/types.d.ts +23 -0
  163. package/dist/tools/dispatch/types.js +15 -0
  164. package/dist/tools/dispatch/validation.d.ts +7 -0
  165. package/dist/tools/dispatch/validation.js +71 -0
  166. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  167. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +2 -2
  168. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  169. package/dist/tools/goals/index.d.ts +3 -0
  170. package/dist/tools/goals/index.js +3 -0
  171. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  172. package/dist/tools/registry.d.ts +10 -14
  173. package/dist/tools/registry.js +47 -1651
  174. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  175. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  176. package/dist/tools/release/index.d.ts +2 -0
  177. package/dist/tools/release/index.js +2 -0
  178. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  179. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  180. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  181. package/dist/tools/tasks/cancelTask.js +85 -0
  182. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
  183. package/dist/tools/{createTask.js → tasks/createTask.js} +46 -31
  184. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  185. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
  186. package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +6 -0
  187. package/dist/tools/tasks/evidencePack.js +387 -0
  188. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  189. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  190. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
  191. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  192. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
  193. package/dist/tools/tasks/index.d.ts +18 -0
  194. package/dist/tools/tasks/index.js +18 -0
  195. package/dist/tools/tasks/killTask.d.ts +1 -0
  196. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  197. package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
  198. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
  199. package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
  200. package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +2 -2
  201. package/dist/tools/tasks/retryTask.js +60 -0
  202. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  203. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +59 -31
  204. package/dist/tools/tasks/runVerification.js +38 -0
  205. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  206. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  207. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  208. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  209. package/dist/tools/workspace/applyPatch.js +53 -0
  210. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  211. package/dist/tools/workspace/index.d.ts +10 -0
  212. package/dist/tools/workspace/index.js +10 -0
  213. package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
  214. package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
  215. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  216. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  217. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  218. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  219. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  220. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  221. package/dist/tools/workspace/syncFile.js +134 -0
  222. package/dist/utils/atomicFile.d.ts +7 -0
  223. package/dist/utils/atomicFile.js +48 -0
  224. package/dist/utils/boundedFile.d.ts +8 -0
  225. package/dist/utils/boundedFile.js +93 -0
  226. package/dist/utils/lockedJsonFile.d.ts +15 -0
  227. package/dist/utils/lockedJsonFile.js +224 -0
  228. package/dist/utils/platform.d.ts +11 -0
  229. package/dist/utils/platform.js +26 -0
  230. package/dist/utils/stableJson.d.ts +2 -0
  231. package/dist/utils/stableJson.js +12 -0
  232. package/dist/version.d.ts +3 -2
  233. package/dist/version.js +3 -2
  234. package/dist/watcherStatus.d.ts +2 -2
  235. package/dist/watcherStatus.js +18 -0
  236. package/docs/CODE_WIKI.md +1648 -0
  237. package/docs/agentseal-integration.md +150 -0
  238. package/docs/architecture.md +23 -0
  239. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  240. package/docs/assets/patchwarden-oss-demo.gif +0 -0
  241. package/docs/control-center/README.md +4 -4
  242. package/docs/dashboard-overview.md +86 -0
  243. package/docs/demo.md +8 -0
  244. package/docs/desktop-app.md +175 -0
  245. package/docs/direct-session-workflow.md +98 -0
  246. package/docs/evidence-pack-schema.md +215 -0
  247. package/docs/lineage-evidence-pack-workflow.md +127 -0
  248. package/docs/mcp-inspector-testing.md +200 -0
  249. package/docs/open-source-application.md +168 -0
  250. package/docs/opencode-worker.md +151 -0
  251. package/docs/openhands-worker.md +181 -0
  252. package/docs/release-checklist.md +1 -1
  253. package/docs/release-evidence.md +82 -0
  254. package/docs/spec-kit-integration.md +131 -0
  255. package/docs/task-safe-review-workflow.md +98 -0
  256. package/docs/threat-model.md +97 -0
  257. package/docs/user-feedback.md +40 -0
  258. package/docs/why-patchwarden.md +110 -0
  259. package/examples/config.example.json +4 -2
  260. package/package.json +28 -11
  261. package/scripts/README.md +15 -6
  262. package/scripts/build.js +11 -0
  263. package/scripts/checks/brand-check.js +3 -0
  264. package/scripts/checks/build-output-check.js +37 -0
  265. package/scripts/checks/control-center-smoke.js +447 -6
  266. package/scripts/checks/control-smoke.js +19 -8
  267. package/scripts/checks/http-mcp-smoke.js +82 -4
  268. package/scripts/checks/lifecycle-smoke.js +39 -33
  269. package/scripts/checks/mcp-manifest-check.js +1 -1
  270. package/scripts/checks/mcp-smoke.js +30 -4
  271. package/scripts/checks/package-manifest-check.js +20 -7
  272. package/scripts/checks/unit-tests.js +3 -5
  273. package/scripts/checks/watcher-supervisor-smoke.js +9 -0
  274. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  275. package/scripts/control/manage-patchwarden.ps1 +69 -4
  276. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  277. package/scripts/control/run-background-supervisor.ps1 +41 -0
  278. package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
  279. package/scripts/generate-demo-gif.py +320 -0
  280. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  281. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  282. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  283. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  284. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  285. package/scripts/lib/clean-generated-output.js +11 -0
  286. package/scripts/lib/file-discovery.js +12 -0
  287. package/scripts/release/desktop-preflight.js +211 -0
  288. package/scripts/release/pack-clean.js +59 -14
  289. package/src/assessments/agentAssessor.ts +29 -15
  290. package/src/assessments/assessmentStore.ts +55 -43
  291. package/src/assessments/confirmCli.ts +5 -4
  292. package/src/config.ts +105 -11
  293. package/src/control/fileManager.ts +40 -0
  294. package/src/control/middleware/auth.ts +35 -0
  295. package/src/control/middleware/static.ts +84 -0
  296. package/src/control/routeTable.ts +301 -0
  297. package/src/control/routes/audit.ts +329 -0
  298. package/src/control/routes/evidence.ts +107 -0
  299. package/src/control/routes/lineage.ts +92 -0
  300. package/src/control/routes/policy.ts +81 -0
  301. package/src/control/routes/process.ts +505 -0
  302. package/src/control/routes/sessions.ts +314 -0
  303. package/src/control/routes/status.ts +405 -0
  304. package/src/control/routes/taskActions.ts +214 -0
  305. package/src/control/routes/tasks.ts +331 -0
  306. package/src/control/routes/workspace.ts +234 -0
  307. package/src/control/runtime.ts +508 -0
  308. package/src/control/server.ts +165 -0
  309. package/src/control/shared.ts +360 -0
  310. package/src/controlCenter.ts +7 -2347
  311. package/src/direct/directAudit.ts +17 -9
  312. package/src/direct/directGuards.ts +100 -51
  313. package/src/direct/directPatch.ts +95 -8
  314. package/src/direct/directSessionStore.ts +211 -55
  315. package/src/direct/directVerification.ts +9 -23
  316. package/src/doctor.ts +766 -492
  317. package/src/goal/acceptanceEngine.ts +1 -1
  318. package/src/goal/goalProgress.ts +17 -13
  319. package/src/goal/goalReport.ts +263 -0
  320. package/src/goal/goalStatus.ts +25 -3
  321. package/src/goal/goalStore.ts +241 -54
  322. package/src/goal/handoffExport.ts +6 -6
  323. package/src/goal/specKitImport.ts +355 -0
  324. package/src/goal/subgoalSync.ts +15 -10
  325. package/src/goal/worktreeManager.ts +139 -22
  326. package/src/httpServer.ts +130 -44
  327. package/src/index.ts +7 -4
  328. package/src/logging.ts +20 -4
  329. package/src/policy/projectPolicy.ts +5 -2
  330. package/src/release/releaseGate.ts +74 -27
  331. package/src/runner/agentInvocation.ts +126 -4
  332. package/src/runner/changeCapture.ts +342 -80
  333. package/src/runner/cli.ts +7 -6
  334. package/src/runner/postTaskCleanup.ts +55 -7
  335. package/src/runner/processSecurity.ts +433 -0
  336. package/src/runner/runTask.ts +441 -292
  337. package/src/runner/simpleProcess.ts +68 -33
  338. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  339. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
  340. package/src/runner/taskStatusStore.ts +54 -0
  341. package/src/runner/watch.ts +290 -57
  342. package/src/security/contentRedaction.ts +31 -2
  343. package/src/security/discoveryTokenStore.ts +18 -4
  344. package/src/security/loopbackHost.ts +19 -0
  345. package/src/security/pathGuard.ts +35 -16
  346. package/src/security/planGuard.ts +2 -1
  347. package/src/security/runtimeGuard.ts +9 -6
  348. package/src/security/secretComparison.ts +13 -0
  349. package/src/security/sensitiveGuard.ts +27 -8
  350. package/src/security/toolInvocationGuard.ts +2 -2
  351. package/src/security/workspaceRootGuard.ts +53 -0
  352. package/src/smoke-test.ts +276 -273
  353. package/src/tools/catalog/index.ts +4 -0
  354. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  355. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +29 -16
  356. package/src/tools/definitions/toolDefs.ts +1264 -0
  357. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  358. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +196 -89
  359. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +11 -11
  360. package/src/tools/diagnostics/index.ts +6 -0
  361. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  362. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +29 -19
  363. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  364. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +9 -9
  365. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +11 -9
  366. package/src/tools/direct/index.ts +3 -0
  367. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  368. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  369. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  370. package/src/tools/discovery/index.ts +3 -0
  371. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  372. package/src/tools/dispatch/coreDispatch.ts +379 -0
  373. package/src/tools/dispatch/diagnosticDispatch.ts +106 -0
  374. package/src/tools/dispatch/directDispatch.ts +168 -0
  375. package/src/tools/dispatch/goalDispatch.ts +128 -0
  376. package/src/tools/dispatch/releaseDispatch.ts +66 -0
  377. package/src/tools/dispatch/types.ts +24 -0
  378. package/src/tools/dispatch/validation.ts +78 -0
  379. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  380. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +43 -28
  381. package/src/tools/goals/index.ts +3 -0
  382. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  383. package/src/tools/registry.ts +54 -1858
  384. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  385. package/src/tools/release/index.ts +2 -0
  386. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  387. package/src/tools/tasks/cancelTask.ts +109 -0
  388. package/src/tools/{createTask.ts → tasks/createTask.ts} +56 -33
  389. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
  390. package/src/tools/tasks/evidencePack.ts +504 -0
  391. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  392. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
  393. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  394. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
  395. package/src/tools/tasks/index.ts +18 -0
  396. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
  397. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
  398. package/src/tools/tasks/retryTask.ts +69 -0
  399. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +70 -35
  400. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  401. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  402. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  403. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  404. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  405. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  406. package/src/tools/workspace/index.ts +10 -0
  407. package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
  408. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  409. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  410. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  411. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  412. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  413. package/src/tools/workspace/syncFile.ts +292 -0
  414. package/src/utils/atomicFile.ts +58 -0
  415. package/src/utils/boundedFile.ts +116 -0
  416. package/src/utils/lockedJsonFile.ts +251 -0
  417. package/src/utils/platform.ts +31 -0
  418. package/src/utils/stableJson.ts +12 -0
  419. package/src/version.ts +3 -2
  420. package/src/watcherStatus.ts +19 -1
  421. package/ui/desktop-bootstrap.js +11 -0
  422. package/ui/desktop-bridge.js +36 -0
  423. package/ui/desktop.css +160 -0
  424. package/ui/getting-started.js +93 -0
  425. package/ui/i18n.js +484 -0
  426. package/ui/log-parser.js +30 -0
  427. package/ui/pages/audit.html +204 -3
  428. package/ui/pages/dashboard.html +937 -79
  429. package/ui/pages/direct-sessions.html +609 -60
  430. package/ui/pages/getting-started.html +43 -0
  431. package/ui/pages/logs.html +105 -22
  432. package/ui/pages/settings.html +101 -0
  433. package/ui/pages/task-detail.html +468 -438
  434. package/ui/pages/tasks.html +648 -65
  435. package/ui/pages/workspace.html +38 -1
  436. package/ui/settings.js +245 -0
  437. package/PatchWarden-Control.cmd +0 -6
  438. package/Restart-PatchWarden-Control.cmd +0 -6
  439. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  440. package/dist/test/unit/acceptance-engine.test.js +0 -228
  441. package/dist/test/unit/agent-router.test.d.ts +0 -1
  442. package/dist/test/unit/agent-router.test.js +0 -287
  443. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  444. package/dist/test/unit/android-doctor.test.js +0 -118
  445. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  446. package/dist/test/unit/audit-checks.test.js +0 -350
  447. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  448. package/dist/test/unit/chinese-path.test.js +0 -91
  449. package/dist/test/unit/command-guard.test.d.ts +0 -1
  450. package/dist/test/unit/command-guard.test.js +0 -160
  451. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  452. package/dist/test/unit/diagnose-task.test.js +0 -457
  453. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  454. package/dist/test/unit/direct-guards.test.js +0 -213
  455. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  456. package/dist/test/unit/discovery-token-store.test.js +0 -139
  457. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  458. package/dist/test/unit/evidence-pack.test.js +0 -130
  459. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  460. package/dist/test/unit/goal-graph.test.js +0 -298
  461. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  462. package/dist/test/unit/goal-progress.test.js +0 -381
  463. package/dist/test/unit/goal-status.test.d.ts +0 -1
  464. package/dist/test/unit/goal-status.test.js +0 -215
  465. package/dist/test/unit/goal-store.test.d.ts +0 -1
  466. package/dist/test/unit/goal-store.test.js +0 -253
  467. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  468. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  469. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  470. package/dist/test/unit/goal-tools-registry.test.js +0 -190
  471. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  472. package/dist/test/unit/handoff-export.test.js +0 -263
  473. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  474. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  475. package/dist/test/unit/logging.test.d.ts +0 -1
  476. package/dist/test/unit/logging.test.js +0 -397
  477. package/dist/test/unit/path-guard.test.d.ts +0 -1
  478. package/dist/test/unit/path-guard.test.js +0 -109
  479. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  480. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  481. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  482. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  483. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  484. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  485. package/dist/test/unit/release-gate.test.d.ts +0 -1
  486. package/dist/test/unit/release-gate.test.js +0 -242
  487. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  488. package/dist/test/unit/run-task-loop.test.js +0 -380
  489. package/dist/test/unit/safe-status.test.d.ts +0 -1
  490. package/dist/test/unit/safe-status.test.js +0 -165
  491. package/dist/test/unit/safe-views.test.d.ts +0 -1
  492. package/dist/test/unit/safe-views.test.js +0 -171
  493. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  494. package/dist/test/unit/schema-drift-check.test.js +0 -176
  495. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  496. package/dist/test/unit/sensitive-guard.test.js +0 -104
  497. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  498. package/dist/test/unit/subgoal-sync.test.js +0 -183
  499. package/dist/test/unit/sync-file.test.d.ts +0 -1
  500. package/dist/test/unit/sync-file.test.js +0 -154
  501. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  502. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  503. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  504. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  505. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  506. package/dist/test/unit/toolSearch.test.js +0 -571
  507. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  508. package/dist/test/unit/watcher-status.test.js +0 -169
  509. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  510. package/dist/test/unit/worktree-manager.test.js +0 -176
  511. package/dist/tools/applyPatch.js +0 -41
  512. package/dist/tools/cancelTask.d.ts +0 -30
  513. package/dist/tools/cancelTask.js +0 -64
  514. package/dist/tools/evidencePack.js +0 -168
  515. package/dist/tools/finalizeDirectSession.js +0 -84
  516. package/dist/tools/goalSubgoalTask.js +0 -110
  517. package/dist/tools/killTask.d.ts +0 -15
  518. package/dist/tools/retryTask.js +0 -45
  519. package/dist/tools/runVerification.js +0 -36
  520. package/dist/tools/syncFile.js +0 -65
  521. package/docs/control-center/control-center-daily-driver.md +0 -211
  522. package/docs/control-center/control-center-mvp.md +0 -205
  523. package/docs/control-center/control-center-phase2.md +0 -159
  524. package/docs/release-v0.6.0.md +0 -71
  525. package/docs/release-v0.6.1.md +0 -75
  526. package/docs/release-v0.6.4.md +0 -45
  527. package/src/test/unit/acceptance-engine.test.ts +0 -261
  528. package/src/test/unit/agent-router.test.ts +0 -342
  529. package/src/test/unit/android-doctor.test.ts +0 -158
  530. package/src/test/unit/audit-checks.test.ts +0 -567
  531. package/src/test/unit/chinese-path.test.ts +0 -106
  532. package/src/test/unit/command-guard.test.ts +0 -221
  533. package/src/test/unit/diagnose-task.test.ts +0 -544
  534. package/src/test/unit/direct-guards.test.ts +0 -297
  535. package/src/test/unit/discovery-token-store.test.ts +0 -181
  536. package/src/test/unit/evidence-pack.test.ts +0 -142
  537. package/src/test/unit/goal-graph.test.ts +0 -347
  538. package/src/test/unit/goal-progress.test.ts +0 -538
  539. package/src/test/unit/goal-status.test.ts +0 -270
  540. package/src/test/unit/goal-store.test.ts +0 -318
  541. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  542. package/src/test/unit/goal-tools-registry.test.ts +0 -243
  543. package/src/test/unit/handoff-export.test.ts +0 -295
  544. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  545. package/src/test/unit/logging.test.ts +0 -497
  546. package/src/test/unit/path-guard.test.ts +0 -150
  547. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  548. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  549. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  550. package/src/test/unit/release-gate.test.ts +0 -314
  551. package/src/test/unit/run-task-loop.test.ts +0 -425
  552. package/src/test/unit/safe-status.test.ts +0 -187
  553. package/src/test/unit/safe-views.test.ts +0 -184
  554. package/src/test/unit/schema-drift-check.test.ts +0 -260
  555. package/src/test/unit/sensitive-guard.test.ts +0 -124
  556. package/src/test/unit/subgoal-sync.test.ts +0 -236
  557. package/src/test/unit/sync-file.test.ts +0 -231
  558. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  559. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  560. package/src/test/unit/toolSearch.test.ts +0 -756
  561. package/src/test/unit/watcher-status.test.ts +0 -190
  562. package/src/test/unit/worktree-manager.test.ts +0 -247
  563. package/src/tools/cancelTask.ts +0 -70
  564. package/src/tools/evidencePack.ts +0 -205
  565. package/src/tools/retryTask.ts +0 -53
  566. package/src/tools/syncFile.ts +0 -122
  567. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  568. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  569. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  570. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  571. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  572. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  573. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  574. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  575. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  576. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  577. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  578. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  579. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  580. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  581. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  582. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  583. /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
  584. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  585. /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
  586. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  587. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  588. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  589. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  590. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  591. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  592. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  593. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  594. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  595. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  596. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
  597. /package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +0 -0
@@ -1,397 +0,0 @@
1
- import { describe, it, beforeEach, afterEach } from "node:test";
2
- import { strict as assert } from "node:assert";
3
- import { mkdtempSync, rmSync, readFileSync, existsSync, writeFileSync } from "node:fs";
4
- import { join } from "node:path";
5
- import { tmpdir } from "node:os";
6
- import { Logger, logUnhandledError, installGlobalHandlers, computeArgumentsDigest, logToolInvocation, } from "../../logging.js";
7
- // ── Test helpers ──────────────────────────────────────────────────
8
- /**
9
- * Capture everything written to stderr during `fn` and return it as an
10
- * array of raw chunks (one per `process.stderr.write` call).
11
- */
12
- function captureStderr(fn) {
13
- const chunks = [];
14
- const original = process.stderr.write;
15
- process.stderr.write = ((chunk) => {
16
- chunks.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf-8"));
17
- return true;
18
- });
19
- try {
20
- fn();
21
- }
22
- finally {
23
- process.stderr.write = original;
24
- }
25
- return chunks;
26
- }
27
- /**
28
- * Capture stderr and return the parsed JSON objects (one per log line).
29
- */
30
- function captureJson(fn) {
31
- const chunks = captureStderr(fn);
32
- return chunks
33
- .flatMap((chunk) => chunk.split("\n"))
34
- .map((line) => line.trim())
35
- .filter((line) => line.length > 0)
36
- .map((line) => JSON.parse(line));
37
- }
38
- // ── Logger: stderr output ─────────────────────────────────────────
39
- describe("Logger — stderr output", () => {
40
- it("writes JSON log entries to stderr (never stdout)", () => {
41
- const log = new Logger();
42
- const entries = captureJson(() => {
43
- log.info("hello from test", { component: "unit-test" });
44
- });
45
- assert.equal(entries.length, 1);
46
- const entry = entries[0];
47
- assert.equal(entry.level, "info");
48
- assert.equal(entry.message, "hello from test");
49
- assert.equal(entry.component, "unit-test");
50
- assert.equal(typeof entry.timestamp, "string");
51
- // timestamp must be a valid ISO date
52
- assert.ok(!Number.isNaN(Date.parse(entry.timestamp)));
53
- });
54
- it("supports warn and error levels", () => {
55
- const log = new Logger();
56
- const entries = captureJson(() => {
57
- log.warn("careful");
58
- log.error("broken");
59
- });
60
- assert.equal(entries.length, 2);
61
- assert.equal(entries[0].level, "warn");
62
- assert.equal(entries[0].message, "careful");
63
- assert.equal(entries[1].level, "error");
64
- assert.equal(entries[1].message, "broken");
65
- });
66
- it("does not write anything to stdout", () => {
67
- const originalStdout = process.stdout.write;
68
- let stdoutWritten = false;
69
- process.stdout.write = (() => {
70
- stdoutWritten = true;
71
- return true;
72
- });
73
- try {
74
- const log = new Logger();
75
- log.info("should not touch stdout");
76
- log.warn("nor this");
77
- log.error("nor this");
78
- }
79
- finally {
80
- process.stdout.write = originalStdout;
81
- }
82
- assert.equal(stdoutWritten, false, "Logger must never write to stdout");
83
- });
84
- });
85
- // ── Logger: audit ─────────────────────────────────────────────────
86
- describe("Logger.audit — format", () => {
87
- it("produces an audit log entry with all required fields", () => {
88
- const log = new Logger();
89
- const entries = captureJson(() => {
90
- log.audit("createTask", true, 150, undefined, "task-001");
91
- });
92
- assert.equal(entries.length, 1);
93
- const entry = entries[0];
94
- // Required core fields
95
- assert.equal(entry.level, "audit");
96
- assert.equal(typeof entry.timestamp, "string");
97
- assert.ok(!Number.isNaN(Date.parse(entry.timestamp)));
98
- assert.equal(typeof entry.message, "string");
99
- // Audit-specific fields
100
- assert.equal(entry.tool, "createTask");
101
- assert.equal(entry.ok, true);
102
- assert.equal(entry.duration_ms, 150);
103
- assert.equal(entry.task_id, "task-001");
104
- // error_reason should be absent when not provided
105
- assert.equal(entry.error_reason, undefined);
106
- });
107
- it("includes error_reason when the tool call failed", () => {
108
- const log = new Logger();
109
- const entries = captureJson(() => {
110
- log.audit("applyPatch", false, 3000, "command_blocked", "task-002");
111
- });
112
- assert.equal(entries.length, 1);
113
- const entry = entries[0];
114
- assert.equal(entry.ok, false);
115
- assert.equal(entry.error_reason, "command_blocked");
116
- assert.equal(entry.task_id, "task-002");
117
- });
118
- it("omits task_id when not provided", () => {
119
- const log = new Logger();
120
- const entries = captureJson(() => {
121
- log.audit("healthCheck", true, 5);
122
- });
123
- assert.equal(entries.length, 1);
124
- const entry = entries[0];
125
- assert.equal(entry.task_id, undefined);
126
- assert.equal(entry.error_reason, undefined);
127
- });
128
- });
129
- // ── Logger: verbose mode ──────────────────────────────────────────
130
- describe("Logger — verbose mode (PATCHWARDEN_VERBOSE_LOG)", () => {
131
- let savedEnv;
132
- beforeEach(() => {
133
- savedEnv = process.env.PATCHWARDEN_VERBOSE_LOG;
134
- });
135
- afterEach(() => {
136
- if (savedEnv === undefined) {
137
- delete process.env.PATCHWARDEN_VERBOSE_LOG;
138
- }
139
- else {
140
- process.env.PATCHWARDEN_VERBOSE_LOG = savedEnv;
141
- }
142
- });
143
- it("does not log args by default (verbose mode off)", () => {
144
- delete process.env.PATCHWARDEN_VERBOSE_LOG;
145
- const log = new Logger();
146
- const fakeToken = "sk_" + "FAKE_TOKEN_VALUE_123456";
147
- const sensitiveArgs = {
148
- prompt: "fix the bug",
149
- token: fakeToken,
150
- };
151
- const entries = captureJson(() => {
152
- log.audit("createTask", true, 100, undefined, "task-003", sensitiveArgs);
153
- });
154
- assert.equal(entries.length, 1);
155
- const entry = entries[0];
156
- // args must NOT be present when verbose mode is off
157
- assert.equal(entry.args, undefined);
158
- // The raw token must not appear anywhere in the serialized entry
159
- const raw = JSON.stringify(entry);
160
- assert.ok(!raw.includes(fakeToken), "raw token must not leak into log output");
161
- });
162
- it("logs sanitized args when PATCHWARDEN_VERBOSE_LOG=true", () => {
163
- process.env.PATCHWARDEN_VERBOSE_LOG = "true";
164
- const log = new Logger();
165
- const fakeToken = "sk_" + "FAKE_TOKEN_VALUE_123456";
166
- const sensitiveArgs = {
167
- prompt: "fix the bug",
168
- token: fakeToken,
169
- };
170
- const entries = captureJson(() => {
171
- log.audit("createTask", true, 100, undefined, "task-004", sensitiveArgs);
172
- });
173
- assert.equal(entries.length, 1);
174
- const entry = entries[0];
175
- // args must be present and redacted
176
- assert.ok(entry.args !== undefined, "args should be logged in verbose mode");
177
- const argsStr = String(entry.args);
178
- assert.ok(argsStr.includes("[REDACTED TOKEN]"), "sensitive token must be redacted");
179
- assert.ok(!argsStr.includes(fakeToken), "raw token must not appear in args");
180
- // non-sensitive content is preserved
181
- assert.ok(argsStr.includes("fix the bug"), "non-sensitive args content is preserved");
182
- });
183
- it("does not log args when PATCHWARDEN_VERBOSE_LOG is set but not 'true'", () => {
184
- process.env.PATCHWARDEN_VERBOSE_LOG = "false";
185
- const log = new Logger();
186
- const entries = captureJson(() => {
187
- log.audit("createTask", true, 100, undefined, "task-005", { prompt: "hello" });
188
- });
189
- assert.equal(entries.length, 1);
190
- assert.equal(entries[0].args, undefined);
191
- });
192
- });
193
- // ── logUnhandledError ─────────────────────────────────────────────
194
- describe("logUnhandledError", () => {
195
- it("produces structured JSON output for Error instances", () => {
196
- const error = new Error("something went wrong");
197
- error.name = "CustomError";
198
- const entries = captureJson(() => {
199
- logUnhandledError(error);
200
- });
201
- assert.equal(entries.length, 1);
202
- const entry = entries[0];
203
- assert.equal(entry.level, "error");
204
- assert.equal(entry.message, "unhandled_error");
205
- assert.equal(entry.error, "something went wrong");
206
- assert.equal(entry.error_name, "CustomError");
207
- assert.equal(typeof entry.timestamp, "string");
208
- assert.ok(!Number.isNaN(Date.parse(entry.timestamp)));
209
- assert.equal(typeof entry.stack, "string");
210
- assert.ok(entry.stack.includes("something went wrong"));
211
- });
212
- it("handles non-Error values (string)", () => {
213
- const entries = captureJson(() => {
214
- logUnhandledError("a plain string rejection");
215
- });
216
- assert.equal(entries.length, 1);
217
- const entry = entries[0];
218
- assert.equal(entry.level, "error");
219
- assert.equal(entry.error, "a plain string rejection");
220
- assert.equal(entry.error_name, "string");
221
- assert.equal(entry.stack, undefined);
222
- });
223
- it("handles non-Error values (object)", () => {
224
- const entries = captureJson(() => {
225
- logUnhandledError({ code: 42, detail: "weird" });
226
- });
227
- assert.equal(entries.length, 1);
228
- const entry = entries[0];
229
- assert.equal(entry.level, "error");
230
- assert.ok(String(entry.error).includes("42"));
231
- assert.equal(entry.error_name, "object");
232
- });
233
- });
234
- // ── installGlobalHandlers ─────────────────────────────────────────
235
- describe("installGlobalHandlers", () => {
236
- it("does not throw when called", () => {
237
- // Save existing listeners so we can restore them afterwards and avoid
238
- // the fatal `uncaughtException` handler interfering with the test
239
- // runner.
240
- const origUnhandled = process.listeners("unhandledRejection");
241
- const origUncaught = process.listeners("uncaughtException");
242
- process.removeAllListeners("unhandledRejection");
243
- process.removeAllListeners("uncaughtException");
244
- try {
245
- assert.doesNotThrow(() => installGlobalHandlers());
246
- // Verify the handlers were actually registered
247
- assert.ok(process.listenerCount("unhandledRejection") >= 1);
248
- assert.ok(process.listenerCount("uncaughtException") >= 1);
249
- }
250
- finally {
251
- // Restore original listeners
252
- process.removeAllListeners("unhandledRejection");
253
- process.removeAllListeners("uncaughtException");
254
- for (const fn of origUnhandled)
255
- process.on("unhandledRejection", fn);
256
- for (const fn of origUncaught)
257
- process.on("uncaughtException", fn);
258
- }
259
- });
260
- it("can be called multiple times without throwing", () => {
261
- const origUnhandled = process.listeners("unhandledRejection");
262
- const origUncaught = process.listeners("uncaughtException");
263
- process.removeAllListeners("unhandledRejection");
264
- process.removeAllListeners("uncaughtException");
265
- try {
266
- assert.doesNotThrow(() => {
267
- installGlobalHandlers();
268
- installGlobalHandlers();
269
- });
270
- }
271
- finally {
272
- process.removeAllListeners("unhandledRejection");
273
- process.removeAllListeners("uncaughtException");
274
- for (const fn of origUnhandled)
275
- process.on("unhandledRejection", fn);
276
- for (const fn of origUncaught)
277
- process.on("uncaughtException", fn);
278
- }
279
- });
280
- });
281
- // ── computeArgumentsDigest ───────────────────────────────────────
282
- describe("computeArgumentsDigest", () => {
283
- it("returns the same digest for identical arguments (stability)", () => {
284
- const args = { b: 2, a: 1, nested: { y: "yes", x: [1, 2] } };
285
- const d1 = computeArgumentsDigest(args);
286
- const d2 = computeArgumentsDigest(args);
287
- assert.equal(d1, d2);
288
- });
289
- it("returns the same digest regardless of key order (stable serialization)", () => {
290
- const d1 = computeArgumentsDigest({ a: 1, b: 2 });
291
- const d2 = computeArgumentsDigest({ b: 2, a: 1 });
292
- assert.equal(d1, d2);
293
- });
294
- it("returns different digests for different arguments", () => {
295
- const d1 = computeArgumentsDigest({ a: 1 });
296
- const d2 = computeArgumentsDigest({ a: 2 });
297
- assert.notEqual(d1, d2);
298
- });
299
- it("returns a 'sha256:' prefixed digest", () => {
300
- const digest = computeArgumentsDigest({ foo: "bar" });
301
- assert.ok(digest.startsWith("sha256:"), `digest should start with "sha256:", got: ${digest}`);
302
- // full format: sha256: + 64 lowercase hex chars
303
- assert.match(digest, /^sha256:[a-f0-9]{64}$/);
304
- });
305
- it("does not leak raw argument values into the digest string", () => {
306
- const fakeToken = "tok-value";
307
- const sensitiveArgs = { token: fakeToken, prompt: "hello-world" };
308
- const digest = computeArgumentsDigest(sensitiveArgs);
309
- assert.ok(!digest.includes(fakeToken), "digest must not leak raw token value");
310
- assert.ok(!digest.includes("hello-world"), "digest must not leak raw prompt value");
311
- });
312
- });
313
- // ── logToolInvocation ────────────────────────────────────────────
314
- describe("logToolInvocation", () => {
315
- let tempDir;
316
- beforeEach(() => {
317
- tempDir = mkdtempSync(join(tmpdir(), "pw-invlog-"));
318
- });
319
- afterEach(() => {
320
- rmSync(tempDir, { recursive: true, force: true });
321
- });
322
- function sampleEntry(overrides = {}) {
323
- return {
324
- timestamp: new Date().toISOString(),
325
- toolName: "read_workspace_file",
326
- discoveryToken: "tok",
327
- risk: "workspace_read_sensitive",
328
- profile: "full",
329
- arguments_digest: "sha256:" + "0".repeat(64),
330
- result: "ok",
331
- duration_ms: 42,
332
- ...overrides,
333
- };
334
- }
335
- it("writes an entry to invocation.log inside the provided logsDir", () => {
336
- const logsDir = join(tempDir, ".patchwarden", "logs");
337
- const entry = sampleEntry();
338
- logToolInvocation(entry, { logsDir });
339
- const logPath = join(logsDir, "invocation.log");
340
- assert.ok(existsSync(logPath), "invocation.log should be created");
341
- const content = readFileSync(logPath, "utf-8");
342
- const lines = content.trim().split("\n");
343
- assert.equal(lines.length, 1, "exactly one JSON line should be written");
344
- const parsed = JSON.parse(lines[0]);
345
- assert.equal(parsed.toolName, "read_workspace_file");
346
- assert.equal(parsed.discoveryToken, "tok");
347
- assert.equal(parsed.risk, "workspace_read_sensitive");
348
- assert.equal(parsed.profile, "full");
349
- assert.equal(parsed.result, "ok");
350
- assert.equal(parsed.duration_ms, 42);
351
- assert.match(parsed.arguments_digest, /^sha256:[a-f0-9]{64}$/);
352
- assert.ok(!Number.isNaN(Date.parse(parsed.timestamp)));
353
- });
354
- it("appends multiple entries as JSON Lines", () => {
355
- const logsDir = join(tempDir, "logs");
356
- logToolInvocation(sampleEntry({ duration_ms: 1 }), { logsDir });
357
- logToolInvocation(sampleEntry({ duration_ms: 2, result: "error", error_code: "blocked" }), { logsDir });
358
- const content = readFileSync(join(logsDir, "invocation.log"), "utf-8");
359
- const lines = content.trim().split("\n");
360
- assert.equal(lines.length, 2);
361
- const first = JSON.parse(lines[0]);
362
- const second = JSON.parse(lines[1]);
363
- assert.equal(first.duration_ms, 1);
364
- assert.equal(first.result, "ok");
365
- assert.equal(second.duration_ms, 2);
366
- assert.equal(second.result, "error");
367
- assert.equal(second.error_code, "blocked");
368
- });
369
- it("includes allowedScope and error_code when provided", () => {
370
- const logsDir = join(tempDir, "logs");
371
- logToolInvocation(sampleEntry({
372
- allowedScope: ["src/", "docs/"],
373
- result: "error",
374
- error_code: "scope_violation",
375
- }), { logsDir });
376
- const parsed = JSON.parse(readFileSync(join(logsDir, "invocation.log"), "utf-8").trim());
377
- assert.deepEqual(parsed.allowedScope, ["src/", "docs/"]);
378
- assert.equal(parsed.error_code, "scope_violation");
379
- });
380
- it("omits error_code and allowedScope when not provided", () => {
381
- const logsDir = join(tempDir, "logs");
382
- logToolInvocation(sampleEntry(), { logsDir });
383
- const parsed = JSON.parse(readFileSync(join(logsDir, "invocation.log"), "utf-8").trim());
384
- assert.equal(parsed.error_code, undefined);
385
- assert.equal(parsed.allowedScope, undefined);
386
- });
387
- it("does not throw when the log directory cannot be created", () => {
388
- // Create a file, then attempt to use a path beneath it as logsDir.
389
- // mkdirSync will fail with ENOTDIR because the parent is a file.
390
- const blockingFile = join(tempDir, "blocker");
391
- writeFileSync(blockingFile, "x", "utf-8");
392
- const impossibleLogsDir = join(blockingFile, "subdir");
393
- assert.doesNotThrow(() => {
394
- logToolInvocation(sampleEntry(), { logsDir: impossibleLogsDir });
395
- });
396
- });
397
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,109 +0,0 @@
1
- import { describe, it, beforeEach, afterEach } from "node:test";
2
- import { strict as assert } from "node:assert";
3
- import { mkdtempSync, rmSync, mkdirSync, writeFileSync, symlinkSync } from "node:fs";
4
- import { join, resolve } from "node:path";
5
- import { tmpdir } from "node:os";
6
- import { guardPath, guardWorkspacePath } from "../../security/pathGuard.js";
7
- import { PatchWardenError } from "../../errors.js";
8
- describe("guardPath", () => {
9
- let tempDir;
10
- beforeEach(() => {
11
- tempDir = mkdtempSync(join(tmpdir(), "pw-pathguard-"));
12
- });
13
- afterEach(() => {
14
- rmSync(tempDir, { recursive: true, force: true });
15
- });
16
- it("allows paths inside workspace", () => {
17
- const result = guardPath("src/main.ts", tempDir);
18
- assert.ok(result.startsWith(tempDir));
19
- assert.ok(result.includes("src"));
20
- assert.ok(result.includes("main.ts"));
21
- });
22
- it("allows root workspace path", () => {
23
- const result = guardPath(".", tempDir);
24
- assert.equal(result, resolve(tempDir));
25
- });
26
- it("allows empty path as workspace root", () => {
27
- const result = guardPath("", tempDir);
28
- assert.equal(result, resolve(tempDir));
29
- });
30
- it("rejects path traversal with ..", () => {
31
- assert.throws(() => guardPath("../../../etc/passwd", tempDir), PatchWardenError);
32
- });
33
- it("rejects path traversal nested with ..", () => {
34
- assert.throws(() => guardPath("src/../../../etc/passwd", tempDir), PatchWardenError);
35
- });
36
- it("rejects absolute path outside workspace", () => {
37
- const outside = process.platform === "win32" ? "C:\\Windows\\System32" : "/etc";
38
- assert.throws(() => guardPath(outside, tempDir), PatchWardenError);
39
- });
40
- it("allows absolute path inside workspace", () => {
41
- const insidePath = join(tempDir, "src", "main.ts");
42
- mkdirSync(join(tempDir, "src"), { recursive: true });
43
- writeFileSync(insidePath, "test", "utf-8");
44
- const result = guardPath(insidePath, tempDir);
45
- assert.equal(result, resolve(insidePath));
46
- });
47
- it("enforces allowedPrefix", () => {
48
- mkdirSync(join(tempDir, "allowed"), { recursive: true });
49
- mkdirSync(join(tempDir, "forbidden"), { recursive: true });
50
- // Path inside allowed prefix works
51
- const okResult = guardPath("allowed/file.ts", tempDir, "allowed");
52
- assert.ok(okResult.includes("allowed"));
53
- // Path outside allowed prefix fails
54
- assert.throws(() => guardPath("forbidden/file.ts", tempDir, "allowed"), PatchWardenError);
55
- });
56
- it("handles Windows mixed separators", () => {
57
- const mixedPath = "src\\subdir/file.ts";
58
- const result = guardPath(mixedPath, tempDir);
59
- assert.ok(result.startsWith(tempDir));
60
- });
61
- it("rejects symlink escape", { skip: process.platform === "win32" ? "Windows symlink permissions unstable" : undefined }, () => {
62
- const target = mkdtempSync(join(tmpdir(), "pw-symlink-target-"));
63
- try {
64
- const linkPath = join(tempDir, "escape-link");
65
- symlinkSync(target, linkPath);
66
- assert.throws(() => guardPath("escape-link/secret.txt", tempDir), PatchWardenError);
67
- }
68
- finally {
69
- rmSync(target, { recursive: true, force: true });
70
- }
71
- });
72
- });
73
- describe("guardWorkspacePath", () => {
74
- let tempDir;
75
- beforeEach(() => {
76
- tempDir = mkdtempSync(join(tmpdir(), "pw-wspath-"));
77
- });
78
- afterEach(() => {
79
- rmSync(tempDir, { recursive: true, force: true });
80
- });
81
- it("allows relative path inside workspace", () => {
82
- const result = guardWorkspacePath("my-repo", tempDir);
83
- assert.equal(result, resolve(tempDir, "my-repo"));
84
- });
85
- it("allows absolute path inside workspace", () => {
86
- const absPath = join(tempDir, "my-repo");
87
- const result = guardWorkspacePath(absPath, tempDir);
88
- assert.equal(result, resolve(absPath));
89
- });
90
- it("rejects path outside workspace", () => {
91
- assert.throws(() => guardWorkspacePath("../../../etc", tempDir), PatchWardenError);
92
- });
93
- it("rejects different drive letter on Windows", { skip: process.platform !== "win32" ? "Windows-only test" : undefined }, () => {
94
- // If workspace is on C:, a D: path should be rejected
95
- const wsDrive = tempDir.match(/^([A-Za-z]):/);
96
- if (!wsDrive)
97
- return;
98
- const otherDrive = wsDrive[1].toLowerCase() === "c" ? "D" : "C";
99
- assert.throws(() => guardWorkspacePath(`${otherDrive}:\\some\\repo`, tempDir), (err) => err instanceof PatchWardenError && err.reason === "workspace_path_escape");
100
- });
101
- it("defaults to workspace root for empty input", () => {
102
- const result = guardWorkspacePath("", tempDir);
103
- assert.equal(result, resolve(tempDir));
104
- });
105
- it("defaults to workspace root for dot input", () => {
106
- const result = guardWorkspacePath(".", tempDir);
107
- assert.equal(result, resolve(tempDir));
108
- });
109
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,48 +0,0 @@
1
- import { describe, it } from "node:test";
2
- import { strict as assert } from "node:assert";
3
- import { execFileSync } from "node:child_process";
4
- import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
5
- import { join } from "node:path";
6
- import { tmpdir } from "node:os";
7
- import { runPostTaskCleanup } from "../../runner/postTaskCleanup.js";
8
- describe("postTaskCleanup", () => {
9
- it("removes untracked low-risk artifacts and skips tracked or excluded paths", () => {
10
- const root = mkdtempSync(join(tmpdir(), "pw-cleanup-"));
11
- try {
12
- execFileSync("git", ["init"], { cwd: root, stdio: "ignore" });
13
- execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: root, stdio: "ignore" });
14
- execFileSync("git", ["config", "user.name", "PatchWarden Test"], { cwd: root, stdio: "ignore" });
15
- mkdirSync(join(root, "tracked", "__pycache__"), { recursive: true });
16
- writeFileSync(join(root, "tracked", "__pycache__", "keep.pyc"), "tracked", "utf-8");
17
- execFileSync("git", ["add", "."], { cwd: root, stdio: "ignore" });
18
- execFileSync("git", ["commit", "-m", "init"], { cwd: root, stdio: "ignore" });
19
- mkdirSync(join(root, "backend", "__pycache__"), { recursive: true });
20
- mkdirSync(join(root, ".venv", "__pycache__"), { recursive: true });
21
- mkdirSync(join(root, ".patchwarden", "tasks", "old-task", "__pycache__"), { recursive: true });
22
- mkdirSync(join(root, "node_modules", "pkg", "__pycache__"), { recursive: true });
23
- mkdirSync(join(root, "docs", "__pycache__"), { recursive: true });
24
- writeFileSync(join(root, "backend", "__pycache__", "drop.pyc"), "drop", "utf-8");
25
- writeFileSync(join(root, ".venv", "__pycache__", "skip.pyc"), "skip", "utf-8");
26
- writeFileSync(join(root, ".patchwarden", "tasks", "old-task", "__pycache__", "skip.pyc"), "skip", "utf-8");
27
- writeFileSync(join(root, "node_modules", "pkg", "__pycache__", "skip.pyc"), "skip", "utf-8");
28
- writeFileSync(join(root, "docs", "__pycache__", "skip.pyc"), "skip", "utf-8");
29
- const taskDir = join(root, ".patchwarden", "tasks", "task-1");
30
- mkdirSync(taskDir, { recursive: true });
31
- const report = runPostTaskCleanup(root, taskDir);
32
- assert.ok(report.removed.some((entry) => entry.path === "backend/__pycache__"));
33
- assert.ok(report.skipped.some((entry) => entry.path === "tracked/__pycache__" && entry.skip_reason === "tracked_by_git"));
34
- assert.equal(report.source_files_touched, 0);
35
- assert.ok(!existsSync(join(root, "backend", "__pycache__")));
36
- assert.ok(existsSync(join(root, "tracked", "__pycache__", "keep.pyc")));
37
- assert.ok(existsSync(join(root, ".venv", "__pycache__", "skip.pyc")));
38
- assert.ok(existsSync(join(root, ".patchwarden", "tasks", "old-task", "__pycache__", "skip.pyc")));
39
- assert.ok(existsSync(join(root, "node_modules", "pkg", "__pycache__", "skip.pyc")));
40
- assert.ok(existsSync(join(root, "docs", "__pycache__", "skip.pyc")));
41
- const written = JSON.parse(readFileSync(join(taskDir, "post-task-cleanup.json"), "utf-8"));
42
- assert.equal(written.enabled, true);
43
- }
44
- finally {
45
- rmSync(root, { recursive: true, force: true });
46
- }
47
- });
48
- });
@@ -1 +0,0 @@
1
- export {};