claude-code-session-manager 0.86.0 → 0.88.0

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 (335) hide show
  1. package/README.md +26 -53
  2. package/dist/assets/AgentLibrary-BZ6IY2g1.js +3 -0
  3. package/dist/assets/{DataModel-Q4jhl24R.js → DataModel-dAoGAODX.js} +1 -1
  4. package/dist/assets/{History-Cj2FejEo.js → History-CF157HyX.js} +2 -2
  5. package/dist/assets/{Hooks-CaelQI6t.js → Hooks-CCPPipBk.js} +3 -3
  6. package/dist/assets/{HostBilko--v7cMR8I.js → HostBilko-Dlmz2m06.js} +1 -1
  7. package/dist/assets/{Library-DgI9oCCZ.js → Library-BaXcboWX.js} +1 -1
  8. package/dist/assets/{ListDetail-DYUZN-x-.js → ListDetail-CoUCzG5S.js} +1 -1
  9. package/dist/assets/MarkdownEditor-DTg3JlOT.js +1 -0
  10. package/dist/assets/{McpServers-ypCYURh3.js → McpServers-DdjY1sA3.js} +2 -2
  11. package/dist/assets/{Memory-C2qYp-3M.js → Memory-BJ3Jlw0C.js} +4 -4
  12. package/dist/assets/{Panel-Cj2kw-Zv.js → Panel-ygRSgc41.js} +1 -1
  13. package/dist/assets/Permissions-m0KAnccF.js +3 -0
  14. package/dist/assets/{Plugins-C1Vj8_dU.js → Plugins-CJRl6B3Q.js} +2 -2
  15. package/dist/assets/{ProvenanceBadge-DczPNM5U.js → ProvenanceBadge-w8cnlVvW.js} +1 -1
  16. package/dist/assets/{SaveBar-Cd_7U6Gb.js → SaveBar-BpyPcUIk.js} +1 -1
  17. package/dist/assets/Scheduler-D2ppxSfy.js +14 -0
  18. package/dist/assets/{ScopeSwitcher-DVSyI44-.js → ScopeSwitcher-EyuIwt-j.js} +1 -1
  19. package/dist/assets/Settings-BHSGGFEc.js +3 -0
  20. package/dist/assets/{SkillReferenceGraph-CUv1_Q2c.js → SkillReferenceGraph-BNaZ2JCF.js} +1 -1
  21. package/dist/assets/{Skills-C_YHkAy-.js → Skills-Dl3q75QV.js} +2 -2
  22. package/dist/assets/{SystemPrompt-B8R7T9xn.js → SystemPrompt-Bg6F-Cyp.js} +1 -1
  23. package/dist/assets/{TagLibrary-dj9YHWyy.js → TagLibrary-BSutflCy.js} +1 -1
  24. package/dist/assets/{TiptapBody-DnSBUjHE.js → TiptapBody-CXih4xrc.js} +1 -1
  25. package/dist/assets/{Toggle-CjV_BJn6.js → Toggle-2qtUHhA9.js} +1 -1
  26. package/dist/assets/{index-CXFQIPhO.js → index-B3emy2oI.js} +583 -583
  27. package/dist/assets/{index-CDo9xBR9.css → index-BHTX4OTc.css} +1 -1
  28. package/dist/assets/settingsSchema-CdzxaqwM.js +3 -0
  29. package/dist/assets/{whisperWorker-Dbia1OpC.js → whisperWorker-C7ZGQwKg.js} +7 -7
  30. package/dist/index.html +2 -2
  31. package/dist/vad/ort-wasm-simd-threaded.asyncify.mjs +106 -110
  32. package/dist/vad/ort-wasm-simd-threaded.asyncify.wasm +0 -0
  33. package/dist/vad/ort-wasm-simd-threaded.jsep.mjs +98 -98
  34. package/dist/vad/ort-wasm-simd-threaded.jsep.wasm +0 -0
  35. package/dist/vad/ort-wasm-simd-threaded.jspi.mjs +99 -102
  36. package/dist/vad/ort-wasm-simd-threaded.jspi.wasm +0 -0
  37. package/dist/vad/ort-wasm-simd-threaded.mjs +46 -46
  38. package/dist/vad/ort-wasm-simd-threaded.wasm +0 -0
  39. package/package.json +14 -17
  40. package/plugins/CLAUDE.md +61 -0
  41. package/plugins/session-manager-dev/.claude-plugin/plugin.json +1 -1
  42. package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +1 -1
  43. package/plugins/session-manager-dev/skills/ops-sweep/SKILL.md +1 -1
  44. package/scripts/README.md +59 -0
  45. package/scripts/audit-ops-hygiene.cjs +350 -0
  46. package/scripts/hooks/guard-destructive-git.cjs +10 -34
  47. package/scripts/hooks/guard-inline-implementation.cjs +14 -8
  48. package/scripts/hooks/guard-prd-writes.cjs +7 -36
  49. package/scripts/hooks/guard-self-schedule.cjs +175 -0
  50. package/scripts/ops-sweep.cjs +355 -0
  51. package/scripts/scheduler-mcp-server.cjs +35 -71
  52. package/src/main/__tests__/agentModelResolve.test.cjs +100 -9
  53. package/src/main/__tests__/bilkoHost-integration.test.cjs +3 -3
  54. package/src/main/__tests__/broadcastCoalescer.test.cjs +18 -0
  55. package/src/main/__tests__/chat-cancel-terminal.test.cjs +6 -9
  56. package/src/main/__tests__/chat-exit-close-race.test.cjs +3 -3
  57. package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +4 -5
  58. package/src/main/__tests__/chat-queue.test.cjs +2 -2
  59. package/src/main/__tests__/chat-stop-signal.test.cjs +2 -2
  60. package/src/main/__tests__/dep-orphan-archive-health.test.cjs +77 -0
  61. package/src/main/__tests__/dod-batchkey.test.cjs +2 -2
  62. package/src/main/__tests__/dod-drain-hook.test.cjs +2 -2
  63. package/src/main/__tests__/dod-report.test.cjs +2 -2
  64. package/src/main/__tests__/dod-reverify.test.cjs +2 -2
  65. package/src/main/__tests__/epicMint.test.cjs +4 -4
  66. package/src/main/__tests__/exchanges.test.cjs +2 -2
  67. package/src/main/__tests__/extractJson.test.cjs +2 -2
  68. package/src/main/__tests__/files-reject-credentials.test.cjs +1 -1
  69. package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +556 -0
  70. package/src/main/__tests__/health-build-freshness.test.cjs +39 -0
  71. package/src/main/__tests__/health-delegation-chain.test.cjs +17 -2
  72. package/src/main/__tests__/health-queue-dispatch.test.cjs +58 -7
  73. package/src/main/__tests__/health-tick-liveness.test.cjs +11 -3
  74. package/src/main/__tests__/health-usage-poller.test.cjs +70 -23
  75. package/src/main/__tests__/historyRollup.test.cjs +2 -2
  76. package/src/main/__tests__/kg-augment.test.cjs +2 -2
  77. package/src/main/__tests__/mcpStatus.test.cjs +2 -2
  78. package/src/main/__tests__/memoryAggregate.test.cjs +1 -1
  79. package/src/main/__tests__/memoryStale.test.cjs +1 -1
  80. package/src/main/__tests__/needsReviewLedger.test.cjs +162 -0
  81. package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +28 -4
  82. package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +48 -4
  83. package/src/main/__tests__/prd-group-allocator.test.cjs +2 -2
  84. package/src/main/__tests__/prdAdminRouteParity.test.cjs +2 -0
  85. package/src/main/__tests__/prdAdminRoutes.test.cjs +14 -2
  86. package/src/main/__tests__/prdAuthoringSeed.test.cjs +39 -0
  87. package/src/main/__tests__/prdCreateDisposition.test.cjs +201 -0
  88. package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +125 -0
  89. package/src/main/__tests__/prdLocations.test.cjs +100 -2
  90. package/src/main/__tests__/prdLocationsArchived.test.cjs +50 -1
  91. package/src/main/__tests__/prdSetDisposition.test.cjs +222 -0
  92. package/src/main/__tests__/proc-role-env.test.cjs +125 -0
  93. package/src/main/__tests__/procname-claude-spawn-sites.test.cjs +304 -0
  94. package/src/main/__tests__/procname-sm-processes.test.cjs +127 -0
  95. package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +81 -401
  96. package/src/main/__tests__/projectPages.test.cjs +63 -149
  97. package/src/main/__tests__/queue-health-verdict.test.cjs +170 -0
  98. package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +117 -20
  99. package/src/main/__tests__/queue-starvation-per-project.test.cjs +14 -2
  100. package/src/main/__tests__/queueHistory.test.cjs +65 -2
  101. package/src/main/__tests__/rateLimitPollerStreak.test.cjs +38 -3
  102. package/src/main/__tests__/reconcileTiming.test.cjs +135 -0
  103. package/src/main/__tests__/runVerify-landed-commit-outranks.test.cjs +181 -0
  104. package/src/main/__tests__/runVerify.test.cjs +5 -5
  105. package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +3 -3
  106. package/src/main/__tests__/scheduleJobTransitions.test.cjs +103 -3
  107. package/src/main/__tests__/scheduler-adopted-run-supervision.test.cjs +143 -0
  108. package/src/main/__tests__/scheduler-autofix-select.test.cjs +2 -2
  109. package/src/main/__tests__/scheduler-autopromote.test.cjs +2 -2
  110. package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +3 -5
  111. package/src/main/__tests__/scheduler-boot-orphans.test.cjs +79 -98
  112. package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +121 -0
  113. package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +43 -0
  114. package/src/main/__tests__/scheduler-default-eligible-heal.test.cjs +161 -0
  115. package/src/main/__tests__/scheduler-dispatch-loop.test.cjs +58 -0
  116. package/src/main/__tests__/scheduler-epic-digest.test.cjs +3 -5
  117. package/src/main/__tests__/scheduler-force-tick-outcome.test.cjs +2 -2
  118. package/src/main/__tests__/scheduler-gate-shadow.test.cjs +119 -0
  119. package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +390 -0
  120. package/src/main/__tests__/scheduler-heartbeat-payload.test.cjs +80 -0
  121. package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +25 -18
  122. package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +4 -4
  123. package/src/main/__tests__/scheduler-job-budget.test.cjs +172 -0
  124. package/src/main/__tests__/scheduler-launch-failure.test.cjs +3 -5
  125. package/src/main/__tests__/scheduler-looks-done.test.cjs +119 -10
  126. package/src/main/__tests__/scheduler-manual-pause.test.cjs +118 -0
  127. package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +26 -3
  128. package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +164 -0
  129. package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +17 -2
  130. package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +3 -5
  131. package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +41 -6
  132. package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +62 -6
  133. package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +3 -5
  134. package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +182 -3
  135. package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +87 -0
  136. package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +100 -0
  137. package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +3 -3
  138. package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +4 -4
  139. package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +110 -0
  140. package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +168 -0
  141. package/src/main/__tests__/scheduler-starve-escalation.test.cjs +18 -8
  142. package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +245 -0
  143. package/src/main/__tests__/scheduler-supervisor-record.test.cjs +81 -0
  144. package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +2 -2
  145. package/src/main/__tests__/scheduler-tick-wedge.test.cjs +172 -0
  146. package/src/main/__tests__/scheduler-transient-failure.test.cjs +2 -2
  147. package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +51 -11
  148. package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +3 -5
  149. package/src/main/__tests__/usageSingleFlight.test.cjs +157 -0
  150. package/src/main/__tests__/workTypeLibrary.test.cjs +1 -1
  151. package/src/main/bilkoHost.cjs +16 -7
  152. package/src/main/build-info.json +8 -0
  153. package/src/main/chatRunner.cjs +12 -7
  154. package/src/main/config.cjs +2 -3
  155. package/src/main/docEdit.cjs +4 -2
  156. package/src/main/health.cjs +268 -52
  157. package/src/main/heapSnapshot.cjs +2 -2
  158. package/src/main/historyAggregator.cjs +6 -1
  159. package/src/main/index.cjs +152 -50
  160. package/src/main/ipcSchemas.cjs +53 -17
  161. package/src/main/lib/__tests__/active-sessions.test.cjs +251 -0
  162. package/src/main/lib/__tests__/auditLog.test.cjs +38 -0
  163. package/src/main/lib/__tests__/bootSelfHeal.test.cjs +107 -0
  164. package/src/main/lib/__tests__/buildIdentity.test.cjs +121 -0
  165. package/src/main/lib/__tests__/cwdClassify.test.cjs +111 -0
  166. package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +95 -0
  167. package/src/main/lib/__tests__/delegationReadiness.test.cjs +323 -44
  168. package/src/main/lib/__tests__/dispatchLoop.test.cjs +63 -0
  169. package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +239 -0
  170. package/src/main/lib/__tests__/gateFixtures.json +20 -0
  171. package/src/main/lib/__tests__/gitWorktree.test.cjs +97 -1
  172. package/src/main/lib/__tests__/guardShims.test.cjs +151 -0
  173. package/src/main/lib/__tests__/instanceLock.test.cjs +93 -7
  174. package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +78 -0
  175. package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +11 -0
  176. package/src/main/lib/__tests__/localAdminHttp.test.cjs +1 -1
  177. package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +6 -1
  178. package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +5 -5
  179. package/src/main/lib/__tests__/prdDisposition.test.cjs +224 -0
  180. package/src/main/lib/__tests__/procIdentity.test.cjs +119 -0
  181. package/src/main/lib/__tests__/procName.test.cjs +92 -0
  182. package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +67 -0
  183. package/src/main/lib/__tests__/reaperHelpers.test.cjs +179 -1
  184. package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +6 -7
  185. package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +37 -204
  186. package/src/main/lib/__tests__/schedulerPaths.test.cjs +226 -0
  187. package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +94 -0
  188. package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +56 -0
  189. package/src/main/lib/__tests__/sessionSlots.test.cjs +45 -0
  190. package/src/main/lib/__tests__/telemetryBacklog.test.cjs +1 -1
  191. package/src/main/lib/__tests__/upgradeDrain.test.cjs +130 -0
  192. package/src/main/lib/__tests__/usageCircuit.test.cjs +285 -0
  193. package/src/main/lib/__tests__/watchdog-helpers.test.cjs +375 -0
  194. package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +266 -0
  195. package/src/main/lib/activeIndexRebuild.cjs +1 -3
  196. package/{scripts → src/main}/lib/activeSessions.cjs +67 -89
  197. package/src/main/lib/adoptedRunSupervisor.cjs +136 -0
  198. package/src/main/lib/agentModelResolve.cjs +94 -24
  199. package/src/main/lib/agentPersonaSchema.cjs +2 -2
  200. package/src/main/lib/auditLog.cjs +30 -5
  201. package/src/main/lib/bootSelfHeal.cjs +88 -0
  202. package/src/main/lib/buildIdentity.cjs +113 -0
  203. package/src/main/lib/classifyPromptTicket.cjs +3 -2
  204. package/src/main/lib/claudeBin.cjs +37 -2
  205. package/src/main/lib/cleanEnv.cjs +27 -1
  206. package/src/main/lib/credentials.cjs +4 -2
  207. package/src/main/lib/cwdClassify.cjs +185 -0
  208. package/src/main/lib/definitionOfDone.cjs +296 -52
  209. package/src/main/lib/delegationReadiness.cjs +290 -225
  210. package/src/main/lib/dispatchLoop.cjs +40 -0
  211. package/src/main/lib/effectiveModelInfo.cjs +332 -0
  212. package/src/main/lib/ephemeralCwd.cjs +7 -28
  213. package/src/main/lib/epicMint.cjs +3 -3
  214. package/src/main/lib/gitWorktree.cjs +136 -19
  215. package/src/main/lib/guardShims.cjs +156 -0
  216. package/src/main/lib/historyRollup.cjs +6 -7
  217. package/src/main/lib/instanceLock.cjs +32 -5
  218. package/src/main/lib/jobDirtFilter.cjs +7 -2
  219. package/src/main/lib/jobSupervisorRecord.cjs +147 -0
  220. package/src/main/lib/jobWorktreeBootLive.cjs +9 -5
  221. package/src/main/lib/launchFailure.cjs +2 -1
  222. package/src/main/lib/localAdminHttp.cjs +9 -27
  223. package/src/main/lib/mcpToolCatalog.cjs +33 -78
  224. package/src/main/lib/needsReviewLedger.cjs +205 -0
  225. package/src/main/lib/opsErrorLog.cjs +1 -1
  226. package/src/main/lib/opsOwnership.cjs +27 -19
  227. package/src/main/lib/prdAuthoringSeed.cjs +36 -0
  228. package/src/main/lib/prdCreate.cjs +56 -1
  229. package/src/main/lib/prdDisposition.cjs +199 -0
  230. package/src/main/lib/prdFrontmatter.cjs +8 -2
  231. package/src/main/lib/prdLocations.cjs +214 -45
  232. package/src/main/lib/procIdentity.cjs +126 -0
  233. package/src/main/lib/procName.cjs +98 -0
  234. package/src/main/lib/projectHomeAdminRoutes.cjs +56 -327
  235. package/src/main/lib/projectRootResolve.cjs +1 -1
  236. package/src/main/lib/queueHistory.cjs +27 -8
  237. package/src/main/lib/queueStore.cjs +63 -38
  238. package/src/main/lib/quietMachineLease.cjs +19 -2
  239. package/src/main/lib/reaperHelpers.cjs +181 -15
  240. package/src/main/lib/reservationExpiry.cjs +30 -0
  241. package/src/main/lib/runClaudeP.cjs +4 -2
  242. package/src/main/lib/runLogRetention.cjs +3 -2
  243. package/src/main/lib/scheduleJobSchema.cjs +10 -2
  244. package/src/main/lib/scheduleJobTransitions.cjs +60 -0
  245. package/src/main/lib/schedulerBatch.cjs +27 -2
  246. package/src/main/lib/schedulerConfig.cjs +24 -0
  247. package/src/main/lib/schedulerPaths.cjs +175 -0
  248. package/src/main/lib/schedulerRuntimeState.cjs +59 -0
  249. package/src/main/lib/sessionSlots.cjs +47 -9
  250. package/src/main/lib/smProcNames.cjs +51 -0
  251. package/src/main/lib/upgradeDrain.cjs +188 -0
  252. package/src/main/lib/usageCircuit.cjs +206 -0
  253. package/{scripts → src/main}/lib/watchdogHelpers.cjs +71 -38
  254. package/src/main/lib/withTimeout.cjs +33 -0
  255. package/src/main/mcpStatus.cjs +4 -2
  256. package/src/main/pluginInstall.cjs +6 -2
  257. package/src/main/projectPages.cjs +41 -145
  258. package/src/main/pty.cjs +8 -1
  259. package/src/main/queueOps.cjs +10 -10
  260. package/src/main/runVerify.cjs +69 -3
  261. package/src/main/scheduler/prdParser.cjs +13 -0
  262. package/src/main/scheduler.cjs +2841 -482
  263. package/src/main/seedAgentPersonas.cjs +1 -1
  264. package/src/main/seedDevPlugin.cjs +1 -1
  265. package/src/main/seedSchedulerMcp.cjs +7 -6
  266. package/src/main/seedStatus.cjs +1 -1
  267. package/src/main/supervisor.cjs +5 -3
  268. package/src/main/templates/PRD_AUTHORING.md +50 -0
  269. package/src/main/usage.cjs +140 -58
  270. package/src/preload/api.d.ts +110 -65
  271. package/src/preload/index.cjs +12 -4
  272. package/src/seed/agents/project-home-builder.md +31 -48
  273. package/dist/assets/AgentLibrary-DTFL7y8G.js +0 -3
  274. package/dist/assets/MarkdownEditor-DCIubYWf.js +0 -1
  275. package/dist/assets/Permissions-BiYZNGYW.js +0 -3
  276. package/dist/assets/Scheduler-DcLBiJBq.js +0 -14
  277. package/dist/assets/Settings-Cv-pRyms.js +0 -3
  278. package/dist/assets/settingsSchema-BJVciriw.js +0 -3
  279. package/screenshots/.gitkeep +0 -0
  280. package/screenshots/README-screenshots.md +0 -13
  281. package/scripts/project-pages-logic/dist/logic.cjs +0 -4709
  282. package/scripts/render-project-pages/dist/renderer.cjs +0 -18900
  283. package/scripts/render-project-pages.cjs +0 -70
  284. package/scripts/validate-project-pages-summary.cjs +0 -62
  285. package/src/main/lib/projectPageSummarySchema.cjs +0 -181
  286. package/src/main/teams.cjs +0 -95
  287. package/src/main/templates/project-pages-catalog.json +0 -741
  288. package/src/main/templates/project-pages-default-home.html +0 -123
  289. package/src/main/templates/project-pages-pipeline.md +0 -417
  290. package/src/seed/prompts/code-review/ac-coverage-check.md +0 -8
  291. package/src/seed/prompts/code-review/correctness-only.md +0 -8
  292. package/src/seed/prompts/code-review/full-spectrum-high.md +0 -8
  293. package/src/seed/prompts/code-review/hallucination-check.md +0 -8
  294. package/src/seed/prompts/code-review/public-api-compat.md +0 -8
  295. package/src/seed/prompts/code-review/readability-naming.md +0 -8
  296. package/src/seed/prompts/debugging/bug-as-failing-test.md +0 -8
  297. package/src/seed/prompts/debugging/git-bisect-regression.md +0 -8
  298. package/src/seed/prompts/debugging/instrument-intermittent-bug.md +0 -8
  299. package/src/seed/prompts/debugging/localize-pipeline-failure.md +0 -8
  300. package/src/seed/prompts/debugging/reproduce-then-diagnose.md +0 -8
  301. package/src/seed/prompts/documentation/adr-from-change.md +0 -8
  302. package/src/seed/prompts/documentation/module-readme.md +0 -8
  303. package/src/seed/prompts/documentation/onboarding-plan.md +0 -8
  304. package/src/seed/prompts/documentation/refresh-claude-md.md +0 -8
  305. package/src/seed/prompts/documentation/tsdoc-public-exports.md +0 -8
  306. package/src/seed/prompts/git-pr/conventional-commit.md +0 -8
  307. package/src/seed/prompts/git-pr/draft-pr-title-body.md +0 -8
  308. package/src/seed/prompts/git-pr/pre-commit-safety-sweep.md +0 -8
  309. package/src/seed/prompts/git-pr/release-notes-block.md +0 -8
  310. package/src/seed/prompts/git-pr/split-large-pr.md +0 -8
  311. package/src/seed/prompts/performance/bundle-startup-audit.md +0 -8
  312. package/src/seed/prompts/performance/complexity-audit.md +0 -8
  313. package/src/seed/prompts/performance/cpu-profile-hot-path.md +0 -8
  314. package/src/seed/prompts/performance/db-query-plan-review.md +0 -8
  315. package/src/seed/prompts/performance/memory-leak-hunt.md +0 -8
  316. package/src/seed/prompts/qa/api-contract-tests.md +0 -8
  317. package/src/seed/prompts/qa/e2e-critical-path.md +0 -8
  318. package/src/seed/prompts/qa/failing-test-for-bug.md +0 -8
  319. package/src/seed/prompts/qa/find-missing-test-coverage.md +0 -8
  320. package/src/seed/prompts/qa/stabilize-flaky-test.md +0 -8
  321. package/src/seed/prompts/qa/tdd-red-first.md +0 -8
  322. package/src/seed/prompts/qa/visual-regression-review.md +0 -8
  323. package/src/seed/prompts/qa/wcag-axe-scan.md +0 -8
  324. package/src/seed/prompts/refactoring/dead-code-sweep.md +0 -8
  325. package/src/seed/prompts/refactoring/extract-duplicated-pattern.md +0 -8
  326. package/src/seed/prompts/refactoring/modernize-legacy-file.md +0 -8
  327. package/src/seed/prompts/refactoring/reduce-cyclomatic-complexity.md +0 -8
  328. package/src/seed/prompts/refactoring/tighten-module-boundaries.md +0 -8
  329. package/src/seed/prompts/security/authz-audit.md +0 -8
  330. package/src/seed/prompts/security/crypto-correctness.md +0 -8
  331. package/src/seed/prompts/security/cwe-top-25-hunt.md +0 -8
  332. package/src/seed/prompts/security/dependency-audit.md +0 -8
  333. package/src/seed/prompts/security/ipc-boundary-hardening.md +0 -8
  334. package/src/seed/prompts/security/owasp-top-10-staged-diff.md +0 -8
  335. package/src/seed/prompts/security/secret-credential-scan.md +0 -8
