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,164 @@
1
+ /**
2
+ * scheduler-porcelain-rename.test.cjs — PRD 1187/1186-follow-on: a staged
3
+ * rename ("R old -> new") was parsed by `parsePorcelainEntries` as ONE fused
4
+ * path string `"old -> new"`. That string can never match a real path in
5
+ * `dirtyAfter`, `pathsCommittedDuringRun`, or an `fs.existsSync` check, so
6
+ * every staged rename in a shared-tree-guard baseline was structurally
7
+ * guaranteed to be reported reverted — even when it was cleanly committed
8
+ * during the run. On 2026-09-12 this flagged 45 paths and parked a clean,
9
+ * committed run (job `1187-autoresolve-guard-verdict-parks`):
10
+ *
11
+ * "job discarded pre-existing state in the shared tree: 45 path(s)
12
+ * reverted with no commit to explain it
13
+ * (scripts/__tests__/manual-chapter-links.test.cjs ->
14
+ * web/manual/__tests__/chapter-links.test.cjs, ...)" — exit 0, landedCommit
15
+ * recorded, nothing actually lost.
16
+ *
17
+ * Every test below is written to FAIL against the pre-fix implementation
18
+ * (`path: l.slice(3)`, no rename/quote parsing at all).
19
+ *
20
+ * Run: timeout 120 npx vitest run src/main/__tests__/scheduler-porcelain-rename.test.cjs
21
+ */
22
+
23
+ 'use strict';
24
+
25
+ import { test, expect } from 'vitest';
26
+ const scheduler = require('../scheduler.cjs');
27
+ const { parsePorcelainEntries, evaluateSharedTreeGuard } = scheduler;
28
+
29
+ // --- parsePorcelainEntries: rename/copy lines split into real components ---
30
+
31
+ test('parsePorcelainEntries: a plain staged rename splits into new path + oldPath, not one fused string', () => {
32
+ const entries = parsePorcelainEntries('R old.txt -> new.txt\n');
33
+ expect(entries).toEqual([{ code: 'R ', path: 'new.txt', oldPath: 'old.txt' }]);
34
+ });
35
+
36
+ test('parsePorcelainEntries: a rename-with-worktree-modification (RM) line still splits correctly', () => {
37
+ const entries = parsePorcelainEntries('RM old.txt -> new.txt\n');
38
+ expect(entries).toEqual([{ code: 'RM', path: 'new.txt', oldPath: 'old.txt' }]);
39
+ });
40
+
41
+ test('parsePorcelainEntries: a copy line (C) splits the same way as a rename', () => {
42
+ const entries = parsePorcelainEntries('C old.txt -> copy.txt\n');
43
+ expect(entries).toEqual([{ code: 'C ', path: 'copy.txt', oldPath: 'old.txt' }]);
44
+ });
45
+
46
+ test('parsePorcelainEntries: a NEW path containing a space is quoted by git and must be unquoted', () => {
47
+ // Real `git status --porcelain` output for `git mv new.txt "renamed with space.txt"`.
48
+ const entries = parsePorcelainEntries('R new.txt -> "renamed with space.txt"\n');
49
+ expect(entries).toEqual([{ code: 'R ', path: 'renamed with space.txt', oldPath: 'new.txt' }]);
50
+ });
51
+
52
+ test('parsePorcelainEntries: an OLD path containing a space is quoted independently of the new path', () => {
53
+ const entries = parsePorcelainEntries('R "old with space.txt" -> new.txt\n');
54
+ expect(entries).toEqual([{ code: 'R ', path: 'new.txt', oldPath: 'old with space.txt' }]);
55
+ });
56
+
57
+ test('parsePorcelainEntries: a non-ASCII path is quoted with per-byte octal escapes and must decode back to UTF-8', () => {
58
+ // Real recorded `git status --porcelain` output for a rename onto
59
+ // "résumé file.txt" — git's default core.quotepath escapes each raw UTF-8
60
+ // byte >= 0x80 as \NNN octal: 'é' is UTF-8 bytes 0xC3 0xA9 -> \303\251.
61
+ const line = 'R new.txt -> "r\\303\\251sum\\303\\251 file.txt"\n';
62
+ const entries = parsePorcelainEntries(line);
63
+ expect(entries).toEqual([{ code: 'R ', path: 'résumé file.txt', oldPath: 'new.txt' }]);
64
+ });
65
+
66
+ test('parsePorcelainEntries: non-rename entries are unaffected (no oldPath, path as before)', () => {
67
+ const entries = parsePorcelainEntries(' M src/foo.js\n?? untracked.txt\n');
68
+ expect(entries).toEqual([
69
+ { code: ' M', path: 'src/foo.js' },
70
+ { code: '??', path: 'untracked.txt' },
71
+ ]);
72
+ });
73
+
74
+ test('parsePorcelainEntries: a quoted plain (non-rename) path is also unquoted, not left with literal quotes', () => {
75
+ const entries = parsePorcelainEntries('?? "plain space.txt"\n');
76
+ expect(entries).toEqual([{ code: '??', path: 'plain space.txt' }]);
77
+ });
78
+
79
+ // --- The exact 1187 shape: committed rename must not be flagged reverted ---
80
+
81
+ test('evaluateSharedTreeGuard: a staged rename that is committed during the run is NOT reported reverted (real 1187 case)', () => {
82
+ const oldPath = 'scripts/__tests__/manual-chapter-links.test.cjs';
83
+ const newPath = 'web/manual/__tests__/chapter-links.test.cjs';
84
+ const dirtyBefore = parsePorcelainEntries(`R ${oldPath} -> ${newPath}\n`);
85
+ // git diff --name-only <headBefore>..<headAfter> reports only the NEW path
86
+ // for a committed rename (verified against real git 2.43 behavior).
87
+ const result = evaluateSharedTreeGuard({
88
+ stashBefore: [],
89
+ stashAfter: [],
90
+ dirtyBefore,
91
+ dirtyAfter: [], // fully committed, nothing left dirty
92
+ pathsCommittedDuringRun: [newPath],
93
+ });
94
+ expect(result.reverted).toEqual([]);
95
+ });
96
+
97
+ test('evaluateSharedTreeGuard: a batch of committed renames (the real 45-path shape) are all cleared, none reverted', () => {
98
+ const pairs = [
99
+ ['scripts/__tests__/manual-chapter-links.test.cjs', 'web/manual/__tests__/chapter-links.test.cjs'],
100
+ ['scripts/foo-old.js', 'src/foo-new.js'],
101
+ ['docs/old-name.md', 'docs/new-name.md'],
102
+ ];
103
+ const porcelain = pairs.map(([o, n]) => `R ${o} -> ${n}`).join('\n') + '\n';
104
+ const dirtyBefore = parsePorcelainEntries(porcelain);
105
+ const result = evaluateSharedTreeGuard({
106
+ stashBefore: [],
107
+ stashAfter: [],
108
+ dirtyBefore,
109
+ dirtyAfter: [],
110
+ pathsCommittedDuringRun: pairs.map(([, n]) => n),
111
+ });
112
+ expect(result.reverted).toEqual([]);
113
+ });
114
+
115
+ // --- The guard's real purpose survives: an UNDONE rename still parks ---
116
+
117
+ test('evaluateSharedTreeGuard: a staged rename that is UNDONE (old path restored, new path gone, nothing committed) IS still reported reverted', () => {
118
+ // This test must FAIL if the fix is implemented as "blanket skip rename
119
+ // entries" (dropping every rename-coded entry from consideration would
120
+ // make `reverted` come back empty here, exactly like the committed case
121
+ // above — but here nothing was committed to explain the disappearance).
122
+ const dirtyBefore = parsePorcelainEntries('R old.txt -> new.txt\n');
123
+ const result = evaluateSharedTreeGuard({
124
+ stashBefore: [],
125
+ stashAfter: [],
126
+ dirtyBefore,
127
+ dirtyAfter: [], // clean again — reverted to HEAD, old.txt restored, new.txt gone
128
+ pathsCommittedDuringRun: [], // nothing committed
129
+ });
130
+ expect(result.reverted).toEqual(['new.txt']);
131
+ });
132
+
133
+ // --- The two already-fixed false-positive classes stay fixed (pinned here
134
+ // too, using parsePorcelainEntries as the real entry point rather than
135
+ // hand-built fixtures, so a future change to the parser can't silently
136
+ // regress them without failing a test right next to the rename fix). ---
137
+
138
+ test('evaluateSharedTreeGuard: nowIgnored still works for a real parsePorcelainEntries untracked baseline entry', () => {
139
+ const dirtyBefore = parsePorcelainEntries('?? session-manager-operations/logs/errors-2026-09-12.jsonl\n');
140
+ const result = evaluateSharedTreeGuard({
141
+ stashBefore: [],
142
+ stashAfter: [],
143
+ dirtyBefore,
144
+ dirtyAfter: [],
145
+ pathsCommittedDuringRun: ['.gitignore'],
146
+ existsAfter: ['session-manager-operations/logs/errors-2026-09-12.jsonl'],
147
+ });
148
+ expect(result.reverted).toEqual([]);
149
+ expect(result.nowIgnored).toEqual(['session-manager-operations/logs/errors-2026-09-12.jsonl']);
150
+ });
151
+
152
+ test('evaluateSharedTreeGuard: a genuine deletion of an untracked path (from a real parsePorcelainEntries entry) is still reverted', () => {
153
+ const dirtyBefore = parsePorcelainEntries('?? data/scratch.txt\n');
154
+ const result = evaluateSharedTreeGuard({
155
+ stashBefore: [],
156
+ stashAfter: [],
157
+ dirtyBefore,
158
+ dirtyAfter: [],
159
+ pathsCommittedDuringRun: [],
160
+ existsAfter: [],
161
+ });
162
+ expect(result.reverted).toEqual(['data/scratch.txt']);
163
+ expect(result.nowIgnored).toEqual([]);
164
+ });
@@ -103,6 +103,22 @@ function writeProjectQueue(cwd, jobs) {
103
103
  return path.join(stateDir, 'queue.json');
104
104
  }
