claude-code-session-manager 0.98.0 → 0.100.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 (431) hide show
  1. package/README.md +3 -3
  2. package/bin/cli.cjs +16 -4
  3. package/bin/node-floor.cjs +41 -0
  4. package/dist/assets/{DataModel-DCEgMTjH.js → DataModel-HdR0AxFq.js} +1 -1
  5. package/dist/assets/{History-Bfdoywlh.js → History-Dr4Jr_5z.js} +2 -2
  6. package/dist/assets/{Hooks-CKDJjTJV.js → Hooks-NhrvH3JP.js} +3 -3
  7. package/dist/assets/{Library-7akfgy67.js → Library-Dv_EtIMr.js} +1 -1
  8. package/dist/assets/{MarkdownEditor-DDesiqet.js → MarkdownEditor-DTKDWV_n.js} +1 -1
  9. package/dist/assets/{McpServers-D4lZtXof.js → McpServers-M034G9B3.js} +2 -2
  10. package/dist/assets/{Memory-CeI8G1w5.js → Memory-DzF1B4gx.js} +6 -6
  11. package/dist/assets/{Permissions-BGleLja4.js → Permissions-DrgbSGWl.js} +3 -3
  12. package/dist/assets/{Plugins-P4eCXy_S.js → Plugins-Bd3ef35w.js} +2 -2
  13. package/dist/assets/{ProvenanceBadge-BFczzI1B.js → ProvenanceBadge-DcBHT1Iy.js} +1 -1
  14. package/dist/assets/{SaveBar-l9jnEMES.js → SaveBar-rimJem-Y.js} +1 -1
  15. package/dist/assets/Scheduler-DjgFJl2w.js +16 -0
  16. package/dist/assets/{ScopeSwitcher-CjJi8ST5.js → ScopeSwitcher-CnAzTBrl.js} +1 -1
  17. package/dist/assets/Settings-Dc1F_sSg.js +3 -0
  18. package/dist/assets/{SkillReferenceGraph-CjbO01Sg.js → SkillReferenceGraph-B9aZDecM.js} +1 -1
  19. package/dist/assets/{Skills-BxZ9_fCR.js → Skills-DRXd18gu.js} +2 -2
  20. package/dist/assets/{SystemPrompt-D8vfFb8b.js → SystemPrompt-ahy5-Fwe.js} +1 -1
  21. package/dist/assets/{TagLibrary-2vzUo7YL.js → TagLibrary-C_tToDeL.js} +1 -1
  22. package/dist/assets/{TiptapBody-B8ZfbK90.js → TiptapBody-DvdefS6K.js} +1 -1
  23. package/dist/assets/{Toggle-BbW4nGGf.js → Toggle-6Jl5Njc6.js} +1 -1
  24. package/dist/assets/{index-CKH5Uxik.css → index-Bta-hwud.css} +1 -1
  25. package/dist/assets/{index-Cd98Q1lD.js → index-vx8O73l8.js} +497 -502
  26. package/dist/assets/{settingsSchema-IhUfucCF.js → settingsSchema-D6iRguNV.js} +1 -1
  27. package/dist/index.html +2 -2
  28. package/package.json +25 -29
  29. package/plugins/CLAUDE.md +6 -6
  30. package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +21 -19
  31. package/plugins/session-manager-dev/skills/builder/SKILL.md +3 -3
  32. package/plugins/session-manager-dev/skills/develop/SKILL.md +254 -510
  33. package/plugins/session-manager-dev/skills/develop/standards.md +17 -22
  34. package/scripts/README.md +3 -11
  35. package/scripts/audit-ops-hygiene.cjs +3 -3
  36. package/scripts/hooks/lib/guard-prd-writes-policy.cjs +1 -1
  37. package/scripts/hooks/lib/guard-self-schedule-policy.cjs +1 -1
  38. package/scripts/mint-epic.cjs +2 -3
  39. package/scripts/scheduler-mcp-server.cjs +36 -7
  40. package/src/main/agentLibrary.cjs +22 -2
  41. package/src/main/build-info.json +4 -4
  42. package/src/main/config.cjs +41 -38
  43. package/src/main/docEdit.cjs +4 -1
  44. package/src/main/files.cjs +2 -5
  45. package/src/main/health.cjs +1 -1
  46. package/src/main/index.cjs +23 -10
  47. package/src/main/ipcSchemas.cjs +28 -35
  48. package/src/main/lib/agentPersonaSchema.cjs +13 -2
  49. package/src/main/lib/atomicFs.cjs +116 -0
  50. package/src/main/lib/branchSweep.cjs +13 -12
  51. package/src/main/lib/buildTarget.cjs +3 -3
  52. package/src/main/lib/claudeCliCaps.cjs +129 -0
  53. package/src/main/lib/credentials.cjs +2 -4
  54. package/src/main/lib/crossProjectFeedback.cjs +3 -3
  55. package/src/main/lib/cwdClassify.cjs +15 -3
  56. package/src/main/lib/definitionOfDone.cjs +207 -125
  57. package/src/main/lib/dodDrainHook.cjs +11 -5
  58. package/src/main/lib/epicMint.cjs +2 -4
  59. package/src/main/lib/epicStatusMirror.cjs +8 -10
  60. package/src/main/lib/epicWorktreeProjectConfig.cjs +2 -4
  61. package/src/main/lib/gateAuthority.cjs +96 -0
  62. package/src/main/lib/gitExec.cjs +69 -0
  63. package/src/main/lib/gitWorktree.cjs +656 -92
  64. package/src/main/lib/instanceLock.cjs +5 -8
  65. package/src/main/lib/launchFailure.cjs +2 -3
  66. package/src/main/lib/macroLibrary.cjs +386 -0
  67. package/src/main/lib/mcpToolCatalog.cjs +26 -17
  68. package/src/main/lib/needsReviewLedger.cjs +1 -1
  69. package/src/main/lib/opsErrorLog.cjs +11 -1
  70. package/src/main/lib/opsOwnership.cjs +0 -5
  71. package/src/main/lib/pidAlive.cjs +32 -0
  72. package/src/main/lib/prdCreate.cjs +121 -12
  73. package/src/main/lib/prdDisposition.cjs +2 -2
  74. package/src/main/lib/prdGateFiles.cjs +284 -0
  75. package/src/main/lib/prdLocations.cjs +61 -0
  76. package/src/main/lib/prdMigration.cjs +2 -3
  77. package/src/main/lib/prdSizing.cjs +2 -1
  78. package/src/main/lib/promptSessionSchema.cjs +10 -2
  79. package/src/main/lib/rcaReport.cjs +4 -6
  80. package/src/main/lib/reaperHelpers.cjs +8 -1
  81. package/src/main/lib/reservationExpiry.cjs +6 -1
  82. package/src/main/lib/reviewNotice.cjs +263 -0
  83. package/src/main/lib/runClaudeP.cjs +4 -1
  84. package/src/main/lib/schedulerPaths.cjs +22 -2
  85. package/src/main/lib/sessionSlots.cjs +2 -4
  86. package/src/main/lib/shippedPersonaSeeds.cjs +179 -0
  87. package/src/main/lib/timeoutShim.cjs +123 -0
  88. package/src/main/lib/timeoutShimScript.cjs +275 -0
  89. package/src/main/lib/upgradeDrain.cjs +4 -10
  90. package/src/main/lib/watchdogHelpers.cjs +12 -25
  91. package/src/main/lib/workTypeLibrary.cjs +7 -1
  92. package/src/main/promptSessionEvents.cjs +37 -13
  93. package/src/main/scheduler.cjs +910 -198
  94. package/src/main/seedAgentPersonas.cjs +278 -23
  95. package/src/main/supervisor.cjs +4 -1
  96. package/src/main/templates/PRD_AUTHORING.md +126 -406
  97. package/src/preload/__tests__/preload-surface.test.cjs +68 -0
  98. package/src/preload/api.d.ts +41 -94
  99. package/src/preload/index.cjs +14 -9
  100. package/src/seed/agents/architect.md +1 -0
  101. package/src/seed/agents/dev-lead.md +17 -18
  102. package/src/seed/agents/project-home-builder.md +1 -0
  103. package/src/seed/agents/validator.md +10 -4
  104. package/dist/assets/HostBilko-CDVHE0hT.js +0 -1
  105. package/dist/assets/Scheduler-C_phpeUS.js +0 -16
  106. package/dist/assets/Settings-BIwZ3FGQ.js +0 -3
  107. package/src/main/__tests__/activeIndexMerge.test.cjs +0 -235
  108. package/src/main/__tests__/agentEffortResolve.test.cjs +0 -117
  109. package/src/main/__tests__/agentLibrary.test.cjs +0 -276
  110. package/src/main/__tests__/agentModelResolve.test.cjs +0 -311
  111. package/src/main/__tests__/agentOverlayWrite.test.cjs +0 -95
  112. package/src/main/__tests__/bilkoHost-deriveSlug.test.cjs +0 -26
  113. package/src/main/__tests__/bilkoHost-integration.test.cjs +0 -118
  114. package/src/main/__tests__/bilkoHostCore.test.cjs +0 -72
  115. package/src/main/__tests__/broadcastCoalescer.test.cjs +0 -122
  116. package/src/main/__tests__/chat-cancel-terminal.test.cjs +0 -120
  117. package/src/main/__tests__/chat-dead-channels.test.cjs +0 -63
  118. package/src/main/__tests__/chat-exit-close-race.test.cjs +0 -146
  119. package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +0 -139
  120. package/src/main/__tests__/chat-preamble-anchors.test.cjs +0 -100
  121. package/src/main/__tests__/chat-queue.test.cjs +0 -97
  122. package/src/main/__tests__/chat-stop-signal.test.cjs +0 -89
  123. package/src/main/__tests__/chatRunner-epic-worktree-execcwd.test.cjs +0 -125
  124. package/src/main/__tests__/chatRunner-session-flag-retry.test.cjs +0 -252
  125. package/src/main/__tests__/classifyPromptTicket.test.cjs +0 -101
  126. package/src/main/__tests__/classifyTranscriptLine.test.cjs +0 -201
  127. package/src/main/__tests__/computeDepHistorySatisfaction.test.cjs +0 -66
  128. package/src/main/__tests__/config-readText-bounded.test.cjs +0 -84
  129. package/src/main/__tests__/configWriteBoundaryOwners.test.cjs +0 -59
  130. package/src/main/__tests__/crossProjectFeedback.test.cjs +0 -334
  131. package/src/main/__tests__/crossProjectFeedbackRoutes.test.cjs +0 -161
  132. package/src/main/__tests__/dep-orphan-archive-health.test.cjs +0 -77
  133. package/src/main/__tests__/develop-skill-failure-modes.test.cjs +0 -70
  134. package/src/main/__tests__/docEdit.test.cjs +0 -244
  135. package/src/main/__tests__/dod-batchkey.test.cjs +0 -183
  136. package/src/main/__tests__/dod-drain-hook.test.cjs +0 -302
  137. package/src/main/__tests__/dod-report.test.cjs +0 -304
  138. package/src/main/__tests__/dod-reverify.test.cjs +0 -285
  139. package/src/main/__tests__/epicContextDigest.test.cjs +0 -174
  140. package/src/main/__tests__/epicMint.test.cjs +0 -332
  141. package/src/main/__tests__/epicMintTelemetryTap.test.cjs +0 -64
  142. package/src/main/__tests__/epicStatusMirror.test.cjs +0 -110
  143. package/src/main/__tests__/epicValidationHook.test.cjs +0 -291
  144. package/src/main/__tests__/exchanges.test.cjs +0 -122
  145. package/src/main/__tests__/exchangesPromptId.test.cjs +0 -61
  146. package/src/main/__tests__/extractJson.test.cjs +0 -51
  147. package/src/main/__tests__/files-reject-credentials.test.cjs +0 -40
  148. package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +0 -556
  149. package/src/main/__tests__/flatPrdTickSweep.test.cjs +0 -110
  150. package/src/main/__tests__/health-build-freshness.test.cjs +0 -39
  151. package/src/main/__tests__/health-claude-md-budget.test.cjs +0 -57
  152. package/src/main/__tests__/health-credentials.test.cjs +0 -81
  153. package/src/main/__tests__/health-delegation-chain.test.cjs +0 -124
  154. package/src/main/__tests__/health-per-project-stall.test.cjs +0 -84
  155. package/src/main/__tests__/health-prd-migration.test.cjs +0 -37
  156. package/src/main/__tests__/health-queue-dispatch.test.cjs +0 -135
  157. package/src/main/__tests__/health-starve-escalation.test.cjs +0 -94
  158. package/src/main/__tests__/health-tick-liveness.test.cjs +0 -179
  159. package/src/main/__tests__/health-usage-poller.test.cjs +0 -144
  160. package/src/main/__tests__/health-worktree-cap-blocked.test.cjs +0 -65
  161. package/src/main/__tests__/heapSnapshot.test.cjs +0 -121
  162. package/src/main/__tests__/historyAggregatorIntraday.test.cjs +0 -313
  163. package/src/main/__tests__/historyDashboard.test.cjs +0 -163
  164. package/src/main/__tests__/historyRollup.test.cjs +0 -333
  165. package/src/main/__tests__/intradayRefresh.test.cjs +0 -39
  166. package/src/main/__tests__/ipcSchemas-dependsOn.test.cjs +0 -39
  167. package/src/main/__tests__/kg-augment.test.cjs +0 -195
  168. package/src/main/__tests__/loadGateDetailTick.test.cjs +0 -31
  169. package/src/main/__tests__/machineProfile.test.cjs +0 -152
  170. package/src/main/__tests__/mcpStatus.test.cjs +0 -61
  171. package/src/main/__tests__/memoryAggregate.test.cjs +0 -109
  172. package/src/main/__tests__/memoryStale.test.cjs +0 -88
  173. package/src/main/__tests__/needsReviewLedger.test.cjs +0 -162
  174. package/src/main/__tests__/openExternalApp-spawn-error.test.cjs +0 -25
  175. package/src/main/__tests__/opsErrorLog.test.cjs +0 -109
  176. package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +0 -173
  177. package/src/main/__tests__/personaMerge.test.cjs +0 -169
  178. package/src/main/__tests__/planValidator.test.cjs +0 -125
  179. package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +0 -176
  180. package/src/main/__tests__/prd-group-allocator.test.cjs +0 -119
  181. package/src/main/__tests__/prdAdminRouteParity.test.cjs +0 -70
  182. package/src/main/__tests__/prdAdminRoutes.test.cjs +0 -718
  183. package/src/main/__tests__/prdAgentType.test.cjs +0 -103
  184. package/src/main/__tests__/prdAuthoringSeed.test.cjs +0 -39
  185. package/src/main/__tests__/prdCreate.test.cjs +0 -979
  186. package/src/main/__tests__/prdCreateAdoption.test.cjs +0 -205
  187. package/src/main/__tests__/prdCreateDisposition.test.cjs +0 -201
  188. package/src/main/__tests__/prdCreatePlanId.test.cjs +0 -132
  189. package/src/main/__tests__/prdFrontmatterAgentType.test.cjs +0 -117
  190. package/src/main/__tests__/prdFrontmatterDependsOn.test.cjs +0 -136
  191. package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +0 -125
  192. package/src/main/__tests__/prdFrontmatterQuietMachine.test.cjs +0 -108
  193. package/src/main/__tests__/prdLocations.test.cjs +0 -195
  194. package/src/main/__tests__/prdLocationsArchived.test.cjs +0 -201
  195. package/src/main/__tests__/prdMigration.test.cjs +0 -349
  196. package/src/main/__tests__/prdMigrationLegacyAdopt.test.cjs +0 -91
  197. package/src/main/__tests__/prdParserHighWater.test.cjs +0 -74
  198. package/src/main/__tests__/prdParserSourcePromptId.test.cjs +0 -65
  199. package/src/main/__tests__/prdSetDisposition.test.cjs +0 -222
  200. package/src/main/__tests__/prdSizing.test.cjs +0 -106
  201. package/src/main/__tests__/prdSourcePromptIdBackfill.test.cjs +0 -118
  202. package/src/main/__tests__/prdUpdateDependsOn.test.cjs +0 -160
  203. package/src/main/__tests__/proc-role-env.test.cjs +0 -125
  204. package/src/main/__tests__/procname-claude-spawn-sites.test.cjs +0 -304
  205. package/src/main/__tests__/procname-sm-processes.test.cjs +0 -127
  206. package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +0 -177
  207. package/src/main/__tests__/projectPages.test.cjs +0 -137
  208. package/src/main/__tests__/promptSessionEvents.test.cjs +0 -234
  209. package/src/main/__tests__/promptSessionSchema.test.cjs +0 -101
  210. package/src/main/__tests__/promptSessionTranscript.test.cjs +0 -0
  211. package/src/main/__tests__/promptSessionsCreateEpicHandler.test.cjs +0 -159
  212. package/src/main/__tests__/pty-epic-worktree-spawn-cwd.test.cjs +0 -282
  213. package/src/main/__tests__/pty-session-open-telemetry.test.cjs +0 -96
  214. package/src/main/__tests__/pty-write-result.test.cjs +0 -46
  215. package/src/main/__tests__/queue-health-verdict.test.cjs +0 -170
  216. package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +0 -286
  217. package/src/main/__tests__/queue-starvation-per-project.test.cjs +0 -147
  218. package/src/main/__tests__/queueHistory.test.cjs +0 -355
  219. package/src/main/__tests__/queueOps-interactive-ac-lint.test.cjs +0 -65
  220. package/src/main/__tests__/queueOpsArchiveDestination.test.cjs +0 -65
  221. package/src/main/__tests__/queueOpsAutoArchive.test.cjs +0 -211
  222. package/src/main/__tests__/rateLimitPollerStreak.test.cjs +0 -128
  223. package/src/main/__tests__/rcaReport.test.cjs +0 -266
  224. package/src/main/__tests__/reconcileFlatPrdSweep.test.cjs +0 -119
  225. package/src/main/__tests__/reconcileTiming.test.cjs +0 -135
  226. package/src/main/__tests__/runLogRetention.test.cjs +0 -489
  227. package/src/main/__tests__/runVerify-atomic-verdicts.test.cjs +0 -26
  228. package/src/main/__tests__/runVerify-blocked-by-foreign-wip.test.cjs +0 -58
  229. package/src/main/__tests__/runVerify-landed-commit-outranks.test.cjs +0 -191
  230. package/src/main/__tests__/runVerify-policy-denial.test.cjs +0 -89
  231. package/src/main/__tests__/runVerify-transcript-commit-evidence.test.cjs +0 -225
  232. package/src/main/__tests__/runVerify.test.cjs +0 -1784
  233. package/src/main/__tests__/scheduleJobSchema.test.cjs +0 -127
  234. package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +0 -65
  235. package/src/main/__tests__/scheduleJobTransitions.test.cjs +0 -277
  236. package/src/main/__tests__/scheduleJobTransitionsGrep.test.cjs +0 -59
  237. package/src/main/__tests__/scheduleJobTransitionsTelemetryTap.test.cjs +0 -72
  238. package/src/main/__tests__/scheduler-admin-routes.test.cjs +0 -199
  239. package/src/main/__tests__/scheduler-adopted-run-supervision.test.cjs +0 -143
  240. package/src/main/__tests__/scheduler-already-satisfied-on-main.test.cjs +0 -105
  241. package/src/main/__tests__/scheduler-archive-completed-prd.test.cjs +0 -102
  242. package/src/main/__tests__/scheduler-archived-twin-guard.test.cjs +0 -155
  243. package/src/main/__tests__/scheduler-autofix-outcome.test.cjs +0 -188
  244. package/src/main/__tests__/scheduler-autofix-select.test.cjs +0 -439
  245. package/src/main/__tests__/scheduler-autopromote.test.cjs +0 -51
  246. package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +0 -101
  247. package/src/main/__tests__/scheduler-blocked-by-foreign-wip.test.cjs +0 -107
  248. package/src/main/__tests__/scheduler-boot-orphans.test.cjs +0 -153
  249. package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +0 -121
  250. package/src/main/__tests__/scheduler-clear-queue-history.test.cjs +0 -134
  251. package/src/main/__tests__/scheduler-commit-guard-noop.test.cjs +0 -244
  252. package/src/main/__tests__/scheduler-committed-in-window.test.cjs +0 -182
  253. package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +0 -43
  254. package/src/main/__tests__/scheduler-default-eligible-heal.test.cjs +0 -168
  255. package/src/main/__tests__/scheduler-dispatch-loop.test.cjs +0 -58
  256. package/src/main/__tests__/scheduler-effective-concurrency.test.cjs +0 -81
  257. package/src/main/__tests__/scheduler-epic-digest.test.cjs +0 -227
  258. package/src/main/__tests__/scheduler-failed-autoreset.test.cjs +0 -121
  259. package/src/main/__tests__/scheduler-finalize-dispatch-guards.test.cjs +0 -229
  260. package/src/main/__tests__/scheduler-find-prd-dir.test.cjs +0 -75
  261. package/src/main/__tests__/scheduler-fix-plan-path.test.cjs +0 -119
  262. package/src/main/__tests__/scheduler-force-tick-outcome.test.cjs +0 -46
  263. package/src/main/__tests__/scheduler-foreign-wip-manifest.test.cjs +0 -78
  264. package/src/main/__tests__/scheduler-gate-shadow.test.cjs +0 -119
  265. package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +0 -390
  266. package/src/main/__tests__/scheduler-heal-refusal.test.cjs +0 -61
  267. package/src/main/__tests__/scheduler-heartbeat-payload.test.cjs +0 -80
  268. package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +0 -323
  269. package/src/main/__tests__/scheduler-integration-failure-stamp.test.cjs +0 -41
  270. package/src/main/__tests__/scheduler-investigation-clean-skip.test.cjs +0 -63
  271. package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +0 -123
  272. package/src/main/__tests__/scheduler-job-budget.test.cjs +0 -172
  273. package/src/main/__tests__/scheduler-job-overrun.test.cjs +0 -175
  274. package/src/main/__tests__/scheduler-launch-failure.test.cjs +0 -199
  275. package/src/main/__tests__/scheduler-leftover-fields.test.cjs +0 -52
  276. package/src/main/__tests__/scheduler-leftover-quarantine.test.cjs +0 -199
  277. package/src/main/__tests__/scheduler-looks-done.test.cjs +0 -537
  278. package/src/main/__tests__/scheduler-manual-pause.test.cjs +0 -118
  279. package/src/main/__tests__/scheduler-mechanical-recovery.test.cjs +0 -245
  280. package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +0 -46
  281. package/src/main/__tests__/scheduler-mutate-reentrancy.test.cjs +0 -117
  282. package/src/main/__tests__/scheduler-needs-review-autoresolve.test.cjs +0 -197
  283. package/src/main/__tests__/scheduler-never-stop.test.cjs +0 -157
  284. package/src/main/__tests__/scheduler-no-dead-end-status.test.cjs +0 -152
  285. package/src/main/__tests__/scheduler-no-orphan-run-dir.test.cjs +0 -81
  286. package/src/main/__tests__/scheduler-notify-originating-tab-transcript.test.cjs +0 -87
  287. package/src/main/__tests__/scheduler-notify-originating-tab.test.cjs +0 -343
  288. package/src/main/__tests__/scheduler-periodic-reverify-guard.test.cjs +0 -217
  289. package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +0 -164
  290. package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +0 -161
  291. package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +0 -178
  292. package/src/main/__tests__/scheduler-quarantine-autoresolve.test.cjs +0 -165
  293. package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +0 -257
  294. package/src/main/__tests__/scheduler-rate-limit-cooldown-freshness.test.cjs +0 -123
  295. package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +0 -152
  296. package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +0 -156
  297. package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +0 -754
  298. package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +0 -87
  299. package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +0 -100
  300. package/src/main/__tests__/scheduler-reconcile-history-backfill.test.cjs +0 -105
  301. package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +0 -203
  302. package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +0 -247
  303. package/src/main/__tests__/scheduler-reset-job-fields-guard.test.cjs +0 -77
  304. package/src/main/__tests__/scheduler-resume-recovery.test.cjs +0 -254
  305. package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +0 -115
  306. package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +0 -380
  307. package/src/main/__tests__/scheduler-sigterm-commit.test.cjs +0 -43
  308. package/src/main/__tests__/scheduler-stall-per-project.test.cjs +0 -126
  309. package/src/main/__tests__/scheduler-starve-escalation.test.cjs +0 -154
  310. package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +0 -252
  311. package/src/main/__tests__/scheduler-stranded-investigation.test.cjs +0 -185
  312. package/src/main/__tests__/scheduler-stuck-failed-escalation.test.cjs +0 -136
  313. package/src/main/__tests__/scheduler-supervisor-record.test.cjs +0 -81
  314. package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +0 -54
  315. package/src/main/__tests__/scheduler-tick-wedge.test.cjs +0 -172
  316. package/src/main/__tests__/scheduler-transient-failure.test.cjs +0 -141
  317. package/src/main/__tests__/scheduler-unreadable-queue-guard.test.cjs +0 -62
  318. package/src/main/__tests__/scheduler-utilization-hold.test.cjs +0 -89
  319. package/src/main/__tests__/scheduler-verify-prd-path.test.cjs +0 -109
  320. package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +0 -234
  321. package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +0 -120
  322. package/src/main/__tests__/scheduler-writeprd-epic-rollback.test.cjs +0 -105
  323. package/src/main/__tests__/schedulerBatchRootBlocker.test.cjs +0 -117
  324. package/src/main/__tests__/schedulerStateSidecarRestore.test.cjs +0 -110
  325. package/src/main/__tests__/seedAgentPersonas.test.cjs +0 -184
  326. package/src/main/__tests__/seedSchedulerMcp.test.cjs +0 -211
  327. package/src/main/__tests__/seedStatus.test.cjs +0 -100
  328. package/src/main/__tests__/seedValidatorPersona.test.cjs +0 -116
  329. package/src/main/__tests__/stop-signal-anchor.test.cjs +0 -75
  330. package/src/main/__tests__/telemetryClient.test.cjs +0 -1055
  331. package/src/main/__tests__/telemetryContract.test.cjs +0 -930
  332. package/src/main/__tests__/telemetrySettings.test.cjs +0 -210
  333. package/src/main/__tests__/transcripts-batch-flush.test.cjs +0 -249
  334. package/src/main/__tests__/transcripts-doFlush-array.test.cjs +0 -124
  335. package/src/main/__tests__/transcripts-paged-reads.test.cjs +0 -241
  336. package/src/main/__tests__/transcripts-worktree-epic-path.test.cjs +0 -154
  337. package/src/main/__tests__/transcriptsUsageFor.test.cjs +0 -206
  338. package/src/main/__tests__/uniquePrdNumbers.test.cjs +0 -153
  339. package/src/main/__tests__/usageSingleFlight.test.cjs +0 -169
  340. package/src/main/__tests__/validationSentinels.test.cjs +0 -84
  341. package/src/main/__tests__/workTypeLibrary.test.cjs +0 -89
  342. package/src/main/bilkoHost.cjs +0 -314
  343. package/src/main/bilkoHostCore.cjs +0 -89
  344. package/src/main/lib/__tests__/active-sessions.test.cjs +0 -251
  345. package/src/main/lib/__tests__/activeIndexRebuild.test.cjs +0 -179
  346. package/src/main/lib/__tests__/agentPersonaSchema.test.cjs +0 -67
  347. package/src/main/lib/__tests__/auditLog.test.cjs +0 -38
  348. package/src/main/lib/__tests__/bootSelfHeal.test.cjs +0 -107
  349. package/src/main/lib/__tests__/branchSweep.test.cjs +0 -164
  350. package/src/main/lib/__tests__/buildIdentity.test.cjs +0 -121
  351. package/src/main/lib/__tests__/buildTarget.test.cjs +0 -52
  352. package/src/main/lib/__tests__/childWithLog.test.cjs +0 -321
  353. package/src/main/lib/__tests__/coldBootPromptSessionsWrite.test.cjs +0 -87
  354. package/src/main/lib/__tests__/crashTelemetry.test.cjs +0 -103
  355. package/src/main/lib/__tests__/credentials-futile-refresh.test.cjs +0 -115
  356. package/src/main/lib/__tests__/cwdClassify.test.cjs +0 -111
  357. package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +0 -95
  358. package/src/main/lib/__tests__/delegationReadiness.test.cjs +0 -1175
  359. package/src/main/lib/__tests__/dispatchLoop.test.cjs +0 -63
  360. package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +0 -244
  361. package/src/main/lib/__tests__/ephemeralCwd.test.cjs +0 -91
  362. package/src/main/lib/__tests__/epicDelegationStats.test.cjs +0 -137
  363. package/src/main/lib/__tests__/epicSpawnCwd.test.cjs +0 -283
  364. package/src/main/lib/__tests__/epicSpawnPlan.test.cjs +0 -196
  365. package/src/main/lib/__tests__/epicTranscriptPath.test.cjs +0 -163
  366. package/src/main/lib/__tests__/epicWorktreeBoot.test.cjs +0 -136
  367. package/src/main/lib/__tests__/epicWorktreeMerge.test.cjs +0 -130
  368. package/src/main/lib/__tests__/epicWorktreeMint.test.cjs +0 -117
  369. package/src/main/lib/__tests__/epicWorktreeProjectConfig.test.cjs +0 -137
  370. package/src/main/lib/__tests__/fixChainDepth.test.cjs +0 -40
  371. package/src/main/lib/__tests__/fixtures/204-mercury-steam-horse.log.txt +0 -13
  372. package/src/main/lib/__tests__/fixtures/scheduler-machine.json.corrupt-1789147548 +0 -34
  373. package/src/main/lib/__tests__/gateFixtures.json +0 -20
  374. package/src/main/lib/__tests__/gitCacheBound.test.cjs +0 -69
  375. package/src/main/lib/__tests__/gitWorktree.test.cjs +0 -1478
  376. package/src/main/lib/__tests__/gitWorktreeSalvage.test.cjs +0 -107
  377. package/src/main/lib/__tests__/gitWorktreeSalvageDelta.test.cjs +0 -153
  378. package/src/main/lib/__tests__/guardShims.test.cjs +0 -158
  379. package/src/main/lib/__tests__/importReferences.spec.cjs +0 -56
  380. package/src/main/lib/__tests__/instanceLock.test.cjs +0 -173
  381. package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +0 -78
  382. package/src/main/lib/__tests__/jobWorktree.test.cjs +0 -199
  383. package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +0 -82
  384. package/src/main/lib/__tests__/landedSinceRun.test.cjs +0 -133
  385. package/src/main/lib/__tests__/launchFailure.test.cjs +0 -220
  386. package/src/main/lib/__tests__/loadGate.test.cjs +0 -303
  387. package/src/main/lib/__tests__/localAdminHttp.test.cjs +0 -214
  388. package/src/main/lib/__tests__/loopDelay.test.cjs +0 -68
  389. package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +0 -107
  390. package/src/main/lib/__tests__/modelCatalog.test.cjs +0 -202
  391. package/src/main/lib/__tests__/opsOwnership.test.cjs +0 -113
  392. package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +0 -328
  393. package/src/main/lib/__tests__/opsRootNestedWrite.test.cjs +0 -51
  394. package/src/main/lib/__tests__/opsRootResolve.test.cjs +0 -149
  395. package/src/main/lib/__tests__/prdDeclaredPaths.test.cjs +0 -82
  396. package/src/main/lib/__tests__/prdDisposition.test.cjs +0 -224
  397. package/src/main/lib/__tests__/procIdentity.test.cjs +0 -119
  398. package/src/main/lib/__tests__/procName.test.cjs +0 -92
  399. package/src/main/lib/__tests__/projectBriefCore.test.cjs +0 -216
  400. package/src/main/lib/__tests__/projectRootResolve.test.cjs +0 -148
  401. package/src/main/lib/__tests__/queueHealth.test.cjs +0 -58
  402. package/src/main/lib/__tests__/queueStoreAtomicWrite.test.cjs +0 -88
  403. package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +0 -190
  404. package/src/main/lib/__tests__/quietMachineLease.test.cjs +0 -39
  405. package/src/main/lib/__tests__/rateLimitWindow.test.cjs +0 -88
  406. package/src/main/lib/__tests__/reaperHelpers.test.cjs +0 -577
  407. package/src/main/lib/__tests__/schedulerBatchDepends.test.cjs +0 -312
  408. package/src/main/lib/__tests__/schedulerBatchFairness.test.cjs +0 -213
  409. package/src/main/lib/__tests__/schedulerBatchLaunchHold.test.cjs +0 -125
  410. package/src/main/lib/__tests__/schedulerBatchProjectCap.test.cjs +0 -127
  411. package/src/main/lib/__tests__/schedulerBatchQuietMachine.test.cjs +0 -109
  412. package/src/main/lib/__tests__/schedulerMcpServerHeadlessRefusal.test.cjs +0 -71
  413. package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +0 -216
  414. package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +0 -183
  415. package/src/main/lib/__tests__/schedulerPaths.test.cjs +0 -226
  416. package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +0 -133
  417. package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +0 -56
  418. package/src/main/lib/__tests__/sessionSlots.test.cjs +0 -144
  419. package/src/main/lib/__tests__/telemetryBacklog.test.cjs +0 -626
  420. package/src/main/lib/__tests__/telemetryBoot.test.cjs +0 -134
  421. package/src/main/lib/__tests__/telemetryConsent.test.cjs +0 -136
  422. package/src/main/lib/__tests__/telemetryCounters.test.cjs +0 -57
  423. package/src/main/lib/__tests__/telemetryCountersMetadataColumn.test.cjs +0 -98
  424. package/src/main/lib/__tests__/terminalRunOutcome.test.cjs +0 -200
  425. package/src/main/lib/__tests__/toolUseClassify.test.cjs +0 -53
  426. package/src/main/lib/__tests__/updateCheck.test.cjs +0 -63
  427. package/src/main/lib/__tests__/upgradeDrain.test.cjs +0 -130
  428. package/src/main/lib/__tests__/usageCircuit.test.cjs +0 -354
  429. package/src/main/lib/__tests__/watchdog-helpers.test.cjs +0 -441
  430. package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +0 -266
  431. package/src/main/lib/kgExchangePairing.cjs +0 -75
