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
@@ -130,4 +130,4 @@ async function seedAgentPersonas({ logger = console, writeLog = () => {} } = {})
130
130
  }
131
131
  }
132
132
 
133
- module.exports = { seedAgentPersonas, markerPath, MAX_ATTEMPTS };
133
+ module.exports = { seedAgentPersonas, markerPath, MAX_ATTEMPTS, PERSONAS };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * First-boot seeder for the bundled `session-manager-dev` plugin.
3
3
  *
4
- * The plugin (its 10 dev skills) ships inside the npx distribution. To make it
4
+ * The plugin (the dev skills under plugins/session-manager-dev/skills) ships inside the npx distribution. To make it
5
5
  * a true default, the app installs it on first launch from its own bundled
6
6
  * marketplace (offline — no GitHub/registry). Idempotent two ways:
7
7
  *
@@ -35,7 +35,7 @@ const fs = require('node:fs');
35
35
  const path = require('node:path');
36
36
  const os = require('node:os');
37
37
  const { spawn } = require('node:child_process');
38
- const { resolveClaudeBin } = require('./lib/claudeBin.cjs');
38
+ const { resolveClaudeBin, claudeSpawnTarget } = require('./lib/claudeBin.cjs');
39
39
  const { cleanChildEnv, pathWithUserBins } = require('./lib/cleanEnv.cjs');
40
40
  const { writeJsonSync } = require('./config.cjs');
41
41
 