105
105
 
106
+ // This test's whole premise is a PRD source missing EVERYWHERE — so the row
107
+ // this produces is exactly what reconcile()'s auto-archive-drop path treats
108
+ // as "archived on purpose": backfilled to history.jsonl and dropped from
109
+ // queue.json in the very same reconcile pass that spawnJob's own
110
+ // broadcast({flush:true}) triggers on completion (see
111
+ // scheduler-reconcile-history-backfill.test.cjs). Look in both places.
112
+ function readTerminalRow(queuePath, projectCwd, slug) {
113
+ const jobs = JSON.parse(fs.readFileSync(queuePath, 'utf8')).jobs;
114
+ const row = jobs.find((j) => j.slug === slug);
115
+ if (row) return row;
116
+ const historyPath = path.join(projectCwd, 'session-manager-operations', 'scheduler', 'state', 'history.jsonl');
117
+ if (!fs.existsSync(historyPath)) return undefined;
118
+ const lines = fs.readFileSync(historyPath, 'utf8').trim().split('\n').filter(Boolean);
119
+ return lines.map((l) => JSON.parse(l)).reverse().find((j) => j.slug === slug);
120
+ }
121
+
106
122
  // This is the actual regression case: PRD 812's fix only covered executeJob's
107
123
  // own return value. The bug lived one layer up, in spawnJob's post-run
