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,457 +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, utimesSync, existsSync, } from "node:fs";
4
- import { join, dirname } from "node:path";
5
- import { tmpdir } from "node:os";
6
- import { diagnoseTask } from "../../tools/diagnoseTask.js";
7
- // ── Test fixtures ──────────────────────────────────────────────────
8
- function makeConfig(workspaceRoot) {
9
- return {
10
- workspaceRoot,
11
- plansDir: ".patchwarden/plans",
12
- tasksDir: ".patchwarden/tasks",
13
- assessmentsDir: ".patchwarden/assessments",
14
- assessmentTtlSeconds: 3600,
15
- agents: { codex: { command: "codex", args: ["exec", "{prompt}"] } },
16
- allowedTestCommands: ["npm test"],
17
- repoAllowedTestCommands: {},
18
- maxReadFileBytes: 200_000,
19
- defaultTaskTimeoutSeconds: 900,
20
- maxTaskTimeoutSeconds: 3600,
21
- watcherStaleSeconds: 30,
22
- directSessionsDir: ".patchwarden/direct-sessions",
23
- directSessionTtlSeconds: 3600,
24
- directMaxPatchBytes: 200_000,
25
- directMaxFileBytes: 500_000,
26
- };
27
- }
28
- /** A PID that is guaranteed to be alive (the test process itself). */
29
- const ALIVE_PID = process.pid;
30
- /** A PID that is guaranteed to not exist (well above typical PID max). */
31
- const DEAD_PID = 999999;
32
- function isoSecondsAgo(seconds) {
33
- return new Date(Date.now() - seconds * 1000).toISOString();
34
- }
35
- /** Set a file's mtime to N seconds ago so diagnose_task sees it as stale or fresh. */
36
- function ageFile(filePath, secondsAgo) {
37
- if (!existsSync(filePath))
38
- return;
39
- const targetMs = Date.now() - secondsAgo * 1000;
40
- const targetSec = targetMs / 1000;
41
- utimesSync(filePath, targetSec, targetSec);
42
- }
43
- describe("diagnoseTask", () => {
44
- let tempDir;
45
- let config;
46
- let tasksDir;
47
- let watcherHeartbeatPath;
48
- beforeEach(() => {
49
- tempDir = mkdtempSync(join(tmpdir(), "pw-diagnose-"));
50
- config = makeConfig(tempDir);
51
- tasksDir = join(tempDir, ".patchwarden", "tasks");
52
- mkdirSync(tasksDir, { recursive: true });
53
- watcherHeartbeatPath = join(dirname(tasksDir), "watcher-heartbeat.json");
54
- });
55
- afterEach(() => {
56
- rmSync(tempDir, { recursive: true, force: true });
57
- });
58
- /**
59
- * Build a task fixture with status.json, runtime.json, and optional log
60
- * files with controlled mtimes.
61
- */
62
- function buildTask(opts) {
63
- const taskId = opts.taskId;
64
- const taskDir = join(tasksDir, taskId);
65
- mkdirSync(taskDir, { recursive: true });
66
- const statusSecondsAgo = opts.createdSecondsAgo ?? 600;
67
- const status = {
68
- task_id: taskId,
69
- status: opts.status ?? "running",
70
- phase: opts.phase ?? "executing_agent",
71
- created_at: isoSecondsAgo(statusSecondsAgo),
72
- updated_at: isoSecondsAgo(statusSecondsAgo),
73
- };
74
- if (opts.heartbeatSecondsAgo !== undefined) {
75
- status.last_heartbeat_at = isoSecondsAgo(opts.heartbeatSecondsAgo);
76
- }
77
- writeFileSync(join(taskDir, "status.json"), JSON.stringify(status, null, 2), "utf-8");
78
- const runtime = {
79
- phase: opts.phase ?? "executing_agent",
80
- };
81
- if (opts.heartbeatSecondsAgo !== undefined) {
82
- runtime.last_heartbeat_at = isoSecondsAgo(opts.heartbeatSecondsAgo);
83
- }
84
- if (opts.childPid !== undefined) {
85
- runtime.child_pid = opts.childPid;
86
- }
87
- if (opts.childStartedSecondsAgo !== undefined) {
88
- runtime.child_started_at = isoSecondsAgo(opts.childStartedSecondsAgo);
89
- }
90
- if (opts.watcherInstanceId !== undefined) {
91
- runtime.watcher_instance_id = opts.watcherInstanceId;
92
- }
93
- if (opts.taskStartedSecondsAgo !== undefined) {
94
- runtime.task_started_at = isoSecondsAgo(opts.taskStartedSecondsAgo);
95
- }
96
- runtime.current_command = null;
97
- writeFileSync(join(taskDir, "runtime.json"), JSON.stringify(runtime, null, 2), "utf-8");
98
- if (opts.stdoutContent !== undefined) {
99
- const stdoutPath = join(taskDir, "stdout.log");
100
- writeFileSync(stdoutPath, opts.stdoutContent, "utf-8");
101
- if (opts.stdoutSecondsAgo !== undefined)
102
- ageFile(stdoutPath, opts.stdoutSecondsAgo);
103
- }
104
- if (opts.stderrContent !== undefined) {
105
- const stderrPath = join(taskDir, "stderr.log");
106
- writeFileSync(stderrPath, opts.stderrContent, "utf-8");
107
- if (opts.stderrSecondsAgo !== undefined)
108
- ageFile(stderrPath, opts.stderrSecondsAgo);
109
- }
110
- if (opts.resultMd) {
111
- writeFileSync(join(taskDir, "result.md"), "# Result\nTask completed.", "utf-8");
112
- }
113
- if (opts.testLog) {
114
- writeFileSync(join(taskDir, "test.log"), "TAP version 13\n1..1\nok 1 test", "utf-8");
115
- }
116
- if (opts.gitDiff) {
117
- writeFileSync(join(taskDir, "git.diff"), "diff --git a/foo b/foo\n", "utf-8");
118
- }
119
- if (opts.artifactManifest) {
120
- writeFileSync(join(taskDir, "artifact_manifest.json"), "{}", "utf-8");
121
- }
122
- return taskDir;
123
- }
124
- /** Write a watcher heartbeat with the given instance_id and freshness. */
125
- function writeWatcherHeartbeat(instanceId, secondsAgo = 5) {
126
- const heartbeat = {
127
- last_heartbeat_at: isoSecondsAgo(secondsAgo),
128
- pid: process.pid,
129
- instance_id: instanceId,
130
- launcher_pid: process.ppid ?? null,
131
- };
132
- writeFileSync(watcherHeartbeatPath, JSON.stringify(heartbeat, null, 2), "utf-8");
133
- }
134
- // ── 1. active_running: fresh heartbeat, watcher owns, logs growing ──
135
- it("diagnoses active_running when heartbeat fresh, watcher owns, logs growing", () => {
136
- buildTask({
137
- taskId: "task-active-001",
138
- status: "running",
139
- phase: "executing_agent",
140
- heartbeatSecondsAgo: 10, // fresh (< 120s)
141
- childPid: ALIVE_PID, // alive
142
- watcherInstanceId: "watcher-A", // matches watcher
143
- taskStartedSecondsAgo: 60,
144
- stdoutContent: "Running...\n",
145
- stdoutSecondsAgo: 5, // logs growing (< 120s)
146
- stderrContent: "stderr\n",
147
- stderrSecondsAgo: 5,
148
- });
149
- writeWatcherHeartbeat("watcher-A", 3);
150
- const result = diagnoseTask({ task_id: "task-active-001" }, config);
151
- assert.equal(result.diagnosis, "active_running");
152
- assert.equal(result.confidence, "high");
153
- assert.ok(result.safe_actions.includes("leave_unchanged"));
154
- assert.equal(result.evidence.watcher_owns_task, true);
155
- assert.equal(result.evidence.child_pid_alive, true);
156
- assert.equal(result.evidence.pid_reuse_suspected, false);
157
- });
158
- // ── 2. stale_running: heartbeat stale, PID dead, no watcher ownership, logs stale ──
159
- //
160
- // This task was started directly (e.g., via run_task MCP tool) and has no
161
- // watcher_instance_id in runtime, so isWatcherOwningTask returns
162
- // "no_runtime_record" — which does NOT trigger orphaned_running. With
163
- // heartbeat/PID/logs all stale, it falls through to stale_running.
164
- it("diagnoses stale_running when heartbeat stale, PID dead, no watcher record, logs stale", () => {
165
- buildTask({
166
- taskId: "task-stale-001",
167
- status: "running",
168
- phase: "executing_agent",
169
- heartbeatSecondsAgo: 600, // stale (> 300s)
170
- childPid: DEAD_PID, // dead
171
- // No watcherInstanceId — simulates task started directly via run_task
172
- taskStartedSecondsAgo: 700,
173
- stdoutContent: "old output\n",
174
- stdoutSecondsAgo: 600, // stale (> 300s)
175
- stderrContent: "old stderr\n",
176
- stderrSecondsAgo: 600,
177
- });
178
- const result = diagnoseTask({ task_id: "task-stale-001" }, config);
179
- assert.equal(result.diagnosis, "stale_running");
180
- assert.equal(result.confidence, "high");
181
- assert.ok(result.safe_actions.includes("mark_failed_stale"));
182
- assert.equal(result.evidence.child_pid_alive, false);
183
- assert.equal(result.evidence.watcher_owns_task, false);
184
- assert.equal(result.evidence.watcher_ownership_reason, "no_runtime_record");
185
- });
186
- // ── 3. possibly_stale_running: heartbeat possibly stale + logs stale, PID alive ──
187
- //
188
- // heartbeat is between 120-300s (possibly stale), logs are > 300s (stale),
189
- // and PID is alive but pidReuseSuspected is false because heartbeat is not
190
- // stale enough (> 300s required). This falls into possibly_stale_running.
191
- it("diagnoses possibly_stale_running when heartbeat possibly stale and logs stale", () => {
192
- buildTask({
193
- taskId: "task-posstale-001",
194
- status: "running",
195
- phase: "executing_agent",
196
- heartbeatSecondsAgo: 200, // possibly stale (> 120, <= 300)
197
- childPid: ALIVE_PID, // alive
198
- watcherInstanceId: "watcher-A",
199
- taskStartedSecondsAgo: 300,
200
- stdoutContent: "output\n",
201
- stdoutSecondsAgo: 400, // stale (> 300s)
202
- stderrContent: "stderr\n",
203
- stderrSecondsAgo: 400,
204
- });
205
- writeWatcherHeartbeat("watcher-A", 5);
206
- const result = diagnoseTask({ task_id: "task-posstale-001" }, config);
207
- // heartbeatPossiblyStale=true && logsStale=true → possibly_stale_running
208
- assert.equal(result.diagnosis, "possibly_stale_running");
209
- assert.equal(result.confidence, "medium");
210
- assert.ok(result.safe_actions.includes("leave_unchanged"));
211
- assert.equal(result.evidence.pid_reuse_suspected, false);
212
- });
213
- // ── 4. orphaned_running: watcher does not own task (instance mismatch) ──
214
- it("diagnoses orphaned_running when watcher instance_id mismatches", () => {
215
- buildTask({
216
- taskId: "task-orphan-001",
217
- status: "running",
218
- phase: "executing_agent",
219
- heartbeatSecondsAgo: 30, // fresh
220
- childPid: ALIVE_PID,
221
- watcherInstanceId: "watcher-old", // does NOT match current watcher
222
- taskStartedSecondsAgo: 60,
223
- stdoutContent: "output\n",
224
- stdoutSecondsAgo: 10,
225
- stderrContent: "stderr\n",
226
- stderrSecondsAgo: 10,
227
- });
228
- writeWatcherHeartbeat("watcher-new", 5);
229
- const result = diagnoseTask({ task_id: "task-orphan-001" }, config);
230
- assert.equal(result.diagnosis, "orphaned_running");
231
- assert.equal(result.confidence, "high");
232
- assert.ok(result.safe_actions.includes("mark_orphaned"));
233
- assert.equal(result.evidence.watcher_owns_task, false);
234
- assert.equal(result.evidence.watcher_ownership_reason, "instance_mismatch");
235
- });
236
- // ── 5. artifact_collection_stuck: in collecting_artifacts and stuck ──
237
- it("diagnoses artifact_collection_stuck when in collecting phase and signals stale", () => {
238
- buildTask({
239
- taskId: "task-stuckart-001",
240
- status: "collecting_artifacts",
241
- phase: "collecting_artifacts",
242
- heartbeatSecondsAgo: 400, // stale
243
- childPid: DEAD_PID, // dead
244
- watcherInstanceId: "watcher-A",
245
- taskStartedSecondsAgo: 500,
246
- stdoutContent: "agent done\n",
247
- stdoutSecondsAgo: 400,
248
- stderrContent: "stderr\n",
249
- stderrSecondsAgo: 400,
250
- });
251
- writeWatcherHeartbeat("watcher-A", 5);
252
- const result = diagnoseTask({ task_id: "task-stuckart-001" }, config);
253
- assert.equal(result.diagnosis, "artifact_collection_stuck");
254
- assert.equal(result.confidence, "high");
255
- assert.ok(result.safe_actions.includes("recollect_artifacts"));
256
- assert.ok(result.safe_actions.includes("mark_failed_stale"));
257
- assert.equal(result.evidence.collecting_artifacts_phase, true);
258
- });
259
- // ── 6. done_candidate: artifacts exist while status still running ──
260
- it("diagnoses done_candidate with high confidence when result.md + test.log exist", () => {
261
- buildTask({
262
- taskId: "task-donecand-001",
263
- status: "running",
264
- phase: "executing_agent",
265
- heartbeatSecondsAgo: 400,
266
- childPid: DEAD_PID,
267
- watcherInstanceId: "watcher-old",
268
- taskStartedSecondsAgo: 500,
269
- stdoutContent: "done\n",
270
- stdoutSecondsAgo: 400,
271
- stderrContent: "stderr\n",
272
- stderrSecondsAgo: 400,
273
- resultMd: true,
274
- testLog: true,
275
- gitDiff: true,
276
- });
277
- // No watcher heartbeat — orphaned
278
- const result = diagnoseTask({ task_id: "task-donecand-001" }, config);
279
- // done_candidate takes priority over orphaned_running because artifacts exist
280
- assert.equal(result.diagnosis, "done_candidate");
281
- assert.equal(result.confidence, "high");
282
- assert.ok(result.safe_actions.includes("mark_done_by_agent"));
283
- assert.equal(result.evidence.has_result_md, true);
284
- assert.equal(result.evidence.has_test_log, true);
285
- });
286
- it("diagnoses done_candidate with medium confidence when only result.md + collecting phase", () => {
287
- buildTask({
288
- taskId: "task-donecand-002",
289
- status: "collecting_artifacts",
290
- phase: "collecting_artifacts",
291
- heartbeatSecondsAgo: 30, // fresh — not stuck
292
- childPid: ALIVE_PID,
293
- watcherInstanceId: "watcher-A",
294
- taskStartedSecondsAgo: 100,
295
- stdoutContent: "done\n",
296
- stdoutSecondsAgo: 30,
297
- stderrContent: "stderr\n",
298
- stderrSecondsAgo: 30,
299
- resultMd: true,
300
- // No test.log/git.diff → medium signal
301
- });
302
- writeWatcherHeartbeat("watcher-A", 5);
303
- const result = diagnoseTask({ task_id: "task-donecand-002" }, config);
304
- assert.equal(result.diagnosis, "done_candidate");
305
- assert.equal(result.confidence, "medium");
306
- assert.ok(result.safe_actions.includes("mark_done_by_agent"));
307
- assert.ok(result.safe_actions.includes("collect_artifacts"));
308
- });
309
- // ── 7. unknown: insufficient evidence ──
310
- //
311
- // heartbeat is fresh but PID is dead and logs are stale — no single
312
- // branch matches with enough confidence, so it falls through to unknown.
313
- it("diagnoses unknown when signals conflict (fresh heartbeat but dead PID + stale logs)", () => {
314
- buildTask({
315
- taskId: "task-unknown-001",
316
- status: "running",
317
- phase: "executing_agent",
318
- heartbeatSecondsAgo: 10, // fresh — doesn't hit stale paths
319
- childPid: DEAD_PID, // dead — but heartbeat says fresh
320
- watcherInstanceId: "watcher-A",
321
- taskStartedSecondsAgo: 60,
322
- stdoutContent: "running\n",
323
- stdoutSecondsAgo: 400, // stale — but heartbeat says fresh
324
- stderrContent: "stderr\n",
325
- stderrSecondsAgo: 400,
326
- });
327
- writeWatcherHeartbeat("watcher-A", 5);
328
- const result = diagnoseTask({ task_id: "task-unknown-001" }, config);
329
- // Fresh heartbeat means not stale_running / possibly_stale_running.
330
- // Dead PID + stale logs means not active_running.
331
- // Conflicting signals → unknown.
332
- assert.equal(result.diagnosis, "unknown");
333
- assert.equal(result.confidence, "low");
334
- assert.ok(result.safe_actions.includes("leave_unchanged"));
335
- });
336
- // ── 8. PID reuse case: PID alive but everything else stale ──
337
- //
338
- // This is the conservative safety check: even though process.kill(pid, 0)
339
- // returns true, we refuse to call the task "active" because heartbeat and
340
- // logs are all stale. The PID may have been reused by the OS for an
341
- // unrelated process.
342
- it("refuses active_running on PID reuse: PID alive but heartbeat + logs stale", () => {
343
- buildTask({
344
- taskId: "task-pidreuse-001",
345
- status: "running",
346
- phase: "executing_agent",
347
- heartbeatSecondsAgo: 600, // very stale
348
- childPid: ALIVE_PID, // alive — but probably reused!
349
- watcherInstanceId: "watcher-A",
350
- taskStartedSecondsAgo: 700,
351
- stdoutContent: "old output\n",
352
- stdoutSecondsAgo: 600, // very stale
353
- stderrContent: "old stderr\n",
354
- stderrSecondsAgo: 600,
355
- });
356
- writeWatcherHeartbeat("watcher-A", 5);
357
- const result = diagnoseTask({ task_id: "task-pidreuse-001" }, config);
358
- // Must NOT be active_running — that would be the unsafe conclusion.
359
- assert.notEqual(result.diagnosis, "active_running");
360
- assert.equal(result.diagnosis, "possibly_stale_running");
361
- assert.equal(result.confidence, "medium");
362
- assert.equal(result.evidence.pid_reuse_suspected, true);
363
- assert.equal(result.evidence.child_pid_alive, true);
364
- assert.ok(result.reasons.some((r) => r.includes("PID reuse")));
365
- });
366
- // ── 9. terminal: already-terminal statuses return terminal diagnosis ──
367
- it("returns terminal diagnosis for done status", () => {
368
- buildTask({
369
- taskId: "pw-terminal-done",
370
- status: "done",
371
- phase: "completed",
372
- createdSecondsAgo: 1000,
373
- });
374
- const result = diagnoseTask({ task_id: "pw-terminal-done" }, config);
375
- assert.equal(result.diagnosis, "terminal");
376
- assert.equal(result.confidence, "high");
377
- assert.ok(result.safe_actions.includes("leave_unchanged"));
378
- });
379
- it("returns terminal diagnosis for failed_stale status (v0.7.0 status)", () => {
380
- buildTask({
381
- taskId: "pw-terminal-failed-stale",
382
- status: "failed_stale",
383
- phase: "failed_stale",
384
- createdSecondsAgo: 1000,
385
- });
386
- const result = diagnoseTask({ task_id: "pw-terminal-failed-stale" }, config);
387
- assert.equal(result.diagnosis, "terminal");
388
- });
389
- it("returns terminal diagnosis for orphaned status (v0.7.0 status)", () => {
390
- buildTask({
391
- taskId: "pw-terminal-orphaned",
392
- status: "orphaned",
393
- phase: "orphaned",
394
- createdSecondsAgo: 1000,
395
- });
396
- const result = diagnoseTask({ task_id: "pw-terminal-orphaned" }, config);
397
- assert.equal(result.diagnosis, "terminal");
398
- });
399
- it("returns terminal diagnosis for done_by_agent status (v0.7.0 status)", () => {
400
- buildTask({
401
- taskId: "pw-terminal-done-by-agent",
402
- status: "done_by_agent",
403
- phase: "done_by_agent",
404
- createdSecondsAgo: 1000,
405
- });
406
- const result = diagnoseTask({ task_id: "pw-terminal-done-by-agent" }, config);
407
- assert.equal(result.diagnosis, "terminal");
408
- });
409
- // ── include_logs flag ──
410
- it("includes redacted log tails when include_logs=true", () => {
411
- buildTask({
412
- taskId: "task-logs-001",
413
- status: "running",
414
- phase: "executing_agent",
415
- heartbeatSecondsAgo: 10,
416
- childPid: ALIVE_PID,
417
- watcherInstanceId: "watcher-A",
418
- taskStartedSecondsAgo: 60,
419
- stdoutContent: "line1\nline2 with Bearer abcdefghijklmnop123456\n",
420
- stdoutSecondsAgo: 5,
421
- stderrContent: "stderr line\n",
422
- stderrSecondsAgo: 5,
423
- });
424
- writeWatcherHeartbeat("watcher-A", 3);
425
- const result = diagnoseTask({ task_id: "task-logs-001", include_logs: true }, config);
426
- assert.ok(result.logs !== null);
427
- assert.ok(result.logs.stdout_tail !== null);
428
- assert.ok(result.logs.stderr_tail !== null);
429
- // The bearer token must be redacted
430
- assert.ok(!result.logs.stdout_tail.includes("abcdefghijklmnop123456"));
431
- assert.ok(result.logs.stdout_tail.includes("[REDACTED]"));
432
- });
433
- it("omits log tails when include_logs is false (default)", () => {
434
- buildTask({
435
- taskId: "task-nologs-001",
436
- status: "running",
437
- phase: "executing_agent",
438
- heartbeatSecondsAgo: 10,
439
- childPid: ALIVE_PID,
440
- watcherInstanceId: "watcher-A",
441
- taskStartedSecondsAgo: 60,
442
- stdoutContent: "line1\n",
443
- stdoutSecondsAgo: 5,
444
- stderrContent: "stderr\n",
445
- stderrSecondsAgo: 5,
446
- });
447
- writeWatcherHeartbeat("watcher-A", 3);
448
- const result = diagnoseTask({ task_id: "task-nologs-001" }, config);
449
- assert.equal(result.logs, null);
450
- });
451
- // ── Error case: task not found ──
452
- it("throws when task does not exist", () => {
453
- // guardReadPath throws "File not found" before the existsSync check
454
- // produces "Task not found" — both indicate the task doesn't exist.
455
- assert.throws(() => diagnoseTask({ task_id: "non-existent-task" }, config), /(Task not found|File not found)/);
456
- });
457
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,213 +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 } from "node:fs";
4
- import { join, resolve } from "node:path";
5
- import { tmpdir } from "node:os";
6
- import { guardDirectPath, guardDirectWritePath, guardDirectReadPath, isBinaryFile } from "../../direct/directGuards.js";
7
- import { PatchWardenError } from "../../errors.js";
8
- function makeConfig(workspaceRoot) {
9
- return {
10
- workspaceRoot,
11
- plansDir: ".patchwarden/plans",
12
- tasksDir: ".patchwarden/tasks",
13
- assessmentsDir: ".patchwarden/assessments",
14
- assessmentTtlSeconds: 3600,
15
- agents: { codex: { command: "codex", args: ["exec", "{prompt}"] } },
16
- allowedTestCommands: ["npm test"],
17
- repoAllowedTestCommands: {},
18
- maxReadFileBytes: 200_000,
19
- defaultTaskTimeoutSeconds: 900,
20
- maxTaskTimeoutSeconds: 3600,
21
- watcherStaleSeconds: 30,
22
- directSessionsDir: ".patchwarden/direct-sessions",
23
- directSessionTtlSeconds: 3600,
24
- directMaxPatchBytes: 200_000,
25
- directMaxFileBytes: 500_000,
26
- directAllowedCommands: ["npm test"],
27
- repoDirectAllowedCommands: {},
28
- };
29
- }
30
- describe("guardDirectPath", () => {
31
- let tempDir;
32
- let repoPath;
33
- beforeEach(() => {
34
- tempDir = mkdtempSync(join(tmpdir(), "pw-directpath-"));
35
- repoPath = join(tempDir, "my-repo");
36
- mkdirSync(repoPath, { recursive: true });
37
- // Set up config so getConfig() works in guardDirectPath
38
- process.env.PATCHWARDEN_CONFIG = "";
39
- });
40
- afterEach(() => {
41
- rmSync(tempDir, { recursive: true, force: true });
42
- delete process.env.PATCHWARDEN_CONFIG;
43
- });
44
- it("allows paths inside repo", () => {
45
- const result = guardDirectPath("src/main.ts", repoPath, tempDir);
46
- assert.equal(result, resolve(repoPath, "src/main.ts"));
47
- });
48
- it("allows nested paths inside repo", () => {
49
- const result = guardDirectPath("src/components/Button.tsx", repoPath, tempDir);
50
- assert.equal(result, resolve(repoPath, "src/components/Button.tsx"));
51
- });
52
- it("rejects paths outside repo but inside workspace", () => {
53
- assert.throws(() => guardDirectPath("../other-repo/file.ts", repoPath, tempDir), PatchWardenError);
54
- });
55
- it("rejects paths outside workspace", () => {
56
- assert.throws(() => guardDirectPath("../../../etc/passwd", repoPath, tempDir), PatchWardenError);
57
- });
58
- it("handles Windows backslash paths", () => {
59
- const result = guardDirectPath("src\\subdir\\file.ts", repoPath, tempDir);
60
- assert.ok(result.startsWith(repoPath));
61
- });
62
- it("rejects path traversal with ..", () => {
63
- assert.throws(() => guardDirectPath("src/../../etc/passwd", repoPath, tempDir), PatchWardenError);
64
- });
65
- });
66
- describe("guardDirectWritePath", () => {
67
- let tempDir;
68
- let repoPath;
69
- beforeEach(() => {
70
- tempDir = mkdtempSync(join(tmpdir(), "pw-directwrite-"));
71
- repoPath = join(tempDir, "my-repo");
72
- mkdirSync(repoPath, { recursive: true });
73
- mkdirSync(join(repoPath, "src"), { recursive: true });
74
- });
75
- afterEach(() => {
76
- rmSync(tempDir, { recursive: true, force: true });
77
- });
78
- it("allows writing to source files in repo", () => {
79
- const result = guardDirectWritePath("src/main.ts", repoPath, tempDir);
80
- assert.ok(result.startsWith(repoPath));
81
- });
82
- it("blocks node_modules paths", () => {
83
- assert.throws(() => guardDirectWritePath("node_modules/evil/index.js", repoPath, tempDir), (err) => err instanceof PatchWardenError && err.reason === "blocked_artifact_path");
84
- });
85
- it("blocks nested node_modules paths", () => {
86
- assert.throws(() => guardDirectWritePath("src/node_modules/evil/index.js", repoPath, tempDir), (err) => err instanceof PatchWardenError && err.reason === "blocked_artifact_path");
87
- });
88
- it("blocks dist paths", () => {
89
- assert.throws(() => guardDirectWritePath("dist/main.js", repoPath, tempDir), (err) => err instanceof PatchWardenError && err.reason === "blocked_artifact_path");
90
- });
91
- it("blocks release paths", () => {
92
- assert.throws(() => guardDirectWritePath("release/app.exe", repoPath, tempDir), (err) => err instanceof PatchWardenError && err.reason === "blocked_artifact_path");
93
- });
94
- it("blocks .patchwarden internal paths", () => {
95
- assert.throws(() => guardDirectWritePath(".patchwarden/tasks/evil.json", repoPath, tempDir), (err) => err instanceof PatchWardenError && err.reason === "internal_patchwarden_path_blocked");
96
- });
97
- it("blocks sensitive files", () => {
98
- assert.throws(() => guardDirectWritePath(".env", repoPath, tempDir), (err) => err instanceof PatchWardenError && err.reason === "sensitive_path_blocked");
99
- assert.throws(() => guardDirectWritePath("config.json", repoPath, tempDir), (err) => err instanceof PatchWardenError && err.reason === "sensitive_path_blocked");
100
- });
101
- it("blocks paths outside repo", () => {
102
- assert.throws(() => guardDirectWritePath("../other-repo/file.ts", repoPath, tempDir), PatchWardenError);
103
- });
104
- });
105
- describe("guardDirectReadPath", () => {
106
- let tempDir;
107
- let repoPath;
108
- beforeEach(() => {
109
- tempDir = mkdtempSync(join(tmpdir(), "pw-directread-"));
110
- repoPath = join(tempDir, "my-repo");
111
- mkdirSync(repoPath, { recursive: true });
112
- mkdirSync(join(repoPath, "src"), { recursive: true });
113
- writeFileSync(join(repoPath, "src", "main.ts"), "console.log('hello');", "utf-8");
114
- });
115
- afterEach(() => {
116
- rmSync(tempDir, { recursive: true, force: true });
117
- });
118
- it("allows reading source files in repo", () => {
119
- const result = guardDirectReadPath("src/main.ts", repoPath, tempDir);
120
- assert.ok(result.startsWith(repoPath));
121
- });
122
- it("blocks .patchwarden internal paths", () => {
123
- assert.throws(() => guardDirectReadPath(".patchwarden/tasks/status.json", repoPath, tempDir), (err) => err instanceof PatchWardenError && err.reason === "internal_patchwarden_path_blocked");
124
- });
125
- it("blocks sensitive files", () => {
126
- assert.throws(() => guardDirectReadPath(".env", repoPath, tempDir), (err) => err instanceof PatchWardenError && err.reason === "sensitive_path_blocked");
127
- });
128
- });
129
- describe("guardDirectPatchSize", () => {
130
- let tempDir;
131
- beforeEach(() => {
132
- tempDir = mkdtempSync(join(tmpdir(), "pw-patchsize-"));
133
- process.env.PATCHWARDEN_CONFIG = "";
134
- });
135
- afterEach(() => {
136
- rmSync(tempDir, { recursive: true, force: true });
137
- delete process.env.PATCHWARDEN_CONFIG;
138
- });
139
- it("allows patches within size limit", () => {
140
- // We can't easily call this without a config, so just test the logic
141
- // guardDirectPatchSize calls getConfig() internally
142
- // We'll test it indirectly by checking it doesn't throw for small sizes
143
- // when config is loaded with defaults
144
- // Skip if no config available
145
- });
146
- it("rejects patches exceeding size limit", () => {
147
- // This test would need config setup; skip for now
148
- });
149
- });
150
- describe("guardDirectFileSize", () => {
151
- it("allows files within size limit", () => {
152
- // Similar to above, needs config
153
- });
154
- });
155
- describe("isBinaryFile", () => {
156
- let tempDir;
157
- beforeEach(() => {
158
- tempDir = mkdtempSync(join(tmpdir(), "pw-binary-"));
159
- });
160
- afterEach(() => {
161
- rmSync(tempDir, { recursive: true, force: true });
162
- });
163
- it("detects binary files by extension", () => {
164
- assert.equal(isBinaryFile("test.exe"), true);
165
- assert.equal(isBinaryFile("test.dll"), true);
166
- assert.equal(isBinaryFile("test.zip"), true);
167
- assert.equal(isBinaryFile("test.png"), true);
168
- assert.equal(isBinaryFile("test.pdf"), true);
169
- assert.equal(isBinaryFile("test.jar"), true);
170
- assert.equal(isBinaryFile("test.pak"), true);
171
- });
172
- it("detects binary files with Windows backslash paths", () => {
173
- assert.equal(isBinaryFile("path\\to\\test.exe"), true);
174
- assert.equal(isBinaryFile("path\\to\\test.dll"), true);
175
- });
176
- it("does not flag text files as binary by extension", () => {
177
- assert.equal(isBinaryFile("test.ts"), false);
178
- assert.equal(isBinaryFile("test.js"), false);
179
- assert.equal(isBinaryFile("test.md"), false);
180
- assert.equal(isBinaryFile("test.json"), false);
181
- assert.equal(isBinaryFile("test.txt"), false);
182
- });
183
- it("detects binary content by null bytes", () => {
184
- const binaryFile = join(tempDir, "test.dat");
185
- // Write a file with null bytes in the first 8KB
186
- const buffer = Buffer.alloc(100, 0x41); // 'A' characters
187
- buffer[50] = 0; // null byte
188
- writeFileSync(binaryFile, buffer);
189
- assert.equal(isBinaryFile(binaryFile), true);
190
- });
191
- it("does not flag text content as binary", () => {
192
- const textFile = join(tempDir, "test.txt");
193
- writeFileSync(textFile, "This is a text file with no null bytes.", "utf-8");
194
- assert.equal(isBinaryFile(textFile), false);
195
- });
196
- it("handles 8KB boundary — text file just under 8KB", () => {
197
- const textFile = join(tempDir, "boundary.txt");
198
- // Write exactly 8192 bytes of text (no null bytes)
199
- const content = "A".repeat(8192);
200
- writeFileSync(textFile, content, "utf-8");
201
- assert.equal(isBinaryFile(textFile), false);
202
- });
203
- it("handles 8KB boundary — null byte at position 8191", () => {
204
- const binaryFile = join(tempDir, "boundary.dat");
205
- const buffer = Buffer.alloc(8192, 0x41);
206
- buffer[8191] = 0;
207
- writeFileSync(binaryFile, buffer);
208
- assert.equal(isBinaryFile(binaryFile), true);
209
- });
210
- it("returns false for non-existent files without binary extension", () => {
211
- assert.equal(isBinaryFile(join(tempDir, "nonexistent.txt")), false);
212
- });
213
- });
@@ -1 +0,0 @@
1
- export {};