@@ -1,257 +0,0 @@
1
- /**
2
- * scheduler-quiet-machine-lease.test.cjs — PRD 1107: the exclusive
3
- * quiet-machine lease must be released on EVERY spawnJob exit path,
4
- * including a killed job (SIGKILL/exit 137), not just a clean exit — the
5
- * same finally block that already releases the sessionSlots token. Drives
6
- * spawnJob end-to-end against a real git fixture repo with a stub `claude`
7
- * binary that exits 137 mid-run (same pattern as
8
- * scheduler-inplace-salvage.test.cjs), then asserts:
9
- * 1. the lease is released once the job settles, and
10
- * 2. an ordinary pending job — held behind the lease while it was held —
11
- * is now eligible to dispatch.
12
- *
13
- * Run: timeout 120 npx vitest run src/main/__tests__/scheduler-quiet-machine-lease.test.cjs
14
- */
15
-
16
- 'use strict';
17
-
18
- import { test, expect, beforeAll, afterAll, afterEach } from 'vitest';
19
- const fs = require('node:fs');
20
- const os = require('node:os');
21
- const path = require('node:path');
22
- const claudeStub = require('../../../tests/helpers/claudeStub.cjs');
23
- const { execFileSync } = require('node:child_process');
24
-
25
- let tmpHome;
26
- let originalHome;
27
- let originalDisable;
28
- let spawnJob;
29
- let quietMachineLease;
30
- let pickNextBatch;
31
-
32
- function git(args, cwd) {
33
- return execFileSync('git', args, { cwd, encoding: 'utf8' });
34
- }
35
-
36
- function initRepo(dir) {
37
- fs.mkdirSync(dir, { recursive: true });
38
- git(['init', '-q'], dir);
39
- git(['config', 'user.email', 'test@example.com'], dir);
40
- git(['config', 'user.name', 'Test'], dir);
41
- fs.writeFileSync(path.join(dir, 'README.md'), 'hello\n', 'utf8');
42
- git(['add', '-A'], dir);
43
- git(['commit', '-q', '-m', 'initial'], dir);
44
- }
45
-
46
- // See scheduler-inplace-salvage.test.cjs's identical helper: readQueue()'s
47
- // per-project discovery walks ~/.claude/projects/*/*.jsonl transcripts for a
48
- // `cwd` field to find known project queue.json shards.
49
- function registerActiveProject(cwd) {
50
- const projectsDir = path.join(tmpHome, '.claude', 'projects');
51
- const slugDir = path.join(projectsDir, `fake-project-slug-${path.basename(cwd)}`);
52
- fs.mkdirSync(slugDir, { recursive: true });
53
- fs.writeFileSync(path.join(slugDir, 'transcript.jsonl'), JSON.stringify({ cwd }) + '\n');
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
-
84
- function writeProjectQueue(cwd, jobs) {
85
- const stateDir = path.join(cwd, 'session-manager-operations', 'scheduler', 'state');
86
- fs.mkdirSync(stateDir, { recursive: true });
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
- }
91
- return path.join(stateDir, 'queue.json');
92
- }
93
-
94
- // Stub `claude` binary that exits 137 (SIGKILL) with no result:success event,
95
- // simulating a leased job killed mid-run before it could finish and release
96
- // naturally through its own clean-exit path. Dirties the tree first (like
97
- // scheduler-inplace-salvage.test.cjs's stub) so the scheduler classifies this
98
- // as a genuine kill-with-partial-work, not an auto-retryable transient blip
99
- // with no evidence of real execution.
100
- function writeKilledClaudeStub() {
101
- return claudeStub.writeClaudeStub({ body: `
102
- const fs = require('fs');
103
- const path = require('path');
104
- fs.writeFileSync(path.join(process.cwd(), 'job-output.txt'), 'work in progress when killed\\n', 'utf8');
105
- process.exit(137);
106
- ` });
107
- }
108
-
109
- let reapDeadRunningJobs;
110
-
111
- beforeAll(() => {
112
- originalHome = process.env.HOME;
113
- tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-quiet-lease-'));
114
- process.env.HOME = tmpHome;
115
- originalDisable = process.env.SM_JOB_WORKTREE_DISABLE;
116
- process.env.SM_JOB_WORKTREE_DISABLE = '1';
117
- ({ spawnJob, reapDeadRunningJobs } = require('../scheduler.cjs'));
118
- ({ pickNextBatch } = require('../lib/schedulerBatch.cjs'));
119
- quietMachineLease = require('../lib/quietMachineLease.cjs');
120
- });
121
-
122
- afterAll(() => {
123
- process.env.HOME = originalHome;
124
- if (originalDisable === undefined) delete process.env.SM_JOB_WORKTREE_DISABLE;
125
- else process.env.SM_JOB_WORKTREE_DISABLE = originalDisable;
126
- fs.rmSync(tmpHome, { recursive: true, force: true });
127
- });
128
-
129
- afterEach(() => {
130
- delete process.env.SM_CLAUDE_BIN;
131
- quietMachineLease.__resetForTests();
132
- });
133
-
134
- test('a killed quietMachine job releases the exclusive lease, and an ordinary job dispatches on the next pick', async () => {
135
- const projectCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-quiet-lease-project-'));
136
- initRepo(projectCwd);
137
- registerActiveProject(projectCwd);
138
-
139
- const quietSlug = `1107-test-quiet-killed-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
140
- const ordinarySlug = `1107-test-ordinary-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
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');
148
- fs.mkdirSync(prdsDir, { recursive: true });
149
- fs.writeFileSync(path.join(prdsDir, `${quietSlug}.md`), '---\nquietMachine: true\n---\nMeasure timing without contention, then get killed.', 'utf8');
150
- fs.writeFileSync(path.join(prdsDir, `${ordinarySlug}.md`), 'An ordinary PRD queued behind the quiet job.', 'utf8');
151
-
152
- const queuePath = writeProjectQueue(projectCwd, [
153
- { slug: quietSlug, status: 'pending', cwd: projectCwd, quietMachine: true },
154
- { slug: ordinarySlug, status: 'pending', cwd: projectCwd, createdAt: new Date().toISOString() },
155
- ]);
156
-
157
- process.env.SM_CLAUDE_BIN = writeKilledClaudeStub();
158
-
159
- const runId = `run-${quietSlug}`;
160
- const runDir = path.join(tmpHome, '.claude', 'session-manager', 'scheduled-plans', 'runs', runId);
161
- fs.mkdirSync(runDir, { recursive: true });
162
-
163
- try {
164
- // Before spawnJob ever runs, demonstrate that a held lease (as it would
165
- // be while quietSlug is genuinely running) blocks EVERY dispatch,
166
- // including the ordinary sibling job sitting right behind it in the same
167
- // project. Uses a standalone dummy holder so it doesn't interact with
168
- // spawnJob's own acquire/release of the real lease below.
169
- expect(quietMachineLease.acquire('dummy-holder-simulating-in-flight-run')).toBe(true);
170
- const heldPick = pickNextBatch(
171
- [
172
- { slug: quietSlug, status: 'running', cwd: projectCwd, quietMachine: true },
173
- { slug: ordinarySlug, status: 'pending', cwd: projectCwd, createdAt: new Date().toISOString() },
174
- ],
175
- new Set([quietSlug]),
176
- 5,
177
- { leaseHeld: quietMachineLease.isHeld(), machineInUse: 1, now: Date.now() },
178
- );
179
- expect(heldPick.batch).toEqual([]);
180
- quietMachineLease.release('dummy-holder-simulating-in-flight-run');
181
-
182
- // Now the real thing: spawnJob acquires the lease itself for quietSlug,
183
- // runs the killed stub, and must release it on this exit path.
184
- await spawnJob({ slug: quietSlug, cwd: projectCwd, quietMachine: true }, runId, runDir, projectCwd);
185
-
186
- const jobs = JSON.parse(fs.readFileSync(queuePath, 'utf8')).jobs;
187
- const quietRow = jobs.find((j) => j.slug === quietSlug);
188
- expect(quietRow).toBeTruthy();
189
- expect(quietRow.exitCode).toBe(137);
190
- expect(quietRow.quietMachine).toBe(true);
191
-
192
- // The lease was released on this killed-job exit path — spawnJob's own
193
- // finally block, the same one that releases the sessionSlots token.
194
- expect(quietMachineLease.isHeld()).toBe(false);
195
-
196
- // With the lease free again, the ordinary sibling job is now eligible.
197
- const nextPick = pickNextBatch(
198
- [
199
- { slug: quietSlug, status: 'failed', cwd: projectCwd, quietMachine: true },
200
- { slug: ordinarySlug, status: 'pending', cwd: projectCwd, createdAt: new Date().toISOString() },
201
- ],
202
- new Set(),
203
- 5,
204
- { leaseHeld: quietMachineLease.isHeld(), machineInUse: 0, now: Date.now() },
205
- );
206
- expect(nextPick.batch.map((j) => j.slug)).toEqual([ordinarySlug]);
207
- } finally {
208
- fs.rmSync(projectCwd, { recursive: true, force: true });
209
- fs.rmSync(runDir, { recursive: true, force: true });
210
- }
211
- }, 30_000);
212
-
213
- test('reapDeadRunningJobs releases a quietMachine job\'s lease too — the process vanished without spawnJob\'s own finally ever running', async () => {
214
- // This reaper exists exactly for the case spawnJob's own finally never
215
- // runs (the process dies without the completion mutate() finishing) — see
216
- // reapDeadRunningJobs's header comment. A quietMachine row reaped through
217
- // THIS path must release the lease itself, or it stays wedged forever.
218
- const projectCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-quiet-lease-reap-project-'));
219
- registerActiveProject(projectCwd);
220
-
221
- const slug = `1107-test-reaped-quiet-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
222
- const queuePath = writeProjectQueue(projectCwd, [
223
- {
224
- slug,
225
- status: 'running',
226
- cwd: projectCwd,
227
- quietMachine: true,
228
- runId: `run-${slug}`,
229
- runtime: { pid: 999999 }, // guaranteed-dead pid (see reaperHelpers tests)
230
- },
231
- ]);
232
- const runDir = path.join(tmpHome, '.claude', 'session-manager', 'scheduled-plans', 'runs', `run-${slug}`);
233
- fs.mkdirSync(runDir, { recursive: true });
234
- fs.writeFileSync(
235
- path.join(runDir, `${slug}.log`),
236
- '{"type":"result","subtype":"success","result":"done","is_error":false}\n',
237
- );
238
-
239
- // Simulate the lease as held by this row, exactly as it would be if
240
- // spawnJob's own acquire ran but its finally never got the chance to.
241
- expect(quietMachineLease.acquire(slug)).toBe(true);
242
-
243
- try {
244
- await reapDeadRunningJobs();
245
-
246
- const jobs = JSON.parse(fs.readFileSync(queuePath, 'utf8')).jobs;
247
- const row = jobs.find((j) => j.slug === slug);
248
- expect(row).toBeTruthy();
249
- expect(row.status).toBe('completed');
250
-
251
- // The lease must be released here too, not just in spawnJob's finally.
252
- expect(quietMachineLease.isHeld()).toBe(false);
253
- } finally {
254
- fs.rmSync(projectCwd, { recursive: true, force: true });
255
- fs.rmSync(runDir, { recursive: true, force: true });
256
- }
257
- });
@@ -1,123 +0,0 @@
1
- /**
2
- * scheduler-rate-limit-cooldown-freshness.test.cjs — PRD 1119.
3
- *
4
- * setPaused()'s manual-override cooldown used to suppress WRITING any pause
5
- * for 5 minutes after a human Resume/Run now, unconditionally — even when
6
- * the triggering rate-limit observation came from a run that started AFTER
7
- * the manual clear (i.e. brand-new evidence, not the stale auto-detection
8
- * the cooldown exists to ignore). Combined with the halt-reset that resets a
9
- * rate-limited job straight back to 'pending', this produced a hot dispatch
10
- * loop: dispatch -> 429 in seconds -> reset to pending -> pause suppressed
11
- * -> dispatch again, on a ~12s cadence, until the cooldown itself expired.
12
- *
13
- * isCooldownSuppressed() is the pure predicate this PRD extracts so both
14
- * directions of the fix (fresh observation bypasses the cooldown; stale one
15
- * stays suppressed) are directly unit-testable without touching queue.json,
16
- * mutate(), or any fs state. nextRapidRateLimitCount() is the pure reducer
17
- * behind the independent rapid-repeat hard-pause cap (a second circuit
18
- * breaker for when the computed resumeAt is itself stale/wrong and would
19
- * otherwise let the resume timer keep re-clearing the pause every ~30s,
20
- * with every subsequent dispatch genuinely "fresh").
21
- *
22
- * Run: timeout 60 npx vitest run src/main/__tests__/scheduler-rate-limit-cooldown-freshness.test.cjs
23
- */
24
-
25
- 'use strict';
26
-
27
- import { test, expect } from 'vitest';
28
- const {
29
- isCooldownSuppressed,
30
- nextRapidRateLimitCount,
31
- CONSECUTIVE_RAPID_RATE_LIMIT_THRESHOLD,
32
- RAPID_RATE_LIMIT_WINDOW_MS,
33
- MANUAL_PAUSE_COOLDOWN_MS,
34
- } = require('../scheduler.cjs');
35
-
36
- const clearedAt = 1_000_000;
37
-
38
- test('a rate_limit pause triggered by a run that STARTED AFTER the manual clear engages regardless of the cooldown', () => {
39
- const now = clearedAt + 60_000; // well inside the 5-minute cooldown window
40
- const observedAt = clearedAt + 5_000; // the triggering run started after the clear
41
- const suppressed = isCooldownSuppressed({ pauseClearedManuallyAt: clearedAt, now, observedAt, force: false });
42
- expect(suppressed).toBe(false);
43
- });
44
-
45
- test('a rate_limit pause triggered by an observation OLDER than the manual clear stays suppressed', () => {
46
- const now = clearedAt + 60_000;
47
- const observedAt = clearedAt - 5_000; // stale: this run started before the human cleared the pause
48
- const suppressed = isCooldownSuppressed({ pauseClearedManuallyAt: clearedAt, now, observedAt, force: false });
49
- expect(suppressed).toBe(true);
50
- });
51
-
52
- test('no observedAt at all (unrelated pause reasons, e.g. auth/network) is treated as stale — unchanged prior behavior', () => {
53
- const now = clearedAt + 60_000;
54
- const suppressed = isCooldownSuppressed({ pauseClearedManuallyAt: clearedAt, now, observedAt: null, force: false });
55
- expect(suppressed).toBe(true);
56
- });
57
-
58
- test('once the cooldown window itself has elapsed, suppression lifts regardless of freshness', () => {
59
- const now = clearedAt + MANUAL_PAUSE_COOLDOWN_MS + 1;
60
- const suppressed = isCooldownSuppressed({ pauseClearedManuallyAt: clearedAt, now, observedAt: null, force: false });
61
- expect(suppressed).toBe(false);
62
- });
63
-
64
- test('no manual clear on record never suppresses', () => {
65
- const suppressed = isCooldownSuppressed({ pauseClearedManuallyAt: null, now: Date.now(), observedAt: null, force: false });
66
- expect(suppressed).toBe(false);
67
- });
68
-
69
- test('force always bypasses the cooldown, fresh or not', () => {
70
- const now = clearedAt + 1_000;
71
- const staleObservedAt = clearedAt - 5_000;
72
- const suppressed = isCooldownSuppressed({ pauseClearedManuallyAt: clearedAt, now, observedAt: staleObservedAt, force: true });
73
- expect(suppressed).toBe(false);
74
- });
75
-
76
- // ---------- rapid-repeat consecutive counter ----------
77
-
78
- test('a rate-limited run under the rapid window increments the counter', () => {
79
- expect(nextRapidRateLimitCount(0, { rateLimited: true, durationMs: 4_000 })).toBe(1);
80
- expect(nextRapidRateLimitCount(1, { rateLimited: true, durationMs: 4_000 })).toBe(2);
81
- });
82
-
83
- test('a rate-limited run that ran for >= the rapid window does not increment the counter', () => {
84
- expect(nextRapidRateLimitCount(1, { rateLimited: true, durationMs: RAPID_RATE_LIMIT_WINDOW_MS })).toBe(1);
85
- });
86
-
87
- test('any non-rate-limited outcome resets the counter to 0', () => {
88
- expect(nextRapidRateLimitCount(2, { rateLimited: false, durationMs: 999_999 })).toBe(0);
89
- });
90
-
91
- test('driving the counter to the documented threshold engages the hard-pause condition', () => {
92
- let count = 0;
93
- for (let i = 0; i < CONSECUTIVE_RAPID_RATE_LIMIT_THRESHOLD; i += 1) {
94
- count = nextRapidRateLimitCount(count, { rateLimited: true, durationMs: 3_000 });
95
- }
96
- expect(count).toBe(CONSECUTIVE_RAPID_RATE_LIMIT_THRESHOLD);
97
- expect(count >= CONSECUTIVE_RAPID_RATE_LIMIT_THRESHOLD).toBe(true);
98
- });
99
-
100
- test('reaching the threshold forces the pause through even against a STALE observation still inside the cooldown window', () => {
101
- // This is the scenario the hard cap exists for: freshness alone is not
102
- // enough when the computed resumeAt is itself stale/wrong and the resume
103
- // timer keeps re-clearing the pause, so a LATER dispatch's observation can
104
- // legitimately look "stale" relative to the most recent clear. The
105
- // rapid-repeat counter is an independent, unconditional circuit breaker.
106
- let count = 0;
107
- for (let i = 0; i < CONSECUTIVE_RAPID_RATE_LIMIT_THRESHOLD; i += 1) {
108
- count = nextRapidRateLimitCount(count, { rateLimited: true, durationMs: 3_000 });
109
- }
110
- const force = count >= CONSECUTIVE_RAPID_RATE_LIMIT_THRESHOLD;
111
- const staleObservedAt = clearedAt - 999_999;
112
- const suppressed = isCooldownSuppressed({ pauseClearedManuallyAt: clearedAt, now: clearedAt + 1_000, observedAt: staleObservedAt, force });
113
- expect(suppressed).toBe(false);
114
- });
115
-
116
- test('one non-rate-limited run in between resets the streak so the hard pause never engages', () => {
117
- let count = 0;
118
- count = nextRapidRateLimitCount(count, { rateLimited: true, durationMs: 3_000 });
119
- count = nextRapidRateLimitCount(count, { rateLimited: true, durationMs: 3_000 });
120
- count = nextRapidRateLimitCount(count, { rateLimited: false, durationMs: 60_000 }); // job actually ran fine
121
- count = nextRapidRateLimitCount(count, { rateLimited: true, durationMs: 3_000 });
122
- expect(count).toBeLessThan(CONSECUTIVE_RAPID_RATE_LIMIT_THRESHOLD);
123
- });
@@ -1,152 +0,0 @@
1
- /**
2
- * scheduler-rate-limit-pause.test.cjs — PRD 1118: pause against the BINDING
3
- * rate-limit window read off a run's own log, not refreshNextReset()'s
4
- * five_hour-only assumption, and never leave an indefinite pause with no
5
- * resume timer when both the log and the billing endpoint come up empty
6
- * (the live failure mode: the usage endpoint itself 429s).
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
- *
15
- * These test the PURE decision functions scheduler.cjs's setPaused() and the
16
- * spawnJob() rateLimited branch delegate to — never the real setPaused/mutate
17
- * path, which writes the machine's live scheduler queue.json at a fixed
18
- * homedir path (~/.claude/session-manager/scheduled-plans) and must not be
19
- * touched by a test run.
20
- *
21
- * Run: timeout 120 npx vitest run src/main/__tests__/scheduler-rate-limit-pause.test.cjs
22
- */
23
-
24
- 'use strict';
25
-
26
- import { test } from 'vitest';
27
- const assert = require('node:assert/strict');
28
- const path = require('node:path');
29
- const {
30
- resolveRateLimitPauseReset,
31
- billingResetForPause,
32
- computeEffectiveResumeAt,
33
- computeResumeDelay,
34
- computeDegradedBudget,
35
- } = require('../scheduler.cjs');
36
- const billing = require('../usage.cjs');
37
-
38
- // Repo-tracked, byte-for-byte copy of the real 2026-09-05 starry-night-ships
39
- // incident log (see src/main/lib/__tests__/rateLimitWindow.test.cjs for the
40
- // live-path parity check) — kept hermetic rather than pointed at the
41
- // machine-local live path directly.
42
- const FIXTURE = path.join(__dirname, '..', 'lib', '__tests__', 'fixtures', '204-mercury-steam-horse.log.txt');
43
- const FIVE_HOUR_ISO = new Date(1788643800 * 1000).toISOString(); // what refreshNextReset() would have returned
44
- const SEVEN_DAY_ISO = new Date(1789059600 * 1000).toISOString(); // the actually-binding window
45
- // Fixed "now" contemporaneous with the fixture's own timestamps (BEFORE both
46
- // resets above), so freshness checks read the fixture the way it looked at
47
- // incident time — not relative to whenever this suite happens to run.
48
- const FIXTURE_NOW = Date.parse('2026-09-05T16:32:00.000Z');
49
-
50
- test('resolveRateLimitPauseReset: prefers the log-derived seven-day reset over the billing five_hour reset', () => {
51
- const resetIso = resolveRateLimitPauseReset(FIXTURE, FIVE_HOUR_ISO, FIXTURE_NOW);
52
- assert.strictEqual(resetIso, SEVEN_DAY_ISO);
53
- assert.notStrictEqual(resetIso, FIVE_HOUR_ISO);
54
- });
55
-
56
- test('resolveRateLimitPauseReset: falls back to the billing reset when the log yields none', () => {
57
- const resetIso = resolveRateLimitPauseReset('/nonexistent/path/job.log', FIVE_HOUR_ISO, FIXTURE_NOW);
58
- assert.strictEqual(resetIso, FIVE_HOUR_ISO);
59
- });
60
-
61
- test('resolveRateLimitPauseReset: null when BOTH the log and the billing endpoint are unavailable', () => {
62
- assert.strictEqual(resolveRateLimitPauseReset('/nonexistent/path/job.log', null, FIXTURE_NOW), null);
63
- });
64
-
65
- test('resolveRateLimitPauseReset: a PAST log-derived reset is rejected, not returned stale', () => {
66
- // "now" is AFTER both fixture resets have already elapsed.
67
- const longAfter = Date.parse('2026-09-15T00:00:00.000Z');
68
- const resetIso = resolveRateLimitPauseReset(FIXTURE, FIVE_HOUR_ISO, longAfter);
69
- assert.strictEqual(resetIso, null);
70
- });
71
-
72
- test('resolveRateLimitPauseReset: log yields nothing and the billing reset has already passed -> null, not the stale value', () => {
73
- const longAfter = Date.parse('2026-09-15T00:00:00.000Z');
74
- const resetIso = resolveRateLimitPauseReset('/nonexistent/path/job.log', FIVE_HOUR_ISO, longAfter);
75
- assert.strictEqual(resetIso, null);
76
- });
77
-
78
- test('computeEffectiveResumeAt: rate_limit with no resumeAt gets the same bounded 30-minute fallback as network', () => {
79
- const now = FIXTURE_NOW;
80
- const resumeAt = computeEffectiveResumeAt('rate_limit', null, now);
81
- assert.strictEqual(resumeAt, new Date(now + 30 * 60_000).toISOString());
82
- });
83
-
84
- test('computeEffectiveResumeAt: a FRESH explicit resumeAt (the log-derived reset) is never overridden', () => {
85
- const resumeAt = computeEffectiveResumeAt('rate_limit', SEVEN_DAY_ISO, FIXTURE_NOW);
86
- assert.strictEqual(resumeAt, SEVEN_DAY_ISO);
87
- });
88
-
89
- test('computeEffectiveResumeAt: a PAST resumeAt gets the 30-minute fallback, not a ~30-second nap', () => {
90
- // now is well AFTER SEVEN_DAY_ISO has already elapsed — the exact shape of
91
- // the bug: Math.max(30_000, <negative>) used to arm a 30-second resume.
92
- const longAfter = Date.parse('2026-09-15T00:00:00.000Z');
93
- const resumeAt = computeEffectiveResumeAt('rate_limit', SEVEN_DAY_ISO, longAfter);
94
- assert.strictEqual(resumeAt, new Date(longAfter + 30 * 60_000).toISOString());
95
- assert.notStrictEqual(resumeAt, SEVEN_DAY_ISO);
96
- const { delayMs } = computeResumeDelay(resumeAt, longAfter);
97
- assert.ok(delayMs >= 30 * 60_000, `expected >= 30 minutes, got ${delayMs}ms`);
98
- });
99
-
100
- test('computeEffectiveResumeAt: other reasons (e.g. auth) get no fallback — stays indefinite', () => {
101
- assert.strictEqual(computeEffectiveResumeAt('auth', null, Date.now()), null);
102
- });
103
-
104
- test('billingResetForPause: skips refreshNextReset() (no billing.fetchUsage call) while the shared circuit is OPEN', async () => {
105
- const originalFetchUsage = billing.fetchUsage;
106
- let fetchCalls = 0;
107
- billing.fetchUsage = async () => { fetchCalls++; return { kind: 'ok', data: { usage: {} } }; };
108
- try {
109
- // Force the shared circuit open (3 consecutive failures), same threshold usageCircuit.cjs uses.
110
- billing.usageCircuit.recordFailure('a');
111
- billing.usageCircuit.recordFailure('a');
112
- billing.usageCircuit.recordFailure('a');
113
- assert.strictEqual(billing.usageCircuit.state(), 'open');
114
-
115
- await billingResetForPause();
116
- assert.strictEqual(fetchCalls, 0, 'refreshNextReset() must not call billing.fetchUsage() while the circuit is open');
117
- } finally {
118
- billing.usageCircuit.recordSuccess({}); // close the shared circuit back up for later tests/files
119
- billing.fetchUsage = originalFetchUsage;
120
- }
121
- });
122
-
123
- test('computeResumeDelay: a seven-day-out reset (~5 days from now) is armed, not treated as too-far', () => {
124
- const now = Date.parse('2026-09-05T16:32:00.000Z');
125
- // ~5 days out — comfortably under the 0x7fffffff (~24.8 day) setTimeout cap.
126
- const fiveDaysOut = new Date(now + 5 * 24 * 60 * 60_000).toISOString();
127
- const { delayMs, tooFar } = computeResumeDelay(fiveDaysOut, now);
128
- assert.strictEqual(tooFar, false);
129
- assert.ok(delayMs < 0x7fffffff, `expected delay under setTimeout cap, got ${delayMs}`);
130
- assert.ok(delayMs > 400_000_000, `expected ~5 days in ms, got ${delayMs}`);
131
- });
132
-
133
- test('computeResumeDelay: a reset far enough out DOES trip the overflow guard', () => {
134
- const now = Date.parse('2026-09-05T16:32:00.000Z');
135
- const thirtyDaysOut = new Date(now + 30 * 24 * 60 * 60_000).toISOString();
136
- const { tooFar } = computeResumeDelay(thirtyDaysOut, now);
137
- assert.strictEqual(tooFar, true);
138
- });
139
-
140
- test('refreshNextReset (via billingResetForPause) seeds lastGoodUsagePayload: a following degraded cycle yields the fetched percent, not 100', async () => {
141
- const originalFetchUsage = billing.fetchUsage;
142
- billing.fetchUsage = async () => ({
143
- kind: 'ok',
144
- data: { usage: { limits: [{ kind: 'weekly_all', percent: 64, resets_at: '2026-09-24T17:00:00Z', scope: null, is_active: true }] } },
145
- });
146
- try {
147
- await billingResetForPause();
148
- assert.strictEqual(computeDegradedBudget().utilization, 64);
149
- } finally {
150
- billing.fetchUsage = originalFetchUsage;
151
- }
152
- });