108
124
  // handler, which only short-circuited on skipped === 'prd-archived' and let
@@ -123,8 +139,7 @@ test("spawnJob's post-run handler retires a prd-missing skip as a plain completi
123
139
  const job = { slug, cwd: projectCwd };
124
140
  await spawnJob(job, 'run-spawnjob-missing', runDir, projectCwd);
125
141
 
126
- const state = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
127
- const row = state.jobs.find((j) => j.slug === slug);
142
+ const row = readTerminalRow(queuePath, projectCwd, slug);
128
143
  expect(row).toBeDefined();
129
144
  // A 'prd-missing' skip means no executor ever ran — it must land on the
130
145
  // distinct 'skipped' status, never 'completed' (the status a genuine
@@ -17,6 +17,7 @@ import { test, expect, beforeAll, afterAll, afterEach } from 'vitest';
17
17
  const fs = require('node:fs');
18
18
  const os = require('node:os');
19
19
  const path = require('node:path');
20
+ const claudeStub = require('../../../tests/helpers/claudeStub.cjs');
20
21
 
21
22
  let tmpHome;
22
23
  let originalHome;
@@ -66,16 +67,13 @@ test('buildClaudeSpawnArgs: model is never omitted even without a systemPrompt',
66
67
  // stream-json success result and exits 0 — same technique as
67
68
  // scheduler-bash-timeout-env.test.cjs's env-var proof, applied to argv.
68
69
  function writeClaudeStub() {
69
- const stubPath = path.join(os.tmpdir(), `sm-claude-stub-argv-${process.pid}-${Math.floor(Math.random() * 1e9)}.cjs`);
70
- const body = `
70
+ return claudeStub.writeClaudeStub({ body: `
71
71
  const fs = require('fs');
72
72
  const path = require('path');
73
73
  fs.writeFileSync(path.join(process.cwd(), 'argv.marker'), JSON.stringify(process.argv.slice(2)), 'utf8');
74
74
  process.stdout.write(JSON.stringify({ type: 'result', subtype: 'success', result: 'ok' }) + '\\n');
75
75
  process.exit(0);
76
- `;
77
- fs.writeFileSync(stubPath, `#!${process.execPath}\n${body}\n`, { mode: 0o755 });
78
- return stubPath;
76
+ ` });
79
77
  }
80
78
 
81
79
  function writePersona(dir, name, frontmatterLines, bodyText) {
@@ -19,6 +19,7 @@ import { test, expect, beforeAll, afterAll, afterEach } from 'vitest';
19
19
  const fs = require('node:fs');
20
20
  const os = require('node:os');
21
21
  const path = require('node:path');
22
+ const claudeStub = require('../../../tests/helpers/claudeStub.cjs');
22
23
  const { execFileSync } = require('node:child_process');
23
24
 
24
25
  let tmpHome;
@@ -52,10 +53,41 @@ function registerActiveProject(cwd) {
52
53
  fs.writeFileSync(path.join(slugDir, 'transcript.jsonl'), JSON.stringify({ cwd }) + '\n');
53
54
  }
54
55
 
56
+ // Every real scheduler row is born from a real PRD .md file — reconcile()'s
57
+ // auto-archive-drop path (a terminal job whose slug has no on-disk PRD
58
+ // anywhere is treated as "archived on purpose": dropped from queue.json and
59
+ // backfilled to history.jsonl, see scheduler-reconcile-history-backfill.
60
+ // test.cjs) fires the moment a completion path reconciles with
61
+ // broadcast({flush:true}). Without a matching PRD source, a row this test
62
+ // drives to a terminal status vanishes from queue.json before the assertions
63
+ // below ever read it back. Epic-scoped (not the legacy flat prds/ dir) so
64
+ // it's never swept by consolidateFlatPrds either.
65
+ // Some tests author their own real PRD (e.g. with `quietMachine: true`
66
+ // frontmatter) before calling writeProjectQueue — never shadow that with a
67
+ // blank fixture, which would silently overwrite its parsed frontmatter on
68
+ // the next reconcile() refresh (onDisk.set(slug, ...) keeps whichever file a
69
+ // later scan pass parses).
70
+ function prdAlreadyExists(cwd, slug) {
71
+ const flatPath = path.join(cwd, 'session-manager-operations', 'scheduler', 'prds', `${slug}.md`);
72
+ if (fs.existsSync(flatPath)) return true;
73
+ const epicsRoot = path.join(cwd, 'session-manager-operations', 'scheduler', 'epics');
74
+ if (!fs.existsSync(epicsRoot)) return false;
75
+ return fs.readdirSync(epicsRoot).some((epicId) => fs.existsSync(path.join(epicsRoot, epicId, 'prds', `${slug}.md`)));
76
+ }
77
+
78
+ function writeFixturePrd(cwd, slug) {
79
+ const prdsDir = path.join(cwd, 'session-manager-operations', 'scheduler', 'epics', 'test-fixture-epic', 'prds');
80
+ fs.mkdirSync(prdsDir, { recursive: true });
81
+ fs.writeFileSync(path.join(prdsDir, `${slug}.md`), 'Test fixture PRD.', 'utf8');
82
+ }
83
+
55
84
  function writeProjectQueue(cwd, jobs) {
56
85
  const stateDir = path.join(cwd, 'session-manager-operations', 'scheduler', 'state');
57
86
  fs.mkdirSync(stateDir, { recursive: true });
58
87
  fs.writeFileSync(path.join(stateDir, 'queue.json'), JSON.stringify({ jobs }, null, 2));
88
+ for (const job of jobs) {
89
+ if (job && job.slug && !prdAlreadyExists(job.cwd || cwd, job.slug)) writeFixturePrd(job.cwd || cwd, job.slug);
90
+ }
59
91
  return path.join(stateDir, 'queue.json');
60
92
  }
61
93
 
@@ -66,15 +98,12 @@ function writeProjectQueue(cwd, jobs) {
66
98
  // as a genuine kill-with-partial-work, not an auto-retryable transient blip
67
99
  // with no evidence of real execution.
68
100
  function writeKilledClaudeStub() {
69
- const stubPath = path.join(os.tmpdir(), `sm-claude-stub-quiet-killed-${process.pid}-${Math.floor(Math.random() * 1e9)}.cjs`);
70
- const body = `
101
+ return claudeStub.writeClaudeStub({ body: `
71
102
  const fs = require('fs');
72
103
  const path = require('path');
73
104
  fs.writeFileSync(path.join(process.cwd(), 'job-output.txt'), 'work in progress when killed\\n', 'utf8');
74
105
  process.exit(137);
75
- `;
76
- fs.writeFileSync(stubPath, `#!${process.execPath}\n${body}\n`, { mode: 0o755 });
77
- return stubPath;
106
+ ` });
78
107
  }
79
108
 
80
109
  let reapDeadRunningJobs;
@@ -109,7 +138,13 @@ test('a killed quietMachine job releases the exclusive lease, and an ordinary jo
109
138
 
110
139
  const quietSlug = `1107-test-quiet-killed-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
111
140
  const ordinarySlug = `1107-test-ordinary-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
112
- const prdsDir = path.join(projectCwd, 'session-manager-operations', 'scheduler', 'prds');
141
+ // Epic-scoped, not the legacy flat prds/ dir: the flat dir is retired and
142
+ // swept into prds-archived/ by consolidateFlatPrds the moment a job is no
143
+ // longer pending/running, which would silently strip this PRD's
144
+ // `quietMachine: true` frontmatter from the row on the very next
145
+ // reconcile() refresh (or drop the row entirely once terminal — see
146
+ // scheduler-reconcile-history-backfill.test.cjs).
147
+ const prdsDir = path.join(projectCwd, 'session-manager-operations', 'scheduler', 'epics', 'test-fixture-epic', 'prds');
113
148
  fs.mkdirSync(prdsDir, { recursive: true });
114
149
  fs.writeFileSync(path.join(prdsDir, `${quietSlug}.md`), '---\nquietMachine: true\n---\nMeasure timing without contention, then get killed.', 'utf8');
115
150
  fs.writeFileSync(path.join(prdsDir, `${ordinarySlug}.md`), 'An ordinary PRD queued behind the quiet job.', 'utf8');
@@ -5,6 +5,13 @@
5
5
  * resume timer when both the log and the billing endpoint come up empty
6
6
  * (the live failure mode: the usage endpoint itself 429s).
7
7
  *
8
+ * Also (usage-meter circuit PRD): neither function may arm a PAST reset —
9
+ * resolveRateLimitPauseReset/computeEffectiveResumeAt now reject a stale ISO
10
+ * via usageCircuit.isResetFresh and fall back to the bounded 30-MINUTE pause
11
+ * instead. A stale reset used to survive straight into computeResumeDelay's
12
+ * `Math.max(30_000, <negative>)`, arming a 30-SECOND nap that spun the queue
13
+ * right back into the same still-active rate limit.
14
+ *
8
15
  * These test the PURE decision functions scheduler.cjs's setPaused() and the
9
16
  * spawnJob() rateLimited branch delegate to — never the real setPaused/mutate
10
17
  * path, which writes the machine's live scheduler queue.json at a fixed
@@ -21,9 +28,11 @@ const assert = require('node:assert/strict');
21
28
  const path = require('node:path');
22
29
  const {
23
30
  resolveRateLimitPauseReset,
31
+ billingResetForPause,
24
32
  computeEffectiveResumeAt,
25
33
  computeResumeDelay,
26
34
  } = require('../scheduler.cjs');
35
+ const billing = require('../usage.cjs');
27
36
 
28
37
  // Repo-tracked, byte-for-byte copy of the real 2026-09-05 starry-night-ships
29
38
  // incident log (see src/main/lib/__tests__/rateLimitWindow.test.cjs for the
@@ -32,37 +41,84 @@ const {
32
41
  const FIXTURE = path.join(__dirname, '..', 'lib', '__tests__', 'fixtures', '204-mercury-steam-horse.log.txt');
33
42
  const FIVE_HOUR_ISO = new Date(1788643800 * 1000).toISOString(); // what refreshNextReset() would have returned
34
43
  const SEVEN_DAY_ISO = new Date(1789059600 * 1000).toISOString(); // the actually-binding window
44
+ // Fixed "now" contemporaneous with the fixture's own timestamps (BEFORE both
45
+ // resets above), so freshness checks read the fixture the way it looked at
46
+ // incident time — not relative to whenever this suite happens to run.
47
+ const FIXTURE_NOW = Date.parse('2026-09-05T16:32:00.000Z');
35
48
 
36
49
  test('resolveRateLimitPauseReset: prefers the log-derived seven-day reset over the billing five_hour reset', () => {
37
- const resetIso = resolveRateLimitPauseReset(FIXTURE, FIVE_HOUR_ISO);
50
+ const resetIso = resolveRateLimitPauseReset(FIXTURE, FIVE_HOUR_ISO, FIXTURE_NOW);
38
51
  assert.strictEqual(resetIso, SEVEN_DAY_ISO);
39
52
  assert.notStrictEqual(resetIso, FIVE_HOUR_ISO);
40
53
  });
41
54
 
42
55
  test('resolveRateLimitPauseReset: falls back to the billing reset when the log yields none', () => {
43
- const resetIso = resolveRateLimitPauseReset('/nonexistent/path/job.log', FIVE_HOUR_ISO);
56
+ const resetIso = resolveRateLimitPauseReset('/nonexistent/path/job.log', FIVE_HOUR_ISO, FIXTURE_NOW);
44
57
  assert.strictEqual(resetIso, FIVE_HOUR_ISO);
45
58
  });
46
59
 
47
60
  test('resolveRateLimitPauseReset: null when BOTH the log and the billing endpoint are unavailable', () => {
48
- assert.strictEqual(resolveRateLimitPauseReset('/nonexistent/path/job.log', null), null);
61
+ assert.strictEqual(resolveRateLimitPauseReset('/nonexistent/path/job.log', null, FIXTURE_NOW), null);
62
+ });
63
+
64
+ test('resolveRateLimitPauseReset: a PAST log-derived reset is rejected, not returned stale', () => {
65
+ // "now" is AFTER both fixture resets have already elapsed.
66
+ const longAfter = Date.parse('2026-09-15T00:00:00.000Z');
67
+ const resetIso = resolveRateLimitPauseReset(FIXTURE, FIVE_HOUR_ISO, longAfter);
68
+ assert.strictEqual(resetIso, null);
69
+ });
70
+
71
+ test('resolveRateLimitPauseReset: log yields nothing and the billing reset has already passed -> null, not the stale value', () => {
72
+ const longAfter = Date.parse('2026-09-15T00:00:00.000Z');
73
+ const resetIso = resolveRateLimitPauseReset('/nonexistent/path/job.log', FIVE_HOUR_ISO, longAfter);
74
+ assert.strictEqual(resetIso, null);
49
75
  });
50
76
 
51
77
  test('computeEffectiveResumeAt: rate_limit with no resumeAt gets the same bounded 30-minute fallback as network', () => {
52
- const now = Date.parse('2026-09-05T16:32:00.000Z');
78
+ const now = FIXTURE_NOW;
53
79
  const resumeAt = computeEffectiveResumeAt('rate_limit', null, now);
54
80
  assert.strictEqual(resumeAt, new Date(now + 30 * 60_000).toISOString());
55
81
  });
56
82
 
57
- test('computeEffectiveResumeAt: an explicit resumeAt (the log-derived reset) is never overridden', () => {
58
- const resumeAt = computeEffectiveResumeAt('rate_limit', SEVEN_DAY_ISO, Date.now());
83
+ test('computeEffectiveResumeAt: a FRESH explicit resumeAt (the log-derived reset) is never overridden', () => {
84
+ const resumeAt = computeEffectiveResumeAt('rate_limit', SEVEN_DAY_ISO, FIXTURE_NOW);
59
85
  assert.strictEqual(resumeAt, SEVEN_DAY_ISO);
60
86
  });
61
87
 
88
+ test('computeEffectiveResumeAt: a PAST resumeAt gets the 30-minute fallback, not a ~30-second nap', () => {
89
+ // now is well AFTER SEVEN_DAY_ISO has already elapsed — the exact shape of
90
+ // the bug: Math.max(30_000, <negative>) used to arm a 30-second resume.
91
+ const longAfter = Date.parse('2026-09-15T00:00:00.000Z');
92
+ const resumeAt = computeEffectiveResumeAt('rate_limit', SEVEN_DAY_ISO, longAfter);
93
+ assert.strictEqual(resumeAt, new Date(longAfter + 30 * 60_000).toISOString());
94
+ assert.notStrictEqual(resumeAt, SEVEN_DAY_ISO);
95
+ const { delayMs } = computeResumeDelay(resumeAt, longAfter);
96
+ assert.ok(delayMs >= 30 * 60_000, `expected >= 30 minutes, got ${delayMs}ms`);
97
+ });
98
+
62
99
  test('computeEffectiveResumeAt: other reasons (e.g. auth) get no fallback — stays indefinite', () => {
63
100
  assert.strictEqual(computeEffectiveResumeAt('auth', null, Date.now()), null);
64
101
  });
65
102
 
103
+ test('billingResetForPause: skips refreshNextReset() (no billing.fetchUsage call) while the shared circuit is OPEN', async () => {
104
+ const originalFetchUsage = billing.fetchUsage;
105
+ let fetchCalls = 0;
106
+ billing.fetchUsage = async () => { fetchCalls++; return { kind: 'ok', data: { usage: {} } }; };
107
+ try {
108
+ // Force the shared circuit open (3 consecutive failures), same threshold usageCircuit.cjs uses.
109
+ billing.usageCircuit.recordFailure('a');
110
+ billing.usageCircuit.recordFailure('a');
111
+ billing.usageCircuit.recordFailure('a');
112
+ assert.strictEqual(billing.usageCircuit.state(), 'open');
113
+
114
+ await billingResetForPause();
115
+ assert.strictEqual(fetchCalls, 0, 'refreshNextReset() must not call billing.fetchUsage() while the circuit is open');
116
+ } finally {
117
+ billing.usageCircuit.recordSuccess({}); // close the shared circuit back up for later tests/files
118
+ billing.fetchUsage = originalFetchUsage;
119
+ }
120
+ });
121
+
66
122
  test('computeResumeDelay: a seven-day-out reset (~5 days from now) is armed, not treated as too-far', () => {
67
123
  const now = Date.parse('2026-09-05T16:32:00.000Z');
68
124
  // ~5 days out — comfortably under the 0x7fffffff (~24.8 day) setTimeout cap.
@@ -27,6 +27,7 @@ import { test, expect, beforeAll, afterAll, afterEach } from 'vitest';
27
27
  const fs = require('node:fs');
28
28
  const os = require('node:os');
29
29
  const path = require('node:path');
30
+ const claudeStub = require('../../../tests/helpers/claudeStub.cjs');
30
31
  const { execFileSync } = require('node:child_process');
31
32
 
32
33
  let tmpHome;
@@ -92,15 +93,12 @@ function writeProjectQueue(cwd, jobs) {
92
93
  // non-zero almost instantly, and bumps a counter file each invocation so the
93
94
  // test can assert exactly how many times it was actually spawned.
94
95
  function writeAlways429ClaudeStub(counterPath) {
95
- const stubPath = path.join(os.tmpdir(), `sm-claude-stub-429-${process.pid}-${Math.floor(Math.random() * 1e9)}.cjs`);
96
- const body = `
96
+ return claudeStub.writeClaudeStub({ body: `
97
97
  const fs = require('fs');
98
98
  fs.appendFileSync(${JSON.stringify(counterPath)}, 'x');
99
99
  process.stdout.write(JSON.stringify({ type: 'result', subtype: 'error', is_error: true, api_error_status: 429 }) + '\\n');
100
100
  process.exit(1);
101
- `;
102
- fs.writeFileSync(stubPath, `#!${process.execPath}\n${body}\n`, { mode: 0o755 });
103
- return stubPath;
101
+ ` });
104
102
  }
105
103
 
106
104
  test('a manual Resume immediately followed by a permanently-429ing job never produces a runaway dispatch loop', async () => {