@@ -0,0 +1,56 @@
1
+ // schedulerRuntimeState.cjs + quietMachineLease.cjs — fail-closed expiry.
2
+ const assert = require('node:assert');
3
+ const state = require('../schedulerRuntimeState.cjs');
4
+ const lease = require('../quietMachineLease.cjs');
5
+
6
+ const NOW = 10_000_000;
7
+ const GRACE = 10 * 60 * 1000;
8
+
9
+ beforeEach(() => { state.__resetForTests(); lease.__resetForTests(); });
10
+ afterEach(() => { state.__resetForTests(); lease.__resetForTests(); });
11
+
12
+ test('lease: expires only when holder not running AND past grace', () => {
13
+ lease.acquire('q', { claimedAt: NOW - GRACE - 1 });
14
+ assert.equal(lease.expireDead({ liveSlugs: new Set(['q']), now: NOW, graceMs: GRACE }), false, 'running row keeps it');
15
+ assert.equal(lease.isHeld(), true);
16
+ assert.equal(lease.expireDead({ liveSlugs: new Set(), now: NOW, graceMs: GRACE }), true);
17
+ assert.equal(lease.isHeld(), false);
18
+ });
19
+
20
+ test('lease: inside the grace window or claimed after the pass began is kept', () => {
21
+ lease.acquire('q', { claimedAt: NOW - 1000 });
22
+ assert.equal(lease.expireDead({ liveSlugs: new Set(), now: NOW, graceMs: GRACE }), false);
23
+ lease.release('q');
24
+ lease.acquire('q', { claimedAt: NOW + 10 });
25
+ assert.equal(lease.expireDead({ liveSlugs: new Set(), now: NOW, graceMs: GRACE }), false);
26
+ assert.equal(lease.isHeld(), true);
27
+ });
28
+
29
+ test('investigations: set semantics, no double reservation, idempotent release', () => {
30
+ assert.equal(state.reserveInvestigation('a'), true);
31
+ assert.equal(state.reserveInvestigation('a'), false);
32
+ assert.equal(state.investigationCount(), 1);
33
+ assert.equal(state.releaseInvestigation('a'), true);
34
+ assert.equal(state.releaseInvestigation('a'), false);
35
+ assert.equal(state.investigationCount(), 0);
36
+ });
37
+
38
+ test('investigations: dead stamped pid with no investigating row expires; live pid/row survives', () => {
39
+ state.reserveInvestigation('dead', { claimedAt: NOW - 1000 });
40
+ state.stampInvestigationPid('dead', 111);
41
+ state.reserveInvestigation('alive', { claimedAt: NOW - 1000 });
42
+ state.stampInvestigationPid('alive', 222);
43
+ state.reserveInvestigation('rowlive', { claimedAt: NOW - 2 * GRACE });
44
+ const out = state.expireDeadInvestigations({
45
+ liveSlugs: new Set(['rowlive']), pidAlive: (p) => p === 222, now: NOW, graceMs: GRACE,
46
+ });
47
+ assert.deepEqual(out, ['dead']);
48
+ assert.equal(state.investigationCount(), 2);
49
+ });
50
+
51
+ test('investigations: pidless reservation expires past grace only', () => {
52
+ state.reserveInvestigation('old', { claimedAt: NOW - GRACE - 1 });
53
+ state.reserveInvestigation('fresh', { claimedAt: NOW - 1000 });
54
+ const out = state.expireDeadInvestigations({ liveSlugs: new Set(), pidAlive: () => false, now: NOW, graceMs: GRACE });
55
+ assert.deepEqual(out, ['old']);
56
+ });
@@ -97,3 +97,48 @@ test('snapshot names holders for attribution', () => {
97
97
  assert.equal(snap.inUse, 2);
98
98
  assert.deepEqual(snap.holders.map((h) => h.owner).sort(), ['chat:tab-1', 'scheduler:42-fix-thing']);
99
99
  });
