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
@@ -1,497 +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 {
7
- Logger,
8
- logUnhandledError,
9
- installGlobalHandlers,
10
- computeArgumentsDigest,
11
- logToolInvocation,
12
- type InvocationLogEntry,
13
- } from "../../logging.js";
14
-
15
- // ── Test helpers ──────────────────────────────────────────────────
16
-
17
- /**
18
- * Capture everything written to stderr during `fn` and return it as an
19
- * array of raw chunks (one per `process.stderr.write` call).
20
- */
21
- function captureStderr(fn: () => void): string[] {
22
- const chunks: string[] = [];
23
- const original = process.stderr.write;
24
- process.stderr.write = ((chunk: string | Uint8Array) => {
25
- chunks.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf-8"));
26
- return true;
27
- }) as typeof process.stderr.write;
28
- try {
29
- fn();
30
- } finally {
31
- process.stderr.write = original;
32
- }
33
- return chunks;
34
- }
35
-
36
- /**
37
- * Capture stderr and return the parsed JSON objects (one per log line).
38
- */
39
- function captureJson(fn: () => void): Record<string, unknown>[] {
40
- const chunks = captureStderr(fn);
41
- return chunks
42
- .flatMap((chunk) => chunk.split("\n"))
43
- .map((line) => line.trim())
44
- .filter((line) => line.length > 0)
45
- .map((line) => JSON.parse(line));
46
- }
47
-
48
- // ── Logger: stderr output ─────────────────────────────────────────
49
-
50
- describe("Logger — stderr output", () => {
51
- it("writes JSON log entries to stderr (never stdout)", () => {
52
- const log = new Logger();
53
- const entries = captureJson(() => {
54
- log.info("hello from test", { component: "unit-test" });
55
- });
56
-
57
- assert.equal(entries.length, 1);
58
- const entry = entries[0];
59
-
60
- assert.equal(entry.level, "info");
61
- assert.equal(entry.message, "hello from test");
62
- assert.equal(entry.component, "unit-test");
63
- assert.equal(typeof entry.timestamp, "string");
64
- // timestamp must be a valid ISO date
65
- assert.ok(!Number.isNaN(Date.parse(entry.timestamp as string)));
66
- });
67
-
68
- it("supports warn and error levels", () => {
69
- const log = new Logger();
70
- const entries = captureJson(() => {
71
- log.warn("careful");
72
- log.error("broken");
73
- });
74
-
75
- assert.equal(entries.length, 2);
76
- assert.equal(entries[0].level, "warn");
77
- assert.equal(entries[0].message, "careful");
78
- assert.equal(entries[1].level, "error");
79
- assert.equal(entries[1].message, "broken");
80
- });
81
-
82
- it("does not write anything to stdout", () => {
83
- const originalStdout = process.stdout.write;
84
- let stdoutWritten = false;
85
- process.stdout.write = (() => {
86
- stdoutWritten = true;
87
- return true;
88
- }) as typeof process.stdout.write;
89
-
90
- try {
91
- const log = new Logger();
92
- log.info("should not touch stdout");
93
- log.warn("nor this");
94
- log.error("nor this");
95
- } finally {
96
- process.stdout.write = originalStdout;
97
- }
98
-
99
- assert.equal(stdoutWritten, false, "Logger must never write to stdout");
100
- });
101
- });
102
-
103
- // ── Logger: audit ─────────────────────────────────────────────────
104
-
105
- describe("Logger.audit — format", () => {
106
- it("produces an audit log entry with all required fields", () => {
107
- const log = new Logger();
108
- const entries = captureJson(() => {
109
- log.audit("createTask", true, 150, undefined, "task-001");
110
- });
111
-
112
- assert.equal(entries.length, 1);
113
- const entry = entries[0];
114
-
115
- // Required core fields
116
- assert.equal(entry.level, "audit");
117
- assert.equal(typeof entry.timestamp, "string");
118
- assert.ok(!Number.isNaN(Date.parse(entry.timestamp as string)));
119
- assert.equal(typeof entry.message, "string");
120
-
121
- // Audit-specific fields
122
- assert.equal(entry.tool, "createTask");
123
- assert.equal(entry.ok, true);
124
- assert.equal(entry.duration_ms, 150);
125
- assert.equal(entry.task_id, "task-001");
126
-
127
- // error_reason should be absent when not provided
128
- assert.equal(entry.error_reason, undefined);
129
- });
130
-
131
- it("includes error_reason when the tool call failed", () => {
132
- const log = new Logger();
133
- const entries = captureJson(() => {
134
- log.audit("applyPatch", false, 3000, "command_blocked", "task-002");
135
- });
136
-
137
- assert.equal(entries.length, 1);
138
- const entry = entries[0];
139
-
140
- assert.equal(entry.ok, false);
141
- assert.equal(entry.error_reason, "command_blocked");
142
- assert.equal(entry.task_id, "task-002");
143
- });
144
-
145
- it("omits task_id when not provided", () => {
146
- const log = new Logger();
147
- const entries = captureJson(() => {
148
- log.audit("healthCheck", true, 5);
149
- });
150
-
151
- assert.equal(entries.length, 1);
152
- const entry = entries[0];
153
- assert.equal(entry.task_id, undefined);
154
- assert.equal(entry.error_reason, undefined);
155
- });
156
- });
157
-
158
- // ── Logger: verbose mode ──────────────────────────────────────────
159
-
160
- describe("Logger — verbose mode (PATCHWARDEN_VERBOSE_LOG)", () => {
161
- let savedEnv: string | undefined;
162
-
163
- beforeEach(() => {
164
- savedEnv = process.env.PATCHWARDEN_VERBOSE_LOG;
165
- });
166
-
167
- afterEach(() => {
168
- if (savedEnv === undefined) {
169
- delete process.env.PATCHWARDEN_VERBOSE_LOG;
170
- } else {
171
- process.env.PATCHWARDEN_VERBOSE_LOG = savedEnv;
172
- }
173
- });
174
-
175
- it("does not log args by default (verbose mode off)", () => {
176
- delete process.env.PATCHWARDEN_VERBOSE_LOG;
177
-
178
- const log = new Logger();
179
- const fakeToken = "sk_" + "FAKE_TOKEN_VALUE_123456";
180
- const sensitiveArgs = {
181
- prompt: "fix the bug",
182
- token: fakeToken,
183
- };
184
-
185
- const entries = captureJson(() => {
186
- log.audit("createTask", true, 100, undefined, "task-003", sensitiveArgs);
187
- });
188
-
189
- assert.equal(entries.length, 1);
190
- const entry = entries[0];
191
-
192
- // args must NOT be present when verbose mode is off
193
- assert.equal(entry.args, undefined);
194
- // The raw token must not appear anywhere in the serialized entry
195
- const raw = JSON.stringify(entry);
196
- assert.ok(!raw.includes(fakeToken), "raw token must not leak into log output");
197
- });
198
-
199
- it("logs sanitized args when PATCHWARDEN_VERBOSE_LOG=true", () => {
200
- process.env.PATCHWARDEN_VERBOSE_LOG = "true";
201
-
202
- const log = new Logger();
203
- const fakeToken = "sk_" + "FAKE_TOKEN_VALUE_123456";
204
- const sensitiveArgs = {
205
- prompt: "fix the bug",
206
- token: fakeToken,
207
- };
208
-
209
- const entries = captureJson(() => {
210
- log.audit("createTask", true, 100, undefined, "task-004", sensitiveArgs);
211
- });
212
-
213
- assert.equal(entries.length, 1);
214
- const entry = entries[0];
215
-
216
- // args must be present and redacted
217
- assert.ok(entry.args !== undefined, "args should be logged in verbose mode");
218
- const argsStr = String(entry.args);
219
- assert.ok(argsStr.includes("[REDACTED TOKEN]"), "sensitive token must be redacted");
220
- assert.ok(!argsStr.includes(fakeToken), "raw token must not appear in args");
221
- // non-sensitive content is preserved
222
- assert.ok(argsStr.includes("fix the bug"), "non-sensitive args content is preserved");
223
- });
224
-
225
- it("does not log args when PATCHWARDEN_VERBOSE_LOG is set but not 'true'", () => {
226
- process.env.PATCHWARDEN_VERBOSE_LOG = "false";
227
-
228
- const log = new Logger();
229
- const entries = captureJson(() => {
230
- log.audit("createTask", true, 100, undefined, "task-005", { prompt: "hello" });
231
- });
232
-
233
- assert.equal(entries.length, 1);
234
- assert.equal(entries[0].args, undefined);
235
- });
236
- });
237
-
238
- // ── logUnhandledError ─────────────────────────────────────────────
239
-
240
- describe("logUnhandledError", () => {
241
- it("produces structured JSON output for Error instances", () => {
242
- const error = new Error("something went wrong");
243
- error.name = "CustomError";
244
-
245
- const entries = captureJson(() => {
246
- logUnhandledError(error);
247
- });
248
-
249
- assert.equal(entries.length, 1);
250
- const entry = entries[0];
251
-
252
- assert.equal(entry.level, "error");
253
- assert.equal(entry.message, "unhandled_error");
254
- assert.equal(entry.error, "something went wrong");
255
- assert.equal(entry.error_name, "CustomError");
256
- assert.equal(typeof entry.timestamp, "string");
257
- assert.ok(!Number.isNaN(Date.parse(entry.timestamp as string)));
258
- assert.equal(typeof entry.stack, "string");
259
- assert.ok((entry.stack as string).includes("something went wrong"));
260
- });
261
-
262
- it("handles non-Error values (string)", () => {
263
- const entries = captureJson(() => {
264
- logUnhandledError("a plain string rejection");
265
- });
266
-
267
- assert.equal(entries.length, 1);
268
- const entry = entries[0];
269
-
270
- assert.equal(entry.level, "error");
271
- assert.equal(entry.error, "a plain string rejection");
272
- assert.equal(entry.error_name, "string");
273
- assert.equal(entry.stack, undefined);
274
- });
275
-
276
- it("handles non-Error values (object)", () => {
277
- const entries = captureJson(() => {
278
- logUnhandledError({ code: 42, detail: "weird" });
279
- });
280
-
281
- assert.equal(entries.length, 1);
282
- const entry = entries[0];
283
-
284
- assert.equal(entry.level, "error");
285
- assert.ok(String(entry.error).includes("42"));
286
- assert.equal(entry.error_name, "object");
287
- });
288
- });
289
-
290
- // ── installGlobalHandlers ─────────────────────────────────────────
291
-
292
- describe("installGlobalHandlers", () => {
293
- it("does not throw when called", () => {
294
- // Save existing listeners so we can restore them afterwards and avoid
295
- // the fatal `uncaughtException` handler interfering with the test
296
- // runner.
297
- const origUnhandled = process.listeners("unhandledRejection");
298
- const origUncaught = process.listeners("uncaughtException");
299
-
300
- process.removeAllListeners("unhandledRejection");
301
- process.removeAllListeners("uncaughtException");
302
-
303
- try {
304
- assert.doesNotThrow(() => installGlobalHandlers());
305
-
306
- // Verify the handlers were actually registered
307
- assert.ok(process.listenerCount("unhandledRejection") >= 1);
308
- assert.ok(process.listenerCount("uncaughtException") >= 1);
309
- } finally {
310
- // Restore original listeners
311
- process.removeAllListeners("unhandledRejection");
312
- process.removeAllListeners("uncaughtException");
313
- for (const fn of origUnhandled) process.on("unhandledRejection", fn);
314
- for (const fn of origUncaught) process.on("uncaughtException", fn);
315
- }
316
- });
317
-
318
- it("can be called multiple times without throwing", () => {
319
- const origUnhandled = process.listeners("unhandledRejection");
320
- const origUncaught = process.listeners("uncaughtException");
321
-
322
- process.removeAllListeners("unhandledRejection");
323
- process.removeAllListeners("uncaughtException");
324
-
325
- try {
326
- assert.doesNotThrow(() => {
327
- installGlobalHandlers();
328
- installGlobalHandlers();
329
- });
330
- } finally {
331
- process.removeAllListeners("unhandledRejection");
332
- process.removeAllListeners("uncaughtException");
333
- for (const fn of origUnhandled) process.on("unhandledRejection", fn);
334
- for (const fn of origUncaught) process.on("uncaughtException", fn);
335
- }
336
- });
337
- });
338
-
339
- // ── computeArgumentsDigest ───────────────────────────────────────
340
-
341
- describe("computeArgumentsDigest", () => {
342
- it("returns the same digest for identical arguments (stability)", () => {
343
- const args = { b: 2, a: 1, nested: { y: "yes", x: [1, 2] } };
344
- const d1 = computeArgumentsDigest(args);
345
- const d2 = computeArgumentsDigest(args);
346
- assert.equal(d1, d2);
347
- });
348
-
349
- it("returns the same digest regardless of key order (stable serialization)", () => {
350
- const d1 = computeArgumentsDigest({ a: 1, b: 2 });
351
- const d2 = computeArgumentsDigest({ b: 2, a: 1 });
352
- assert.equal(d1, d2);
353
- });
354
-
355
- it("returns different digests for different arguments", () => {
356
- const d1 = computeArgumentsDigest({ a: 1 });
357
- const d2 = computeArgumentsDigest({ a: 2 });
358
- assert.notEqual(d1, d2);
359
- });
360
-
361
- it("returns a 'sha256:' prefixed digest", () => {
362
- const digest = computeArgumentsDigest({ foo: "bar" });
363
- assert.ok(
364
- digest.startsWith("sha256:"),
365
- `digest should start with "sha256:", got: ${digest}`,
366
- );
367
- // full format: sha256: + 64 lowercase hex chars
368
- assert.match(digest, /^sha256:[a-f0-9]{64}$/);
369
- });
370
-
371
- it("does not leak raw argument values into the digest string", () => {
372
- const fakeToken = "tok-value";
373
- const sensitiveArgs = { token: fakeToken, prompt: "hello-world" };
374
- const digest = computeArgumentsDigest(sensitiveArgs);
375
- assert.ok(
376
- !digest.includes(fakeToken),
377
- "digest must not leak raw token value",
378
- );
379
- assert.ok(
380
- !digest.includes("hello-world"),
381
- "digest must not leak raw prompt value",
382
- );
383
- });
384
- });
385
-
386
- // ── logToolInvocation ────────────────────────────────────────────
387
-
388
- describe("logToolInvocation", () => {
389
- let tempDir: string;
390
-
391
- beforeEach(() => {
392
- tempDir = mkdtempSync(join(tmpdir(), "pw-invlog-"));
393
- });
394
-
395
- afterEach(() => {
396
- rmSync(tempDir, { recursive: true, force: true });
397
- });
398
-
399
- function sampleEntry(overrides: Partial<InvocationLogEntry> = {}): InvocationLogEntry {
400
- return {
401
- timestamp: new Date().toISOString(),
402
- toolName: "read_workspace_file",
403
- discoveryToken: "tok",
404
- risk: "workspace_read_sensitive",
405
- profile: "full",
406
- arguments_digest: "sha256:" + "0".repeat(64),
407
- result: "ok",
408
- duration_ms: 42,
409
- ...overrides,
410
- };
411
- }
412
-
413
- it("writes an entry to invocation.log inside the provided logsDir", () => {
414
- const logsDir = join(tempDir, ".patchwarden", "logs");
415
- const entry = sampleEntry();
416
- logToolInvocation(entry, { logsDir });
417
-
418
- const logPath = join(logsDir, "invocation.log");
419
- assert.ok(existsSync(logPath), "invocation.log should be created");
420
-
421
- const content = readFileSync(logPath, "utf-8");
422
- const lines = content.trim().split("\n");
423
- assert.equal(lines.length, 1, "exactly one JSON line should be written");
424
-
425
- const parsed = JSON.parse(lines[0]);
426
- assert.equal(parsed.toolName, "read_workspace_file");
427
- assert.equal(parsed.discoveryToken, "tok");
428
- assert.equal(parsed.risk, "workspace_read_sensitive");
429
- assert.equal(parsed.profile, "full");
430
- assert.equal(parsed.result, "ok");
431
- assert.equal(parsed.duration_ms, 42);
432
- assert.match(parsed.arguments_digest, /^sha256:[a-f0-9]{64}$/);
433
- assert.ok(!Number.isNaN(Date.parse(parsed.timestamp)));
434
- });
435
-
436
- it("appends multiple entries as JSON Lines", () => {
437
- const logsDir = join(tempDir, "logs");
438
- logToolInvocation(sampleEntry({ duration_ms: 1 }), { logsDir });
439
- logToolInvocation(
440
- sampleEntry({ duration_ms: 2, result: "error", error_code: "blocked" }),
441
- { logsDir },
442
- );
443
-
444
- const content = readFileSync(join(logsDir, "invocation.log"), "utf-8");
445
- const lines = content.trim().split("\n");
446
- assert.equal(lines.length, 2);
447
-
448
- const first = JSON.parse(lines[0]);
449
- const second = JSON.parse(lines[1]);
450
- assert.equal(first.duration_ms, 1);
451
- assert.equal(first.result, "ok");
452
- assert.equal(second.duration_ms, 2);
453
- assert.equal(second.result, "error");
454
- assert.equal(second.error_code, "blocked");
455
- });
456
-
457
- it("includes allowedScope and error_code when provided", () => {
458
- const logsDir = join(tempDir, "logs");
459
- logToolInvocation(
460
- sampleEntry({
461
- allowedScope: ["src/", "docs/"],
462
- result: "error",
463
- error_code: "scope_violation",
464
- }),
465
- { logsDir },
466
- );
467
-
468
- const parsed = JSON.parse(
469
- readFileSync(join(logsDir, "invocation.log"), "utf-8").trim(),
470
- );
471
- assert.deepEqual(parsed.allowedScope, ["src/", "docs/"]);
472
- assert.equal(parsed.error_code, "scope_violation");
473
- });
474
-
475
- it("omits error_code and allowedScope when not provided", () => {
476
- const logsDir = join(tempDir, "logs");
477
- logToolInvocation(sampleEntry(), { logsDir });
478
-
479
- const parsed = JSON.parse(
480
- readFileSync(join(logsDir, "invocation.log"), "utf-8").trim(),
481
- );
482
- assert.equal(parsed.error_code, undefined);
483
- assert.equal(parsed.allowedScope, undefined);
484
- });
485
-
486
- it("does not throw when the log directory cannot be created", () => {
487
- // Create a file, then attempt to use a path beneath it as logsDir.
488
- // mkdirSync will fail with ENOTDIR because the parent is a file.
489
- const blockingFile = join(tempDir, "blocker");
490
- writeFileSync(blockingFile, "x", "utf-8");
491
- const impossibleLogsDir = join(blockingFile, "subdir");
492
-
493
- assert.doesNotThrow(() => {
494
- logToolInvocation(sampleEntry(), { logsDir: impossibleLogsDir });
495
- });
496
- });
497
- });
@@ -1,180 +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, sep } from "node:path";
5
- import { tmpdir } from "node:os";
6
- import { guardPath, guardWorkspacePath } from "../../security/pathGuard.js";
7
- import { PatchWardenError } from "../../errors.js";
8
-
9
- describe("guardPath", () => {
10
- let tempDir: string;
11
-
12
- beforeEach(() => {
13
- tempDir = mkdtempSync(join(tmpdir(), "pw-pathguard-"));
14
- });
15
-
16
- afterEach(() => {
17
- rmSync(tempDir, { recursive: true, force: true });
18
- });
19
-
20
- it("allows paths inside workspace", () => {
21
- const result = guardPath("src/main.ts", tempDir);
22
- assert.ok(result.startsWith(tempDir));
23
- assert.ok(result.includes("src"));
24
- assert.ok(result.includes("main.ts"));
25
- });
26
-
27
- it("allows root workspace path", () => {
28
- const result = guardPath(".", tempDir);
29
- assert.equal(result, resolve(tempDir));
30
- });
31
-
32
- it("allows empty path as workspace root", () => {
33
- const result = guardPath("", tempDir);
34
- assert.equal(result, resolve(tempDir));
35
- });
36
-
37
- it("rejects path traversal with ..", () => {
38
- assert.throws(
39
- () => guardPath("../../../etc/passwd", tempDir),
40
- PatchWardenError
41
- );
42
- });
43
-
44
- it("rejects path traversal nested with ..", () => {
45
- assert.throws(
46
- () => guardPath("src/../../../etc/passwd", tempDir),
47
- PatchWardenError
48
- );
49
- });
50
-
51
- it("rejects absolute path outside workspace", () => {
52
- const outside = process.platform === "win32" ? "C:\\Windows\\System32" : "/etc";
53
- assert.throws(
54
- () => guardPath(outside, tempDir),
55
- PatchWardenError
56
- );
57
- });
58
-
59
- it("allows absolute path inside workspace", () => {
60
- const insidePath = join(tempDir, "src", "main.ts");
61
- mkdirSync(join(tempDir, "src"), { recursive: true });
62
- writeFileSync(insidePath, "test", "utf-8");
63
- const result = guardPath(insidePath, tempDir);
64
- assert.equal(result, resolve(insidePath));
65
- });
66
-
67
- it("enforces allowedPrefix", () => {
68
- mkdirSync(join(tempDir, "allowed"), { recursive: true });
69
- mkdirSync(join(tempDir, "forbidden"), { recursive: true });
70
- // Path inside allowed prefix works
71
- const okResult = guardPath("allowed/file.ts", tempDir, "allowed");
72
- assert.ok(okResult.includes("allowed"));
73
- // Path outside allowed prefix fails
74
- assert.throws(
75
- () => guardPath("forbidden/file.ts", tempDir, "allowed"),
76
- PatchWardenError
77
- );
78
- });
79
-
80
- it("handles Windows mixed separators", () => {
81
- const mixedPath = "src\\subdir/file.ts";
82
- const result = guardPath(mixedPath, tempDir);
83
- assert.ok(result.startsWith(tempDir));
84
- });
85
-
86
- it("rejects symlink escape", { skip: process.platform === "win32" ? "Windows symlink permissions unstable" : undefined }, () => {
87
- const target = mkdtempSync(join(tmpdir(), "pw-symlink-target-"));
88
- try {
89
- const linkPath = join(tempDir, "escape-link");
90
- symlinkSync(target, linkPath);
91
- assert.throws(
92
- () => guardPath("escape-link/secret.txt", tempDir),
93
- PatchWardenError
94
- );
95
- } finally {
96
- rmSync(target, { recursive: true, force: true });
97
- }
98
- });
99
-
100
- it("rejects Windows junction escape", { skip: process.platform !== "win32" ? "Windows-only test" : undefined }, () => {
101
- const target = mkdtempSync(join(tmpdir(), "pw-junction-target-"));
102
- try {
103
- const linkPath = join(tempDir, "escape-junction");
104
- symlinkSync(target, linkPath, "junction");
105
- assert.throws(
106
- () => guardPath("escape-junction/secret.txt", tempDir),
107
- PatchWardenError
108
- );
109
- } finally {
110
- rmSync(target, { recursive: true, force: true });
111
- }
112
- });
113
-
114
- it("rejects intermediate symlink escape (TOCTOU defense)", { skip: process.platform === "win32" ? "Windows symlink permissions unstable" : undefined }, () => {
115
- const target = mkdtempSync(join(tmpdir(), "pw-toctou-target-"));
116
- try {
117
- const linkPath = join(tempDir, "escape-link");
118
- symlinkSync(target, linkPath);
119
- mkdirSync(join(target, "subdir"), { recursive: true });
120
- writeFileSync(join(target, "subdir", "secret.txt"), "secret", "utf-8");
121
- assert.throws(
122
- () => guardPath("escape-link/subdir/secret.txt", tempDir),
123
- PatchWardenError
124
- );
125
- } finally {
126
- rmSync(target, { recursive: true, force: true });
127
- }
128
- });
129
- });
130
-
131
- describe("guardWorkspacePath", () => {
132
- let tempDir: string;
133
-
134
- beforeEach(() => {
135
- tempDir = mkdtempSync(join(tmpdir(), "pw-wspath-"));
136
- });
137
-
138
- afterEach(() => {
139
- rmSync(tempDir, { recursive: true, force: true });
140
- });
141
-
142
- it("allows relative path inside workspace", () => {
143
- const result = guardWorkspacePath("my-repo", tempDir);
144
- assert.equal(result, resolve(tempDir, "my-repo"));
145
- });
146
-
147
- it("allows absolute path inside workspace", () => {
148
- const absPath = join(tempDir, "my-repo");
149
- const result = guardWorkspacePath(absPath, tempDir);
150
- assert.equal(result, resolve(absPath));
151
- });
152
-
153
- it("rejects path outside workspace", () => {
154
- assert.throws(
155
- () => guardWorkspacePath("../../../etc", tempDir),
156
- PatchWardenError
157
- );
158
- });
159
-
160
- it("rejects different drive letter on Windows", { skip: process.platform !== "win32" ? "Windows-only test" : undefined }, () => {
161
- // If workspace is on C:, a D: path should be rejected
162
- const wsDrive = tempDir.match(/^([A-Za-z]):/);
163
- if (!wsDrive) return;
164
- const otherDrive = wsDrive[1].toLowerCase() === "c" ? "D" : "C";
165
- assert.throws(
166
- () => guardWorkspacePath(`${otherDrive}:\\some\\repo`, tempDir),
167
- (err: unknown) => err instanceof PatchWardenError && err.reason === "workspace_path_escape"
168
- );
169
- });
170
-
171
- it("defaults to workspace root for empty input", () => {
172
- const result = guardWorkspacePath("", tempDir);
173
- assert.equal(result, resolve(tempDir));
174
- });
175
-
176
- it("defaults to workspace root for dot input", () => {
177
- const result = guardWorkspacePath(".", tempDir);
178
- assert.equal(result, resolve(tempDir));
179
- });
180
- });