@@ -106,13 +106,14 @@ function scriptPathMissing() {
106
106
  * No shell:true — argv array passed directly to spawn. */
107
107
  function runClaudeMcpAdd(serverPath) {
108
108
  return new Promise((resolve) => {
109
- const claudeBin = resolveClaudeBin();
109
+ const target = claudeSpawnTarget('aux', 'mcp-add');
110
110
  let proc;
111
111
  try {
112
112
  proc = spawn(
113
- claudeBin,
113
+ target.command,
114
114
  ['mcp', 'add', SERVER_NAME, '--scope', 'user', '--', 'node', serverPath],
115
115
  {
116
+ ...(target.argv0 ? { argv0: target.argv0 } : {}),
116
117
  cwd: os.homedir(),
117
118
  env: cleanChildEnv({ PATH: pathWithUserBins() }),
118
119
  stdio: 'ignore',
@@ -130,13 +131,13 @@ function runClaudeMcpAdd(serverPath) {
130
131
  /** Run `claude mcp remove session-manager-scheduler --scope user`, best-effort. */
131
132
  function runClaudeMcpRemove() {
132
133
  return new Promise((resolve) => {
133
- const claudeBin = resolveClaudeBin();
134
+ const target = claudeSpawnTarget('aux', 'mcp-remove');
134
135
  let proc;
135
136
  try {
136
137
  proc = spawn(
137
- claudeBin,
138
+ target.command,
138
139
  ['mcp', 'remove', SERVER_NAME, '--scope', 'user'],
139
- { cwd: os.homedir(), env: cleanChildEnv({ PATH: pathWithUserBins() }), stdio: 'ignore' }
140
+ { cwd: os.homedir(), env: cleanChildEnv({ PATH: pathWithUserBins() }), stdio: 'ignore', ...(target.argv0 ? { argv0: target.argv0 } : {}) }
140
141
  );
141
142
  } catch (err) {
142
143
  resolve({ ok: false, error: err?.message ?? String(err) });
@@ -43,7 +43,7 @@ const FIXES = {
43
43
  'dev-plugin': 'Open Plugins → Library and click Install next to "session-manager-dev".',
44
44
  'scheduler-mcp': () =>
45
45
  `Run in a terminal: claude mcp add ${seedSchedulerMcp.SERVER_NAME} --scope user -- node ${seedSchedulerMcp.serverScriptPath()}`,
46
- 'agent-personas': 'Copy src/seed/agents/architect.md and dev-lead.md into ~/.claude/agents/ (skip any that already exist there).',
46
+ 'agent-personas': `Copy ${seedAgentPersonas.PERSONAS.map((n) => `src/seed/agents/${n}.md`).join(', ')} into ~/.claude/agents/ (skip any that already exist there).`,
47
47
  };
48
48
 
49
49
  /** { 'dev-plugin': {status, fix}, 'scheduler-mcp': {status, fix}, 'agent-personas': {status, fix} } */
@@ -16,7 +16,8 @@ const path = require('node:path');
16
16
  const os = require('node:os');
17
17
  const { spawn, execFileSync } = require('node:child_process');
18
18
  const { ipcMain } = require('electron');
19
- const { resolveClaudeBin } = require('./lib/claudeBin.cjs');
19
+ const { resolveClaudeBin, claudeSpawnTarget } = require('./lib/claudeBin.cjs');
20
+ const { withProcRole } = require('./lib/cleanEnv.cjs');
20
21
 
21
22
  const HOME = os.homedir();
22
23
  const SUPERVISOR_LOG_PATH = path.join(HOME, '.claude', 'session-manager', 'supervisor.log');
@@ -207,7 +208,8 @@ function runProbe(claudeBin, prompt) {
207
208
  return new Promise((resolve) => {
208
209
  let child;
209
210
  try {
210
- child = spawn(claudeBin, [
211
+ const target = claudeSpawnTarget('aux', 'supervisor', claudeBin);
212
+ child = spawn(target.command, [
211
213
  '-p', prompt,
212
214
  '--model', 'claude-opus-4-7',
213
215
  '--no-session-persistence',
@@ -215,7 +217,7 @@ function runProbe(claudeBin, prompt) {
215
217
  '--max-budget-usd', '0.10',
216
218
  '--dangerously-skip-permissions',
217
219
  '--allowedTools', 'Bash',
218
- ], { stdio: ['ignore', 'pipe', 'pipe'] });
220
+ ], { env: withProcRole(process.env, 'aux'), stdio: ['ignore', 'pipe', 'pipe'], ...(target.argv0 ? { argv0: target.argv0 } : {}) });
219
221
  } catch (e) {
220
222
  console.error('[supervisor] probe spawn failed:', e?.message);
221
223
  resolve({ verdict: 'ok', action: 'none', targetPid: null, reason: `spawn failed: ${e?.message}`, costUsd: null });
@@ -1,3 +1,4 @@
1
+ <!-- PRD_AUTHORING.md v2 -->
1
2
  # PRD Authoring Guide — Scheduler Safety Rules
2
3
 
3
4
  This guide codifies lessons from two stuck-job incidents (fizzpop poll-hang, etch-engine post-AC overrun) into enforceable rules every PRD MUST follow. Violating these rules costs real money and wastes hours waiting at a terminal.
@@ -148,6 +149,8 @@ estimateMinutes: 60
148
149
 
149
150
  **Rule:** Include exact file paths, function signatures if they save a Read, library versions, and the name of any sibling PRD the executor must NOT duplicate. Do not reference "the conversation", "what we discussed", "the design doc", or any other external context. If the executor would need to search for something, include the answer.
150
151
 
152
+ **Epic context digest is additive, not a dependency (PRD 958):** when a job's `epicId` resolves to a known Epic in that project's `active-index.json`, the scheduler prepends a short digest of the Epic's own session (goal text + recent turns, built by `src/main/lib/epicContextDigest.cjs`'s `buildContextDigest`) to the `-p` prompt sent to the executor — the on-disk PRD `.md` file itself is never rewritten. This exists purely to orient the executor faster; it is never load-bearing. The digest is a silent no-op when the Epic doesn't resolve, and any failure building it is caught and logged, never blocking dispatch. Every PRD body must still stand on its own per the rule above — write it as if the digest will not be there.
153
+
151
154
  ---
152
155
 
153
156
  ## §8 Scope sizing — target ~15 min, ceiling 30 (data-driven, 2026-06)
@@ -448,3 +451,50 @@ intake`, commit `352b89c`). It syncs open GitHub issues into `session-manager-op
448
451
  → queue path the rest of this guide documents. An external project building a Slack (or any
449
452
  other) adapter should follow the same source → triage → queue shape, ending at
450
453
  `scheduler_create_prd` (app running) or a hand-written PRD file (app closed) as described above.
454
+
455
+ ## §15 Resolving a `failed`/`needs_review` job whose target work is already done
456
+
457
+ **Symptom:** a job lands in `failed` or `needs_review` even though its actual objective was
458
+ already satisfied — either by a sibling/concurrent job that finished the same work first (a
459
+ duplicate PRD racing another one), or by an out-of-band actor (a human, another agent) reaching
460
+ the same target state before the scheduler's run even started. The job's own transcript may be
461
+ completely accurate (`SCHEDULER_VERDICT: PASS but no commit landed during the run window`) — the
462
+ "failure" is a stale queue entry, not broken work.
463
+
464
+ **Do NOT hand-edit `queue.json` to fix the status.** It's a live file the running Electron
465
+ scheduler process (and the external watchdog) both read and write; a manual edit races the app's
466
+ own save cycle and risks a torn write. There is also no supported IPC/CLI surface today to flip a
467
+ single job's `status` field directly.
468
+
469
+ **The safe, supported remediation — confirmed working live (2026-07-18):** archive the job's PRD
470
+ *source file*, not the queue entry. `reconcile()` (`src/main/scheduler.cjs`) runs on every queue
471
+ read/tick and drops any `queue.json` job entry whose PRD `.md` no longer exists in `prds/` — so
472
+ archiving the file is sufficient; you never touch `queue.json` yourself.
473
+
474
+ ```js
475
+ // From this repo's root (session-manager), or anywhere queueOps.cjs is reachable:
476
+ const q = require('./src/main/queueOps.cjs');
477
+ await q.archiveMany(['<slug-of-the-stale-job>']);
478
+ // Atomic rename to prds-archived/<ISO>/<slug>.md — reversible, path-contained,
479
+ // no queue.json write. The next reconcile() (within one scheduler tick, ~10-15s
480
+ // observed) drops the matching queue.json job entry automatically.
481
+ ```
482
+
483
+ This is the exact mechanism `schedule:clear-queue`'s IPC handler uses internally, just scoped to
484
+ one slug instead of "every non-running job" — safe to call from outside Electron (no admin API
485
+ needed) since `queueOps.cjs`'s `archiveMany` is a plain exported function, not IPC-gated.
486
+
487
+ **When to use this vs. requeueing a retry:** only when you've confirmed the target state is
488
+ already correct (read the failed job's own transcript/log — did it conclude "nothing left to
489
+ do"? did a sibling job's commit already land the same objective? does an independent check like
490
+ `gh pr view <n> --json mergeable` already show the desired end state?). If the target state is
491
+ genuinely NOT yet reached, fix and requeue instead — archiving does not fix an actual bug, it
492
+ only clears a stale status label for already-completed work.
493
+
494
+ **Known gap this doesn't cover:** the verifier's `pass_no_commit` classification (a run that
495
+ concludes "PASS, no code change needed" gets flagged `needs_review` as if it were suspicious)
496
+ does not yet special-case "another actor already satisfied this PRD's postcondition" for
497
+ externally-checkable targets (e.g. a `gh pr view`-mergeable branch). `RESCANNABLE_VERDICTS`
498
+ already includes `pass_no_commit` for one narrow case (fix-plan jobs, exempted 2026-07-12) but
499
+ not the general case. See `session-manager-operations/feedback/processed/` for the tracked
500
+ follow-up on softening this classification for merge-style PRDs with a checkable postcondition.
@@ -23,6 +23,7 @@ const os = require('node:os');
23
23
  const { ipcMain } = require('electron');
24
24
  const { refreshIfNeeded, expiresAtMs } = require('./lib/credentials.cjs');
25
25
  const { writeJson } = require('./config.cjs');
26
+ const { createUsageCircuit, singleFlight } = require('./lib/usageCircuit.cjs');
26
27
 
27
28
  const USAGE_URL = 'https://api.anthropic.com/api/oauth/usage';
28
29
 
@@ -116,6 +117,19 @@ const OK_CACHE_TTL_MS = 30_000;
116
117
  *
117
118
  * Returns one of: 'ok' | 'auth' | 'transient' | 'meter_rate_limited'
118
119
  */
120
+ /**
121
+ * Parses a `Retry-After` header value (delta-seconds or an HTTP-date) into a
122
+ * millisecond duration from now, or null if absent/unparseable.
123
+ */
124
+ function parseRetryAfterMs(headerValue) {
125
+ if (!headerValue) return null;
126
+ const seconds = Number(headerValue);
127
+ if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000;
128
+ const dateMs = Date.parse(headerValue);
129
+ if (Number.isFinite(dateMs)) return Math.max(0, dateMs - Date.now());
130
+ return null;
131
+ }
132
+
119
133
  function classifyUsageResponse(status, bodyText) {
120
134
  if (status === 401 || status === 403) return { kind: 'auth', httpStatus: status };
121
135
  if (status === 429) {
@@ -133,6 +147,18 @@ function classifyUsageResponse(status, bodyText) {
133
147
 
134
148
  let cache = null;
135
149
  let hydrationPromise = null;
150
+ // Retry-After suppression window: while `now < retryNotBeforeMs`, fetchUsage()
151
+ // refuses to issue another request and instead replays `lastRateLimitedResult`
152
+ // (annotated `suppressed: true`) — set only from a 429's Retry-After header.
153
+ let retryNotBeforeMs = 0;
154
+ let lastRateLimitedResult = null;
155
+
156
+ // Single shared circuit for the /api/oauth/usage meter. Both callers — the
157
+ // renderer's billing:fetch IPC handler and the scheduler's pollLoop — go
158
+ // through this same fetchUsage(), so a success recorded from EITHER caller
159
+ // clears the streak for both, and concurrent callers coalesce into one HTTP
160
+ // request via singleFlight.
161
+ const circuit = createUsageCircuit();
136
162
 
137
163
  async function hydrateCache() {
138
164
  try {
@@ -142,23 +168,17 @@ async function hydrateCache() {
142
168
  }
143
169
  }
144
170
 
171
+ function ensureHydrated() {
172
+ if (!hydrationPromise) hydrationPromise = hydrateCache();
173
+ return hydrationPromise;
174
+ }
175
+
145
176
  async function persistCache(c) {
146
177
  await writeJson(CACHE_PATH, c);
147
178
  }
148
179
 
149
- async function fetchUsage() {
150
- // Test stub: SM_MOCK_BILLING_KIND lets e2e tests simulate billing API responses
151
- // without hitting the real endpoint. Only active when SM_E2E=1 to prevent
152
- // accidental use in production.
153
- if (process.env.SM_E2E === '1' && process.env.SM_MOCK_BILLING_KIND) {
154
- const kind = process.env.SM_MOCK_BILLING_KIND;
155
- if (kind === 'meter_rate_limited') return { kind: 'meter_rate_limited', message: 'e2e stub', httpStatus: 429 };
156
- if (kind === 'transient') return { kind: 'transient', message: 'e2e stub', httpStatus: 503 };
157
- if (kind === 'auth') return { kind: 'auth', message: 'e2e stub', httpStatus: 401 };
158
- // 'ok' stub returns a minimal valid payload.
159
- return { kind: 'ok', data: { usage: { five_hour: { utilization: 10, resets_at: null }, seven_day: { utilization: 10, resets_at: null }, seven_day_sonnet: null, seven_day_opus: null, extra_usage: null }, subscriptionType: null, rateLimitTier: null, credentialsExpiresAt: null, fetchedAt: Date.now() } };
160
- }
161
-
180
+ /** The actual network round-trip, coalesced across concurrent callers below. */
181
+ async function networkFetchUsage() {
162
182
  // Check expiry and attempt proactive refresh before touching the network.
163
183
  const refresh = await refreshIfNeeded();
164
184
  if (refresh.kind === 'auth') {
@@ -168,6 +188,7 @@ async function fetchUsage() {
168
188
  // 'ok' or 'unsupported' — creds present and not yet expired
169
189
  const creds = refresh.creds;
170
190
 
191
+ let result;
171
192
  let r;
172
193
  try {
173
194
  r = await fetch(USAGE_URL, {
@@ -179,59 +200,107 @@ async function fetchUsage() {
179
200
  signal: AbortSignal.timeout(10_000),
180
201
  });
181
202
  } catch (e) {
182
- return { kind: 'transient', message: e.message || String(e), httpStatus: null };
203
+ result = { kind: 'transient', message: e.message || String(e), httpStatus: null };
183
204
  }
184
- if (r.status === 401 || r.status === 403) {
185
- const body = await r.text().catch(() => '');
186
- const ms = expiresAtMs(creds);
187
- return { kind: 'auth', message: body.slice(0, 200) || `HTTP ${r.status}`, httpStatus: r.status, expiredAt: ms };
188
- }
189
- if (r.status === 408 || r.status >= 500) {
190
- const body = await r.text().catch(() => '');
191
- return { kind: 'transient', message: body.slice(0, 200) || `HTTP ${r.status}`, httpStatus: r.status };
192
- }
193
- if (r.status === 429) {
194
- const body = await r.text().catch(() => '');
195
- let parsed = null;
196
- try { parsed = JSON.parse(body); } catch { /* */ }
197
- if (parsed?.error?.type === 'rate_limit_error') {
198
- return { kind: 'meter_rate_limited', message: body.slice(0, 200), httpStatus: 429 };
205
+ if (!result) {
206
+ if (r.status === 401 || r.status === 403) {
207
+ const body = await r.text().catch(() => '');
208
+ const ms = expiresAtMs(creds);
209
+ result = { kind: 'auth', message: body.slice(0, 200) || `HTTP ${r.status}`, httpStatus: r.status, expiredAt: ms };
210
+ } else if (r.status === 408 || r.status >= 500) {
211
+ const body = await r.text().catch(() => '');
212
+ result = { kind: 'transient', message: body.slice(0, 200) || `HTTP ${r.status}`, httpStatus: r.status };
213
+ } else if (r.status === 429) {
214
+ const body = await r.text().catch(() => '');
215
+ let parsed = null;
216
+ try { parsed = JSON.parse(body); } catch { /* */ }
217
+ if (parsed?.error?.type === 'rate_limit_error') {
218
+ result = {
219
+ kind: 'meter_rate_limited',
220
+ message: body.slice(0, 200),
221
+ httpStatus: 429,
222
+ retryAfterMs: parseRetryAfterMs(r.headers.get('retry-after')),
223
+ };
224
+ } else {
225
+ result = { kind: 'transient', message: body.slice(0, 200) || 'HTTP 429', httpStatus: 429 };
226
+ }
227
+ } else if (!r.ok) {
228
+ const body = await r.text().catch(() => '');
229
+ result = { kind: 'transient', message: body.slice(0, 200) || `HTTP ${r.status}`, httpStatus: r.status };
230
+ } else {
231
+ const usage = await r.json();
232
+ result = {
233
+ kind: 'ok',
234
+ data: {
235
+ usage,
236
+ subscriptionType: creds.subscriptionType ?? null,
237
+ rateLimitTier: creds.rateLimitTier ?? null,
238
+ credentialsExpiresAt: creds.expiresAt ?? null,
239
+ fetchedAt: Date.now(),
240
+ },
241
+ };
199
242
  }
200
- return { kind: 'transient', message: body.slice(0, 200) || 'HTTP 429', httpStatus: 429 };
201
243
  }
202
- if (!r.ok) {
203
- const body = await r.text().catch(() => '');
204
- return { kind: 'transient', message: body.slice(0, 200) || `HTTP ${r.status}`, httpStatus: r.status };
244
+
245
+ if (result.kind === 'ok') {
246
+ cache = { data: result.data, fetchedAt: Date.now(), sourceCredsExpiresAt: result.data.credentialsExpiresAt };
247
+ persistCache(cache).catch(() => {});
248
+ retryNotBeforeMs = 0;
249
+ lastRateLimitedResult = null;
250
+ circuit.recordSuccess(result.data);
251
+ } else if (result.kind === 'meter_rate_limited') {
252
+ lastRateLimitedResult = result;
253
+ if (Number.isFinite(result.retryAfterMs) && result.retryAfterMs > 0) {
254
+ retryNotBeforeMs = Date.now() + result.retryAfterMs;
255
+ }
256
+ circuit.recordFailure(result.kind);
257
+ } else if (result.kind === 'transient') {
258
+ circuit.recordFailure(result.kind);
205
259
  }
206
- const usage = await r.json();
207
- return {
208
- kind: 'ok',
209
- data: {
210
- usage,
211
- subscriptionType: creds.subscriptionType ?? null,
212
- rateLimitTier: creds.rateLimitTier ?? null,
213
- credentialsExpiresAt: creds.expiresAt ?? null,
214
- fetchedAt: Date.now(),
215
- },
216
- };
260
+ return result;
217
261
  }
218
262
 
219
- function registerBillingHandlers() {
220
- hydrationPromise = hydrateCache();
263
+ // Concurrent callers (the renderer's billing:fetch and the scheduler's
264
+ // pollLoop) coalesce into this one in-flight promise instead of each firing
265
+ // their own request against a rate-limited endpoint.
266
+ const singleFlightNetworkFetch = singleFlight(networkFetchUsage);
221
267
 
222
- ipcMain.handle('billing:fetch', async () => {
223
- if (hydrationPromise) { await hydrationPromise; hydrationPromise = null; }
268
+ /**
269
+ * The single owner of caching, single-flight de-duplication and Retry-After
270
+ * suppression for the usage meter. Every caller — renderer IPC and scheduler
271
+ * pollLoop alike — must go through this function rather than hitting the
272
+ * network or the cache file directly.
273
+ */
274
+ async function fetchUsage() {
275
+ // Test stub: SM_MOCK_BILLING_KIND lets e2e tests simulate billing API responses
276
+ // without hitting the real endpoint. Only active when SM_E2E=1 to prevent
277
+ // accidental use in production.
278
+ if (process.env.SM_E2E === '1' && process.env.SM_MOCK_BILLING_KIND) {
279
+ const kind = process.env.SM_MOCK_BILLING_KIND;
280
+ if (kind === 'meter_rate_limited') return { kind: 'meter_rate_limited', message: 'e2e stub', httpStatus: 429 };
281
+ if (kind === 'transient') return { kind: 'transient', message: 'e2e stub', httpStatus: 503 };
282
+ if (kind === 'auth') return { kind: 'auth', message: 'e2e stub', httpStatus: 401 };
283
+ // 'ok' stub returns a minimal valid payload.
284
+ return { kind: 'ok', data: { usage: { five_hour: { utilization: 10, resets_at: null }, seven_day: { utilization: 10, resets_at: null }, seven_day_sonnet: null, seven_day_opus: null, extra_usage: null }, subscriptionType: null, rateLimitTier: null, credentialsExpiresAt: null, fetchedAt: Date.now() } };
285
+ }
224
286
 
225
- if (cache && cache.fetchedAt && Date.now() - cache.fetchedAt < OK_CACHE_TTL_MS) {
226
- return { kind: 'ok', data: cache.data };
227
- }
287
+ await ensureHydrated();
228
288
 
289
+ if (cache && cache.fetchedAt && Date.now() - cache.fetchedAt < OK_CACHE_TTL_MS) {
290
+ return { kind: 'ok', data: cache.data };
291
+ }
292
+
293
+ if (lastRateLimitedResult && retryNotBeforeMs && Date.now() < retryNotBeforeMs) {
294
+ return { ...lastRateLimitedResult, suppressed: true };
295
+ }
296
+
297
+ return singleFlightNetworkFetch();
298
+ }
299
+
300
+ function registerBillingHandlers() {
301
+ ipcMain.handle('billing:fetch', async () => {
229
302
  const r = await fetchUsage();
230
- if (r.kind === 'ok') {
231
- cache = { data: r.data, fetchedAt: Date.now(), sourceCredsExpiresAt: r.data.credentialsExpiresAt };
232
- persistCache(cache).catch(() => {});
233
- return { kind: 'ok', data: r.data };
234
- }
303
+ if (r.kind === 'ok') return { kind: 'ok', data: r.data };
235
304
  if (r.kind === 'auth') {
236
305
  if (cache) return { kind: 'auth', message: r.message, httpStatus: r.httpStatus, expiredAt: r.expiredAt, cached: cache.data, staleSince: cache.fetchedAt };
237
306
  return r;
@@ -241,11 +310,24 @@ function registerBillingHandlers() {
241
310
  return r;
242
311
  }
243
312
  if (r.kind === 'meter_rate_limited') {
244
- if (cache) return { kind: 'meter_rate_limited', message: r.message, httpStatus: r.httpStatus, cached: cache.data, staleSince: cache.fetchedAt };
313
+ if (cache) return { kind: 'meter_rate_limited', message: r.message, httpStatus: r.httpStatus, retryAfterMs: r.retryAfterMs, suppressed: r.suppressed, cached: cache.data, staleSince: cache.fetchedAt };
245
314
  return r;
246
315
  }
247
316
  return r; // config
248
317
  });
249
318
  }
250
319
 
251
- module.exports = { registerBillingHandlers, fetchUsage, classifyUsageResponse, usageMeterApplicable, readClaudeSettingsAuth };
320
+ module.exports = {
321
+ registerBillingHandlers,
322
+ fetchUsage,
323
+ classifyUsageResponse,
324
+ parseRetryAfterMs,
325
+ usageMeterApplicable,
326
+ readClaudeSettingsAuth,
327
+ // The single shared breaker instance — scheduler.cjs's pollLoop (and every
328
+ // other consumer) reads THIS object's state()/openedAt()/
329
+ // getConsecutiveFailures() rather than deriving its own, so a failure
330
+ // recorded via any path (poller, executor 429) opens the same circuit.
331
+ usageCircuit: circuit,
332
+ __usageCircuitForTest: circuit,
333
+ };