100
+
101
+ // ── expireDead: fail-closed release of provably-dead scheduler reservations ──
102
+ const NOW = 10_000_000;
103
+ const GRACE = 10 * 60 * 1000;
104
+
105
+ test('expireDead: stamped dead pid with no live row is released', () => {
106
+ const t = slots.acquire('scheduler:a', { claimedAt: NOW - 1000 });
107
+ slots.stampPid(t, 4242);
108
+ const out = slots.expireDead({ liveSlugs: new Set(), pidAlive: () => false, now: NOW, graceMs: GRACE });
109
+ assert.deepEqual(out, ['scheduler:a']);
110
+ assert.equal(slots.inUse(), 0);
111
+ });
112
+
113
+ test('expireDead: a live pid or a live row keeps the token', () => {
114
+ const t1 = slots.acquire('scheduler:a', { claimedAt: NOW - 2 * GRACE });
115
+ slots.stampPid(t1, 4242);
116
+ const t2 = slots.acquire('scheduler:b', { claimedAt: NOW - 2 * GRACE });
117
+ slots.expireDead({ liveSlugs: new Set(['b']), pidAlive: () => true, now: NOW, graceMs: GRACE });
118
+ assert.equal(slots.inUse(), 2, 'live pid (a) and live row (b) both survive');
119
+ assert.ok(t1 && t2);
120
+ });
121
+
122
+ test('expireDead: pidless token expires only past the grace window', () => {
123
+ slots.acquire('scheduler:old', { claimedAt: NOW - GRACE - 1 });
124
+ slots.acquire('scheduler:fresh', { claimedAt: NOW - GRACE + 1000 });
125
+ const out = slots.expireDead({ liveSlugs: new Set(), pidAlive: () => false, now: NOW, graceMs: GRACE });
126
+ assert.deepEqual(out, ['scheduler:old']);
127
+ assert.equal(slots.inUse(), 1);
128
+ });
129
+
130
+ test('expireDead: chat and project-brief tokens are never touched', () => {
131
+ slots.acquire('chat:tab1', { claimedAt: 0 });
132
+ slots.acquire('project-brief:x', { claimedAt: 0 });
133
+ const out = slots.expireDead({ liveSlugs: new Set(), pidAlive: () => false, now: NOW, graceMs: GRACE });
134
+ assert.deepEqual(out, []);
135
+ assert.equal(slots.inUse(), 2);
136
+ });
137
+
138
+ test('expireDead: a mid-launch reservation (claimed after the pass began) survives even with a dead pid', () => {
139
+ const t = slots.acquire('scheduler:mid', { claimedAt: NOW + 5 });
140
+ slots.stampPid(t, 4242);
141
+ const out = slots.expireDead({ liveSlugs: new Set(), pidAlive: () => false, now: NOW, graceMs: GRACE });
142
+ assert.deepEqual(out, []);
143
+ assert.equal(slots.inUse(), 1);
144
+ });
@@ -183,7 +183,7 @@ test('duplicate cwds across tabs are visited once; a missing cwd is skipped with
183
183
 
184
184
  test('an ephemeral epic-worktree cwd is never scanned as its own project', async () => {
185
185
  const home = await mkHome();
186
- const ephemeral = path.join(os.tmpdir(), 'session-manager-epic-worktrees', 'abc123', 'psess-xyz');
186
+ const ephemeral = path.join(process.env.SM_WORKTREE_ROOT || os.tmpdir(), 'session-manager-epic-worktrees', 'abc123', 'psess-xyz');
187
187
  const opsDir = path.join(ephemeral, 'session-manager-operations', 'logs');
188
188
  fs.mkdirSync(opsDir, { recursive: true });
189
189
  fs.writeFileSync(path.join(opsDir, `errors-${recentDateStr()}.jsonl`), JSON.stringify(errLine(1)) + '\n');
@@ -0,0 +1,130 @@
1
+ 'use strict';
2
+
3
+ // Run: timeout 120 npx vitest run src/main/lib/__tests__/upgradeDrain.test.cjs
4
+
5
+ const assert = require('node:assert/strict');
6
+ const fs = require('node:fs');
7
+ const os = require('node:os');
8
+ const path = require('node:path');
9
+ const ud = require('../upgradeDrain.cjs');
10
+
11
+ const T0 = Date.parse('2026-09-18T00:00:00Z');
12
+ const req = (extra = {}) => ({ reason: 'r', requestedBy: 'u', requestedAt: new Date(T0).toISOString(), ...extra });
13
+ const ev = (o) => ud.evaluateDrain({ now: T0 + 1000, ...o }).action;
14
+
15
+ function tmp() { return fs.mkdtempSync(path.join(os.tmpdir(), 'upgrade-drain-')); }
16
+
17
+ describe('evaluateDrain state machine', () => {
18
+ test('no request → none', () => {
19
+ assert.equal(ev({ request: null, queueSnapshot: { running: 2 }, drainState: null }), 'none');
20
+ });
21
+ test('pending request, drain not active → pause (even when idle)', () => {
22
+ assert.equal(ev({ request: req(), queueSnapshot: { running: 0, investigating: 0 }, drainState: null }), 'pause');
23
+ assert.equal(ev({ request: req(), queueSnapshot: { running: 3 }, drainState: null }), 'pause');
24
+ });
25
+ test('drain active + running or investigating → wait', () => {
26
+ assert.equal(ev({ request: req(), queueSnapshot: { running: 1, investigating: 0 }, drainState: { active: true } }), 'wait');
27
+ assert.equal(ev({ request: req(), queueSnapshot: { running: 0, investigating: 1 }, drainState: { active: true } }), 'wait');
28
+ });
29
+ test('drain active + zero busy → restart', () => {
30
+ assert.equal(ev({ request: req(), queueSnapshot: { running: 0, investigating: 0 }, drainState: { active: true } }), 'restart');
31
+ });
32
+ test('deadline converts to abort, default 4 h', () => {
33
+ const now = T0 + ud.DEFAULT_DRAIN_DEADLINE_MS;
34
+ assert.equal(ud.DEFAULT_DRAIN_DEADLINE_MS, 4 * 60 * 60_000);
35
+ assert.equal(ud.evaluateDrain({ request: req(), queueSnapshot: { running: 1 }, drainState: { active: true }, now }).action, 'abort');
36
+ assert.equal(ud.evaluateDrain({ request: req(), queueSnapshot: { running: 1 }, drainState: { active: true }, now: now - 1 }).action, 'wait');
37
+ assert.equal(ud.evaluateDrain({ request: req(), queueSnapshot: {}, drainState: { active: true }, now: T0 + 500, deadlineMs: 100 }).action, 'abort');
38
+ });
39
+ test('completed request → none; stale drain without request → abort', () => {
40
+ assert.equal(ev({ request: req({ drainCompletedAt: 'x' }), queueSnapshot: {}, drainState: { active: true } }), 'none');
41
+ assert.equal(ev({ request: null, queueSnapshot: {}, drainState: { active: true } }), 'abort');
42
+ });
43
+ test('a rate-limit pause during a drain does not change the verdict: drain is its own field', () => {
44
+ // evaluateDrain never sees state.paused; a rate_limit pause overwriting
45
+ // `paused` leaves the drain field (and the verdict) untouched.
46
+ const state = { paused: { reason: 'rate_limit' }, drain: { active: true } };
47
+ assert.equal(ev({ request: req(), queueSnapshot: { running: 0, investigating: 0 }, drainState: state.drain }), 'restart');
48
+ assert.equal(ud.effectivePaused(state).reason, 'rate_limit');
49
+ assert.equal(ud.effectivePaused({ paused: null, drain: { active: true } }).reason, 'drain-for-upgrade');
50
+ assert.equal(ud.effectivePaused({ paused: null, drain: null }), null);
51
+ });
52
+ });
53
+
54
+ describe('request file lifecycle', () => {
55
+ test('requestRestart writes atomically and is idempotent while pending', () => {
56
+ const dir = tmp();
57
+ try {
58
+ const file = path.join(dir, 'restart-request.json');
59
+ const a = ud.requestRestart({ reason: 'x', requestedBy: 'user' }, { file, now: T0 });
60
+ const b = ud.requestRestart({ reason: 'y', requestedBy: 'auto' }, { file, now: T0 + 5000 });
61
+ assert.deepEqual(b, a, 'a second request must not reset the deadline');
62
+ assert.deepEqual(fs.readdirSync(dir), ['restart-request.json'], 'no tmp file left behind');
63
+ assert.equal(ud.readRestartRequest(file).requestedBy, 'user');
64
+ } finally { fs.rmSync(dir, { recursive: true, force: true }); }
65
+ });
66
+ test('stampDrainCompleted marks it; a new request after completion starts fresh', () => {
67
+ const dir = tmp();
68
+ try {
69
+ const file = path.join(dir, 'restart-request.json');
70
+ ud.requestRestart({ reason: 'x', requestedBy: 'u' }, { file, now: T0 });
71
+ assert.ok(ud.stampDrainCompleted(T0 + 1, file).drainCompletedAt);
72
+ const fresh = ud.requestRestart({ reason: 'again', requestedBy: 'u' }, { file, now: T0 + 9 });
73
+ assert.equal(fresh.reason, 'again');
74
+ assert.equal(fresh.drainCompletedAt, undefined);
75
+ } finally { fs.rmSync(dir, { recursive: true, force: true }); }
76
+ });
77
+ test('abort retires the request file', () => {
78
+ const dir = tmp();
79
+ try {
80
+ const file = path.join(dir, 'restart-request.json');
81
+ ud.requestRestart({ reason: 'x', requestedBy: 'u' }, { file, now: T0 });
82
+ ud.retireRestartRequest(file);
83
+ assert.equal(ud.readRestartRequest(file), null);
84
+ ud.retireRestartRequest(file); // absent → no throw
85
+ // With the request gone, a re-evaluation is 'none' — no abort flap.
86
+ assert.equal(ev({ request: ud.readRestartRequest(file), queueSnapshot: {}, drainState: null }), 'none');
87
+ } finally { fs.rmSync(dir, { recursive: true, force: true }); }
88
+ });
89
+ });
90
+
91
+ describe('boot', () => {
92
+ test('clears a leftover drain whose request is complete or gone; keeps a pending one', () => {
93
+ assert.equal(ud.bootDrainAction({ drainState: { active: true }, request: req({ drainCompletedAt: 'x' }) }), 'clear');
94
+ assert.equal(ud.bootDrainAction({ drainState: { active: true }, request: null }), 'clear');
95
+ assert.equal(ud.bootDrainAction({ drainState: { active: true }, request: req() }), 'keep');
96
+ assert.equal(ud.bootDrainAction({ drainState: null, request: req() }), 'none');
97
+ });
98
+ });
99
+
100
+ describe('automatic trigger', () => {
101
+ test('only when both shas are known and differ — never from npm', () => {
102
+ assert.equal(ud.installedBuildDiffers({ running: 'aaa', installed: 'bbb' }), true);
103
+ assert.equal(ud.installedBuildDiffers({ running: 'aaa', installed: 'aaa' }), false);
104
+ assert.equal(ud.installedBuildDiffers({ running: null, installed: 'bbb' }), false);
105
+ assert.equal(ud.installedBuildDiffers({ running: 'aaa', installed: null }), false);
106
+ });
107
+ });
108
+
109
+ describe('queueBusyCount', () => {
110
+ test('counts running + investigating only', () => {
111
+ const jobs = [{ status: 'running' }, { status: 'investigating' }, { status: 'pending' }, { status: 'needs_review' }];
112
+ assert.equal(ud.queueBusyCount(jobs), 2);
113
+ assert.equal(ud.queueBusyCount(jobs, 1), 3);
114
+ });
115
+ });
116
+
117
+ describe('restarting marker', () => {
118
+ test('active until expiry, then lapses', () => {
119
+ const dir = tmp();
120
+ try {
121
+ const file = path.join(dir, 'restarting.json');
122
+ assert.equal(ud.isRestartingMarkerActive({ file, now: T0 }), false);
123
+ ud.markRestarting({ file, now: T0, ttlMs: 1000 });
124
+ assert.equal(ud.isRestartingMarkerActive({ file, now: T0 + 999 }), true);
125
+ assert.equal(ud.isRestartingMarkerActive({ file, now: T0 + 1000 }), false);
126
+ ud.clearRestartingMarker(file);
127
+ assert.equal(ud.isRestartingMarkerActive({ file, now: T0 }), false);
128
+ } finally { fs.rmSync(dir, { recursive: true, force: true }); }
129
+ });
130
+ });
@@ -0,0 +1,285 @@
1
+ /**
2
+ * usageCircuit.test.cjs — breaker transitions, jitter bounds, bindingWindow
3
+ * fallback, reset freshness, degraded-budget floor/cap, singleFlight dedup.
4
+ *
5
+ * Run: timeout 300 npx vitest run src/main/lib/__tests__/usageCircuit.test.cjs
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ import { test } from 'vitest';
11
+ const assert = require('node:assert/strict');
12
+ const {
13
+ createUsageCircuit,
14
+ backoffWithJitter,
15
+ isResetFresh,
16
+ bindingWindow,
17
+ degradedBudget,
18
+ singleFlight,
19
+ } = require('../usageCircuit.cjs');
20
+
21
+ test('state machine: closed -> open after 3 consecutive failures', () => {
22
+ let nowMs = 0;
23
+ const circuit = createUsageCircuit({ now: () => nowMs });
24
+ assert.strictEqual(circuit.state(), 'closed');
25
+ circuit.recordFailure('meter_rate_limited');
26
+ assert.strictEqual(circuit.state(), 'closed');
27
+ circuit.recordFailure('meter_rate_limited');
28
+ assert.strictEqual(circuit.state(), 'closed');
29
+ circuit.recordFailure('meter_rate_limited');
30
+ assert.strictEqual(circuit.state(), 'open');
31
+ });
32
+
33
+ test('open -> half_open after the backoff interval elapses, exactly one probe granted', () => {
34
+ let nowMs = 0;
35
+ const circuit = createUsageCircuit({ now: () => nowMs });
36
+ circuit.recordFailure('a');
37
+ circuit.recordFailure('a');
38
+ circuit.recordFailure('a');
39
+ assert.strictEqual(circuit.state(), 'open');
40
+
41
+ nowMs += 10_000; // still inside the (>=15s) minimum backoff window
42
+ assert.strictEqual(circuit.state(), 'open');
43
+
44
+ nowMs += 60 * 60 * 1000; // well past any possible backoff interval
45
+ assert.strictEqual(circuit.state(), 'half_open');
46
+ // Re-querying before the probe's result is recorded must not grant a second probe.
47
+ assert.strictEqual(circuit.state(), 'half_open');
48
+ });
49
+
50
+ test('half_open -> closed on a successful probe, resetting backoff', () => {
51
+ let nowMs = 0;
52
+ const circuit = createUsageCircuit({ now: () => nowMs });
53
+ circuit.recordFailure('a');
54
+ circuit.recordFailure('a');
55
+ circuit.recordFailure('a');
56
+ nowMs += 60 * 60 * 1000;
57
+ assert.strictEqual(circuit.state(), 'half_open');
58
+ circuit.recordSuccess({ five_hour: { utilization: 10, resets_at: null } });
59
+ assert.strictEqual(circuit.state(), 'closed');
60
+ });
61
+
62
+ test('half_open -> open on a failed probe, with backoff doubled (probe takes longer to reopen)', () => {
63
+ let nowMs = 0;
64
+ const circuit = createUsageCircuit({ now: () => nowMs });
65
+ circuit.recordFailure('a');
66
+ circuit.recordFailure('a');
67
+ circuit.recordFailure('a');
68
+ nowMs += 60 * 60 * 1000;
69
+ assert.strictEqual(circuit.state(), 'half_open');
70
+ circuit.recordFailure('a');
71
+ assert.strictEqual(circuit.state(), 'open');
72
+ nowMs += 25_000; // below the doubled 2nd-stage backoff's own 30s floor
73
+ assert.strictEqual(circuit.state(), 'open');
74
+ nowMs += 60 * 60 * 1000;
75
+ assert.strictEqual(circuit.state(), 'half_open');
76
+ });
77
+
78
+ test('backoffWithJitter: 1000 samples stay within [0.5x, 1.0x] of the doubled base, floor 30s / cap 8min', () => {
79
+ for (let i = 0; i < 1000; i++) {
80
+ const first = backoffWithJitter(0);
81
+ assert.ok(first >= 15_000 && first <= 30_000, `first=${first}`);
82
+
83
+ const second = backoffWithJitter(30_000);
84
+ assert.ok(second >= 30_000 && second <= 60_000, `second=${second}`);
85
+
86
+ const atCap = backoffWithJitter(8 * 60 * 1000);
87
+ assert.ok(atCap >= 4 * 60 * 1000 && atCap <= 8 * 60 * 1000, `atCap=${atCap}`);
88
+
89
+ const beyondCap = backoffWithJitter(20 * 60 * 1000); // doubling would exceed the 8min cap
90
+ assert.ok(beyondCap >= 4 * 60 * 1000 && beyondCap <= 8 * 60 * 1000, `beyondCap=${beyondCap}`);
91
+ }
92
+ });
93
+
94
+ test('isResetFresh: past -> false, future -> true, null -> false', () => {
95
+ const now = Date.parse('2026-09-13T12:00:00Z');
96
+ assert.strictEqual(isResetFresh('2026-09-13T11:59:59Z', now), false); // past
97
+ assert.strictEqual(isResetFresh('2026-09-13T12:00:00Z', now), false); // at now
98
+ assert.strictEqual(isResetFresh('2026-09-13T12:00:01Z', now), true); // future
99
+ assert.strictEqual(isResetFresh(null, now), false);
100
+ assert.strictEqual(isResetFresh(undefined, now), false);
101
+ assert.strictEqual(isResetFresh('not-a-date', now), false);
102
+ });
103
+
104
+ test('bindingWindow: limits[] with weekly_all is_active wins over five_hour', () => {
105
+ const payload = {
106
+ five_hour: { utilization: 5, resets_at: '2026-09-13T15:00:00Z' },
107
+ limits: [
108
+ { type: 'five_hour', is_active: false, utilization: 5, resets_at: '2026-09-13T15:00:00Z' },
109
+ { type: 'weekly_all', is_active: true, utilization: 81, resets_at: '2026-09-19T00:00:00Z' },
110
+ ],
111
+ };
112
+ const win = bindingWindow(payload);
113
+ assert.strictEqual(win.name, 'weekly_all');
114
+ assert.strictEqual(win.utilization, 81);
115
+ assert.strictEqual(win.resets_at, '2026-09-19T00:00:00Z');
116
+ });
117
+
118
+ test('bindingWindow: limits[] absent falls back to five_hour', () => {
119
+ const payload = { five_hour: { utilization: 42, resets_at: '2026-09-13T15:00:00Z' } };
120
+ const win = bindingWindow(payload);
121
+ assert.strictEqual(win.name, 'five_hour');
122
+ assert.strictEqual(win.utilization, 42);
123
+ });
124
+
125
+ test('bindingWindow: limits[] present but nothing is_active also falls back to five_hour', () => {
126
+ const payload = {
127
+ five_hour: { utilization: 12, resets_at: null },
128
+ limits: [
129
+ { type: 'five_hour', is_active: false, utilization: 12, resets_at: null },
130
+ { type: 'weekly_all', is_active: false, utilization: 81, resets_at: null },
131
+ ],
132
+ };
133
+ const win = bindingWindow(payload);
134
+ assert.strictEqual(win.name, 'five_hour');
135
+ assert.strictEqual(win.utilization, 12);
136
+ });
137
+
138
+ test('degradedBudget: never yields 0 utilization, even when the last good binding window read 0', () => {
139
+ const payload = { five_hour: { utilization: 0, resets_at: null } };
140
+ const { utilization } = degradedBudget(payload, {});
141
+ assert.notStrictEqual(utilization, 0);
142
+ assert.strictEqual(utilization, 100); // no real signal -> conservative default
143
+ });
144
+
145
+ test('degradedBudget: carries forward the real last-good BINDING (weekly_all) utilization, e.g. 81%, never 0', () => {
146
+ const payload = {
147
+ five_hour: { utilization: 0, resets_at: null },
148
+ limits: [
149
+ { type: 'five_hour', is_active: false, utilization: 0, resets_at: null },
150
+ { type: 'weekly_all', is_active: true, utilization: 81, resets_at: '2026-09-19T00:00:00Z' },
151
+ ],
152
+ };
153
+ const { utilization } = degradedBudget(payload, {});
154
+ assert.strictEqual(utilization, 81);
155
+ });
156
+
157
+ test('degradedBudget: caps concurrency at 2 regardless of a higher configured cap', () => {
158
+ const payload = { five_hour: { utilization: 30, resets_at: null } };
159
+ const { concurrencyCap } = degradedBudget(payload, { configuredCap: 5 });
160
+ assert.strictEqual(concurrencyCap, 2);
161
+ });
162
+
163
+ test('degradedBudget: respects a configured cap below 2', () => {
164
+ const payload = { five_hour: { utilization: 30, resets_at: null } };
165
+ const { concurrencyCap } = degradedBudget(payload, { configuredCap: 1 });
166
+ assert.strictEqual(concurrencyCap, 1);
167
+ });
168
+
169
+ test('degradedBudget: an executor-observed 429 whose window has not passed pins utilization to 100', () => {
170
+ const payload = { five_hour: { utilization: 5, resets_at: null } };
171
+ const now = Date.parse('2026-09-13T12:00:00Z');
172
+ const { utilization } = degradedBudget(payload, {
173
+ observed429: true,
174
+ resetsAt: '2026-09-13T13:00:00Z', // still in the future
175
+ now,
176
+ });
177
+ assert.strictEqual(utilization, 100);
178
+ });
179
+
180
+ test('degradedBudget: once the 429 window passes, it stops pinning to 100 and falls back to real utilization', () => {
181
+ const payload = { five_hour: { utilization: 5, resets_at: null } };
182
+ const now = Date.parse('2026-09-13T12:00:00Z');
183
+ const { utilization } = degradedBudget(payload, {
184
+ observed429: true,
185
+ resetsAt: '2026-09-13T11:00:00Z', // already passed
186
+ now,
187
+ });
188
+ assert.strictEqual(utilization, 5);
189
+ });
190
+
191
+ test('singleFlight: concurrent callers share the same in-flight promise, fn invoked once', async () => {
192
+ let calls = 0;
193
+ let resolveFn;
194
+ const fn = () => new Promise((resolve) => { resolveFn = resolve; calls += 1; });
195
+ const wrapped = singleFlight(fn);
196
+
197
+ const p1 = wrapped();
198
+ const p2 = wrapped();
199
+ assert.strictEqual(p1, p2);
200
+ assert.strictEqual(calls, 1);
201
+ resolveFn('done');
202
+ assert.strictEqual(await p1, 'done');
203
+
204
+ const p3 = wrapped();
205
+ assert.notStrictEqual(p3, p1);
206
+ assert.strictEqual(calls, 2);
207
+ resolveFn('done2');
208
+ await p3;
209
+ });
210
+
211
+ test('openedAt: null while closed, stamped the moment it opens, stays stamped through a failed half_open probe, clears on success', () => {
212
+ let nowMs = 1000;
213
+ const circuit = createUsageCircuit({ now: () => nowMs });
214
+ assert.strictEqual(circuit.openedAt(), null);
215
+ circuit.recordFailure('a');
216
+ circuit.recordFailure('a');
217
+ assert.strictEqual(circuit.openedAt(), null); // still closed, 2 of 3
218
+ circuit.recordFailure('a');
219
+ assert.strictEqual(circuit.state(), 'open');
220
+ assert.strictEqual(circuit.openedAt(), 1000);
221
+
222
+ nowMs += 60 * 60 * 1000;
223
+ assert.strictEqual(circuit.state(), 'half_open');
224
+ assert.strictEqual(circuit.openedAt(), 1000); // unchanged through the probe
225
+
226
+ circuit.recordFailure('a'); // failed probe -> reopens
227
+ assert.strictEqual(circuit.state(), 'open');
228
+ assert.strictEqual(circuit.openedAt(), 1000); // still the ORIGINAL open time, not re-stamped
229
+
230
+ circuit.recordSuccess({});
231
+ assert.strictEqual(circuit.openedAt(), null);
232
+ });
233
+
234
+ test('getConsecutiveFailures: tracks failures, resets on success', () => {
235
+ const circuit = createUsageCircuit({ now: () => 0 });
236
+ assert.strictEqual(circuit.getConsecutiveFailures(), 0);
237
+ circuit.recordFailure('a');
238
+ circuit.recordFailure('a');
239
+ assert.strictEqual(circuit.getConsecutiveFailures(), 2);
240
+ circuit.recordSuccess({});
241
+ assert.strictEqual(circuit.getConsecutiveFailures(), 0);
242
+ });
243
+
244
+ test('SM_USAGE_CIRCUIT=0 bypasses the breaker entirely: state stays closed no matter how many failures', () => {
245
+ const prev = process.env.SM_USAGE_CIRCUIT;
246
+ process.env.SM_USAGE_CIRCUIT = '0';
247
+ try {
248
+ const circuit = createUsageCircuit({ now: () => 0 });
249
+ circuit.recordFailure('a');
250
+ circuit.recordFailure('a');
251
+ circuit.recordFailure('a');
252
+ circuit.recordFailure('a');
253
+ assert.strictEqual(circuit.state(), 'closed');
254
+ assert.strictEqual(circuit.openedAt(), null);
255
+ } finally {
256
+ if (prev === undefined) delete process.env.SM_USAGE_CIRCUIT; else process.env.SM_USAGE_CIRCUIT = prev;
257
+ }
258
+ });
259
+
260
+ test('SM_USAGE_DEGRADED_CAP overrides degradedBudget concurrencyCap outright, bypassing min(cap, 2)', () => {
261
+ const prev = process.env.SM_USAGE_DEGRADED_CAP;
262
+ process.env.SM_USAGE_DEGRADED_CAP = '7';
263
+ try {
264
+ const payload = { five_hour: { utilization: 30, resets_at: null } };
265
+ const { concurrencyCap } = degradedBudget(payload, { configuredCap: 2 });
266
+ assert.strictEqual(concurrencyCap, 7);
267
+ } finally {
268
+ if (prev === undefined) delete process.env.SM_USAGE_DEGRADED_CAP; else process.env.SM_USAGE_DEGRADED_CAP = prev;
269
+ }
270
+ });
271
+
272
+ test('singleFlight: de-dupes even when fn rejects, next call gets a fresh attempt', async () => {
273
+ let calls = 0;
274
+ const fn = () => (calls++ === 0 ? Promise.reject(new Error('boom')) : Promise.resolve('ok'));
275
+ const wrapped = singleFlight(fn);
276
+
277
+ const p1 = wrapped();
278
+ const p2 = wrapped();
279
+ assert.strictEqual(p1, p2);
280
+ await assert.rejects(p1, /boom/);
281
+
282
+ const p3 = wrapped();
283
+ assert.strictEqual(await p3, 'ok');
284
+ assert.strictEqual(calls, 2);
285
+ });