claude-code-session-manager 0.97.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 (430) 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-D93FoaOm.js → DataModel-HdR0AxFq.js} +1 -1
  5. package/dist/assets/{History-CXxxqNP1.js → History-Dr4Jr_5z.js} +2 -2
  6. package/dist/assets/{Hooks-CzeYFQjq.js → Hooks-NhrvH3JP.js} +3 -3
  7. package/dist/assets/{Library-D2zPkwOC.js → Library-Dv_EtIMr.js} +1 -1
  8. package/dist/assets/{MarkdownEditor-DbinP78A.js → MarkdownEditor-DTKDWV_n.js} +1 -1
  9. package/dist/assets/{McpServers-Bu_sVGLr.js → McpServers-M034G9B3.js} +2 -2
  10. package/dist/assets/{Memory-BgiI6LZ_.js → Memory-DzF1B4gx.js} +6 -6
  11. package/dist/assets/{Permissions-C1MMzZRm.js → Permissions-DrgbSGWl.js} +3 -3
  12. package/dist/assets/{Plugins-Dhu6wAZi.js → Plugins-Bd3ef35w.js} +2 -2
  13. package/dist/assets/{ProvenanceBadge-C3AcWKMG.js → ProvenanceBadge-DcBHT1Iy.js} +1 -1
  14. package/dist/assets/{SaveBar-plwYm0iO.js → SaveBar-rimJem-Y.js} +1 -1
  15. package/dist/assets/Scheduler-DjgFJl2w.js +16 -0
  16. package/dist/assets/{ScopeSwitcher-DFy0vv_o.js → ScopeSwitcher-CnAzTBrl.js} +1 -1
  17. package/dist/assets/Settings-Dc1F_sSg.js +3 -0
  18. package/dist/assets/{SkillReferenceGraph-DsI7hRPB.js → SkillReferenceGraph-B9aZDecM.js} +1 -1
  19. package/dist/assets/{Skills-3E748UfB.js → Skills-DRXd18gu.js} +2 -2
  20. package/dist/assets/{SystemPrompt-BqmfUJOd.js → SystemPrompt-ahy5-Fwe.js} +1 -1
  21. package/dist/assets/{TagLibrary-CPz6fO8Q.js → TagLibrary-C_tToDeL.js} +1 -1
  22. package/dist/assets/{TiptapBody-D9EoBQ1P.js → TiptapBody-DvdefS6K.js} +1 -1
  23. package/dist/assets/{Toggle-BHAmZNDn.js → Toggle-6Jl5Njc6.js} +1 -1
  24. package/dist/assets/{index-CKH5Uxik.css → index-Bta-hwud.css} +1 -1
  25. package/dist/assets/{index-tn5JIVPj.js → index-vx8O73l8.js} +497 -502
  26. package/dist/assets/{settingsSchema-Crqy_aNz.js → settingsSchema-D6iRguNV.js} +1 -1
  27. package/dist/index.html +2 -2
  28. package/package.json +26 -29
  29. package/plugins/CLAUDE.md +6 -6
  30. package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +23 -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 +153 -19
  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 +33 -30
  91. package/src/main/lib/workTypeLibrary.cjs +7 -1
  92. package/src/main/promptSessionEvents.cjs +37 -13
  93. package/src/main/queueOps.cjs +17 -6
  94. package/src/main/scheduler.cjs +975 -203
  95. package/src/main/seedAgentPersonas.cjs +278 -23
  96. package/src/main/supervisor.cjs +4 -1
  97. package/src/main/templates/PRD_AUTHORING.md +126 -406
  98. package/src/preload/__tests__/preload-surface.test.cjs +68 -0
  99. package/src/preload/api.d.ts +41 -94
  100. package/src/preload/index.cjs +14 -9
  101. package/src/seed/agents/architect.md +1 -0
  102. package/src/seed/agents/dev-lead.md +17 -18
  103. package/src/seed/agents/project-home-builder.md +1 -0
  104. package/src/seed/agents/validator.md +10 -4
  105. package/dist/assets/HostBilko-nESrTRIg.js +0 -1
  106. package/dist/assets/Scheduler-BVYTeh39.js +0 -16
  107. package/dist/assets/Settings-DIhGgdfs.js +0 -3
  108. package/src/main/__tests__/activeIndexMerge.test.cjs +0 -235
  109. package/src/main/__tests__/agentEffortResolve.test.cjs +0 -117
  110. package/src/main/__tests__/agentLibrary.test.cjs +0 -276
  111. package/src/main/__tests__/agentModelResolve.test.cjs +0 -311
  112. package/src/main/__tests__/agentOverlayWrite.test.cjs +0 -95
  113. package/src/main/__tests__/bilkoHost-deriveSlug.test.cjs +0 -26
  114. package/src/main/__tests__/bilkoHost-integration.test.cjs +0 -118
  115. package/src/main/__tests__/bilkoHostCore.test.cjs +0 -72
  116. package/src/main/__tests__/broadcastCoalescer.test.cjs +0 -122
  117. package/src/main/__tests__/chat-cancel-terminal.test.cjs +0 -120
  118. package/src/main/__tests__/chat-dead-channels.test.cjs +0 -63
  119. package/src/main/__tests__/chat-exit-close-race.test.cjs +0 -146
  120. package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +0 -139
  121. package/src/main/__tests__/chat-preamble-anchors.test.cjs +0 -100
  122. package/src/main/__tests__/chat-queue.test.cjs +0 -97
  123. package/src/main/__tests__/chat-stop-signal.test.cjs +0 -89
  124. package/src/main/__tests__/chatRunner-epic-worktree-execcwd.test.cjs +0 -125
  125. package/src/main/__tests__/chatRunner-session-flag-retry.test.cjs +0 -252
  126. package/src/main/__tests__/classifyPromptTicket.test.cjs +0 -101
  127. package/src/main/__tests__/classifyTranscriptLine.test.cjs +0 -201
  128. package/src/main/__tests__/computeDepHistorySatisfaction.test.cjs +0 -66
  129. package/src/main/__tests__/config-readText-bounded.test.cjs +0 -84
  130. package/src/main/__tests__/configWriteBoundaryOwners.test.cjs +0 -59
  131. package/src/main/__tests__/crossProjectFeedback.test.cjs +0 -334
  132. package/src/main/__tests__/crossProjectFeedbackRoutes.test.cjs +0 -161
  133. package/src/main/__tests__/dep-orphan-archive-health.test.cjs +0 -77
  134. package/src/main/__tests__/develop-skill-failure-modes.test.cjs +0 -70
  135. package/src/main/__tests__/docEdit.test.cjs +0 -244
  136. package/src/main/__tests__/dod-batchkey.test.cjs +0 -183
  137. package/src/main/__tests__/dod-drain-hook.test.cjs +0 -302
  138. package/src/main/__tests__/dod-report.test.cjs +0 -304
  139. package/src/main/__tests__/dod-reverify.test.cjs +0 -285
  140. package/src/main/__tests__/epicContextDigest.test.cjs +0 -174
  141. package/src/main/__tests__/epicMint.test.cjs +0 -332
  142. package/src/main/__tests__/epicMintTelemetryTap.test.cjs +0 -64
  143. package/src/main/__tests__/epicStatusMirror.test.cjs +0 -110
  144. package/src/main/__tests__/epicValidationHook.test.cjs +0 -291
  145. package/src/main/__tests__/exchanges.test.cjs +0 -122
  146. package/src/main/__tests__/exchangesPromptId.test.cjs +0 -61
  147. package/src/main/__tests__/extractJson.test.cjs +0 -51
  148. package/src/main/__tests__/files-reject-credentials.test.cjs +0 -40
  149. package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +0 -556
  150. package/src/main/__tests__/flatPrdTickSweep.test.cjs +0 -110
  151. package/src/main/__tests__/health-build-freshness.test.cjs +0 -39
  152. package/src/main/__tests__/health-claude-md-budget.test.cjs +0 -57
  153. package/src/main/__tests__/health-credentials.test.cjs +0 -81
  154. package/src/main/__tests__/health-delegation-chain.test.cjs +0 -124
  155. package/src/main/__tests__/health-per-project-stall.test.cjs +0 -84
  156. package/src/main/__tests__/health-prd-migration.test.cjs +0 -37
  157. package/src/main/__tests__/health-queue-dispatch.test.cjs +0 -135
  158. package/src/main/__tests__/health-starve-escalation.test.cjs +0 -94
  159. package/src/main/__tests__/health-tick-liveness.test.cjs +0 -179
  160. package/src/main/__tests__/health-usage-poller.test.cjs +0 -144
  161. package/src/main/__tests__/health-worktree-cap-blocked.test.cjs +0 -65
  162. package/src/main/__tests__/heapSnapshot.test.cjs +0 -121
  163. package/src/main/__tests__/historyAggregatorIntraday.test.cjs +0 -313
  164. package/src/main/__tests__/historyDashboard.test.cjs +0 -163
  165. package/src/main/__tests__/historyRollup.test.cjs +0 -333
  166. package/src/main/__tests__/intradayRefresh.test.cjs +0 -39
  167. package/src/main/__tests__/ipcSchemas-dependsOn.test.cjs +0 -39
  168. package/src/main/__tests__/kg-augment.test.cjs +0 -195
  169. package/src/main/__tests__/loadGateDetailTick.test.cjs +0 -31
  170. package/src/main/__tests__/machineProfile.test.cjs +0 -152
  171. package/src/main/__tests__/mcpStatus.test.cjs +0 -61
  172. package/src/main/__tests__/memoryAggregate.test.cjs +0 -109
  173. package/src/main/__tests__/memoryStale.test.cjs +0 -88
  174. package/src/main/__tests__/needsReviewLedger.test.cjs +0 -162
  175. package/src/main/__tests__/openExternalApp-spawn-error.test.cjs +0 -25
  176. package/src/main/__tests__/opsErrorLog.test.cjs +0 -109
  177. package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +0 -173
  178. package/src/main/__tests__/personaMerge.test.cjs +0 -169
  179. package/src/main/__tests__/planValidator.test.cjs +0 -125
  180. package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +0 -176
  181. package/src/main/__tests__/prd-group-allocator.test.cjs +0 -119
  182. package/src/main/__tests__/prdAdminRouteParity.test.cjs +0 -70
  183. package/src/main/__tests__/prdAdminRoutes.test.cjs +0 -718
  184. package/src/main/__tests__/prdAgentType.test.cjs +0 -103
  185. package/src/main/__tests__/prdAuthoringSeed.test.cjs +0 -39
  186. package/src/main/__tests__/prdCreate.test.cjs +0 -979
  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 -154
  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-needs-review-autoresolve.test.cjs +0 -197
  282. package/src/main/__tests__/scheduler-never-stop.test.cjs +0 -157
  283. package/src/main/__tests__/scheduler-no-dead-end-status.test.cjs +0 -152
  284. package/src/main/__tests__/scheduler-no-orphan-run-dir.test.cjs +0 -81
  285. package/src/main/__tests__/scheduler-notify-originating-tab-transcript.test.cjs +0 -87
  286. package/src/main/__tests__/scheduler-notify-originating-tab.test.cjs +0 -343
  287. package/src/main/__tests__/scheduler-periodic-reverify-guard.test.cjs +0 -217
  288. package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +0 -164
  289. package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +0 -161
  290. package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +0 -178
  291. package/src/main/__tests__/scheduler-quarantine-autoresolve.test.cjs +0 -165
  292. package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +0 -257
  293. package/src/main/__tests__/scheduler-rate-limit-cooldown-freshness.test.cjs +0 -123
  294. package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +0 -152
  295. package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +0 -156
  296. package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +0 -754
  297. package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +0 -87
  298. package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +0 -100
  299. package/src/main/__tests__/scheduler-reconcile-history-backfill.test.cjs +0 -105
  300. package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +0 -203
  301. package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +0 -247
  302. package/src/main/__tests__/scheduler-reset-job-fields-guard.test.cjs +0 -77
  303. package/src/main/__tests__/scheduler-resume-recovery.test.cjs +0 -254
  304. package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +0 -115
  305. package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +0 -380
  306. package/src/main/__tests__/scheduler-sigterm-commit.test.cjs +0 -43
  307. package/src/main/__tests__/scheduler-stall-per-project.test.cjs +0 -126
  308. package/src/main/__tests__/scheduler-starve-escalation.test.cjs +0 -154
  309. package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +0 -252
  310. package/src/main/__tests__/scheduler-stranded-investigation.test.cjs +0 -185
  311. package/src/main/__tests__/scheduler-stuck-failed-escalation.test.cjs +0 -136
  312. package/src/main/__tests__/scheduler-supervisor-record.test.cjs +0 -81
  313. package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +0 -54
  314. package/src/main/__tests__/scheduler-tick-wedge.test.cjs +0 -172
  315. package/src/main/__tests__/scheduler-transient-failure.test.cjs +0 -141
  316. package/src/main/__tests__/scheduler-unreadable-queue-guard.test.cjs +0 -62
  317. package/src/main/__tests__/scheduler-utilization-hold.test.cjs +0 -89
  318. package/src/main/__tests__/scheduler-verify-prd-path.test.cjs +0 -109
  319. package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +0 -234
  320. package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +0 -120
  321. package/src/main/__tests__/scheduler-writeprd-epic-rollback.test.cjs +0 -105
  322. package/src/main/__tests__/schedulerBatchRootBlocker.test.cjs +0 -117
  323. package/src/main/__tests__/schedulerStateSidecarRestore.test.cjs +0 -110
  324. package/src/main/__tests__/seedAgentPersonas.test.cjs +0 -184
  325. package/src/main/__tests__/seedSchedulerMcp.test.cjs +0 -211
  326. package/src/main/__tests__/seedStatus.test.cjs +0 -100
  327. package/src/main/__tests__/seedValidatorPersona.test.cjs +0 -116
  328. package/src/main/__tests__/stop-signal-anchor.test.cjs +0 -75
  329. package/src/main/__tests__/telemetryClient.test.cjs +0 -1055
  330. package/src/main/__tests__/telemetryContract.test.cjs +0 -930
  331. package/src/main/__tests__/telemetrySettings.test.cjs +0 -210
  332. package/src/main/__tests__/transcripts-batch-flush.test.cjs +0 -249
  333. package/src/main/__tests__/transcripts-doFlush-array.test.cjs +0 -124
  334. package/src/main/__tests__/transcripts-paged-reads.test.cjs +0 -241
  335. package/src/main/__tests__/transcripts-worktree-epic-path.test.cjs +0 -154
  336. package/src/main/__tests__/transcriptsUsageFor.test.cjs +0 -206
  337. package/src/main/__tests__/uniquePrdNumbers.test.cjs +0 -153
  338. package/src/main/__tests__/usageSingleFlight.test.cjs +0 -169
  339. package/src/main/__tests__/validationSentinels.test.cjs +0 -84
  340. package/src/main/__tests__/workTypeLibrary.test.cjs +0 -89
  341. package/src/main/bilkoHost.cjs +0 -314
  342. package/src/main/bilkoHostCore.cjs +0 -89
  343. package/src/main/lib/__tests__/active-sessions.test.cjs +0 -251
  344. package/src/main/lib/__tests__/activeIndexRebuild.test.cjs +0 -179
  345. package/src/main/lib/__tests__/agentPersonaSchema.test.cjs +0 -67
  346. package/src/main/lib/__tests__/auditLog.test.cjs +0 -38
  347. package/src/main/lib/__tests__/bootSelfHeal.test.cjs +0 -107
  348. package/src/main/lib/__tests__/branchSweep.test.cjs +0 -164
  349. package/src/main/lib/__tests__/buildIdentity.test.cjs +0 -121
  350. package/src/main/lib/__tests__/buildTarget.test.cjs +0 -52
  351. package/src/main/lib/__tests__/childWithLog.test.cjs +0 -321
  352. package/src/main/lib/__tests__/coldBootPromptSessionsWrite.test.cjs +0 -87
  353. package/src/main/lib/__tests__/crashTelemetry.test.cjs +0 -103
  354. package/src/main/lib/__tests__/credentials-futile-refresh.test.cjs +0 -115
  355. package/src/main/lib/__tests__/cwdClassify.test.cjs +0 -111
  356. package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +0 -95
  357. package/src/main/lib/__tests__/delegationReadiness.test.cjs +0 -1175
  358. package/src/main/lib/__tests__/dispatchLoop.test.cjs +0 -63
  359. package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +0 -244
  360. package/src/main/lib/__tests__/ephemeralCwd.test.cjs +0 -91
  361. package/src/main/lib/__tests__/epicDelegationStats.test.cjs +0 -137
  362. package/src/main/lib/__tests__/epicSpawnCwd.test.cjs +0 -283
  363. package/src/main/lib/__tests__/epicSpawnPlan.test.cjs +0 -196
  364. package/src/main/lib/__tests__/epicTranscriptPath.test.cjs +0 -163
  365. package/src/main/lib/__tests__/epicWorktreeBoot.test.cjs +0 -136
  366. package/src/main/lib/__tests__/epicWorktreeMerge.test.cjs +0 -130
  367. package/src/main/lib/__tests__/epicWorktreeMint.test.cjs +0 -117
  368. package/src/main/lib/__tests__/epicWorktreeProjectConfig.test.cjs +0 -137
  369. package/src/main/lib/__tests__/fixChainDepth.test.cjs +0 -40
  370. package/src/main/lib/__tests__/fixtures/204-mercury-steam-horse.log.txt +0 -13
  371. package/src/main/lib/__tests__/fixtures/scheduler-machine.json.corrupt-1789147548 +0 -34
  372. package/src/main/lib/__tests__/gateFixtures.json +0 -20
  373. package/src/main/lib/__tests__/gitCacheBound.test.cjs +0 -69
  374. package/src/main/lib/__tests__/gitWorktree.test.cjs +0 -1478
  375. package/src/main/lib/__tests__/gitWorktreeSalvage.test.cjs +0 -107
  376. package/src/main/lib/__tests__/gitWorktreeSalvageDelta.test.cjs +0 -153
  377. package/src/main/lib/__tests__/guardShims.test.cjs +0 -158
  378. package/src/main/lib/__tests__/importReferences.spec.cjs +0 -56
  379. package/src/main/lib/__tests__/instanceLock.test.cjs +0 -173
  380. package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +0 -78
  381. package/src/main/lib/__tests__/jobWorktree.test.cjs +0 -199
  382. package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +0 -82
  383. package/src/main/lib/__tests__/landedSinceRun.test.cjs +0 -133
  384. package/src/main/lib/__tests__/launchFailure.test.cjs +0 -220
  385. package/src/main/lib/__tests__/loadGate.test.cjs +0 -303
  386. package/src/main/lib/__tests__/localAdminHttp.test.cjs +0 -214
  387. package/src/main/lib/__tests__/loopDelay.test.cjs +0 -68
  388. package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +0 -107
  389. package/src/main/lib/__tests__/modelCatalog.test.cjs +0 -202
  390. package/src/main/lib/__tests__/opsOwnership.test.cjs +0 -113
  391. package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +0 -328
  392. package/src/main/lib/__tests__/opsRootNestedWrite.test.cjs +0 -51
  393. package/src/main/lib/__tests__/opsRootResolve.test.cjs +0 -149
  394. package/src/main/lib/__tests__/prdDeclaredPaths.test.cjs +0 -82
  395. package/src/main/lib/__tests__/prdDisposition.test.cjs +0 -224
  396. package/src/main/lib/__tests__/procIdentity.test.cjs +0 -119
  397. package/src/main/lib/__tests__/procName.test.cjs +0 -92
  398. package/src/main/lib/__tests__/projectBriefCore.test.cjs +0 -216
  399. package/src/main/lib/__tests__/projectRootResolve.test.cjs +0 -148
  400. package/src/main/lib/__tests__/queueHealth.test.cjs +0 -58
  401. package/src/main/lib/__tests__/queueStoreAtomicWrite.test.cjs +0 -88
  402. package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +0 -190
  403. package/src/main/lib/__tests__/quietMachineLease.test.cjs +0 -39
  404. package/src/main/lib/__tests__/rateLimitWindow.test.cjs +0 -88
  405. package/src/main/lib/__tests__/reaperHelpers.test.cjs +0 -577
  406. package/src/main/lib/__tests__/schedulerBatchDepends.test.cjs +0 -312
  407. package/src/main/lib/__tests__/schedulerBatchFairness.test.cjs +0 -213
  408. package/src/main/lib/__tests__/schedulerBatchLaunchHold.test.cjs +0 -125
  409. package/src/main/lib/__tests__/schedulerBatchProjectCap.test.cjs +0 -127
  410. package/src/main/lib/__tests__/schedulerBatchQuietMachine.test.cjs +0 -109
  411. package/src/main/lib/__tests__/schedulerMcpServerHeadlessRefusal.test.cjs +0 -71
  412. package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +0 -216
  413. package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +0 -183
  414. package/src/main/lib/__tests__/schedulerPaths.test.cjs +0 -226
  415. package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +0 -133
  416. package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +0 -56
  417. package/src/main/lib/__tests__/sessionSlots.test.cjs +0 -144
  418. package/src/main/lib/__tests__/telemetryBacklog.test.cjs +0 -626
  419. package/src/main/lib/__tests__/telemetryBoot.test.cjs +0 -134
  420. package/src/main/lib/__tests__/telemetryConsent.test.cjs +0 -136
  421. package/src/main/lib/__tests__/telemetryCounters.test.cjs +0 -57
  422. package/src/main/lib/__tests__/telemetryCountersMetadataColumn.test.cjs +0 -98
  423. package/src/main/lib/__tests__/terminalRunOutcome.test.cjs +0 -200
  424. package/src/main/lib/__tests__/toolUseClassify.test.cjs +0 -53
  425. package/src/main/lib/__tests__/updateCheck.test.cjs +0 -63
  426. package/src/main/lib/__tests__/upgradeDrain.test.cjs +0 -130
  427. package/src/main/lib/__tests__/usageCircuit.test.cjs +0 -354
  428. package/src/main/lib/__tests__/watchdog-helpers.test.cjs +0 -375
  429. package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +0 -266
  430. package/src/main/lib/kgExchangePairing.cjs +0 -75
@@ -1,170 +0,0 @@
1
- /**
2
- * queue-health-verdict.test.cjs — classifyQueueHealth is the Scheduler
3
- * page's queue-health header: the single honest read of WHY a queue looks
4
- * stale (saturated slots / blocked dependencies / genuinely idle / the
5
- * dispatch driver itself stalled / a decision to pause / an open launch
6
- * circuit breaker), reusing classifyQueueStarvation so the header can never
7
- * disagree with the starvation watchdog's own read of the same state.
8
- *
9
- * Run: timeout 180 npx vitest run src/main/__tests__/queue-health-verdict.test.cjs
10
- */
11
-
12
- 'use strict';
13
-
14
- import { test, afterAll } from 'vitest';
15
- const assert = require('node:assert/strict');
16
- const fs = require('node:fs');
17
- const os = require('node:os');
18
- const path = require('node:path');
19
-
20
- // HOME before require: every state path is baked from os.homedir() at load.
21
- const originalHome = process.env.HOME;
22
- const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'queue-health-test-'));
23
- process.env.HOME = tmpHome;
24
- fs.mkdirSync(path.join(tmpHome, '.claude', 'session-manager'), { recursive: true });
25
- fs.mkdirSync(path.join(tmpHome, '.claude', 'projects'), { recursive: true });
26
-
27
- const { classifyQueueHealth, QUEUE_STARVATION_MS } = require('../scheduler.cjs');
28
-
29
- afterAll(() => {
30
- process.env.HOME = originalHome;
31
- fs.rmSync(tmpHome, { recursive: true, force: true });
32
- });
33
-
34
- const CWD = '/home/bilko/Projects/starry-night-ships';
35
- const NOW = Date.parse('2026-09-13T20:00:00.000Z');
36
- const LONG = QUEUE_STARVATION_MS + 60_000;
37
-
38
- test('saturated: jobs running, zero free slots', () => {
39
- const jobs = [
40
- { slug: '1-a', cwd: CWD, status: 'running', startedAt: new Date(NOW - LONG).toISOString(), dependsOn: [] },
41
- { slug: '2-b', cwd: CWD, status: 'pending', dependsOn: [] },
42
- ];
43
- const v = classifyQueueHealth({
44
- jobs, paused: null, launchBlocks: {}, runningSet: new Set(['1-a']),
45
- freeSlots: 0, totalSlots: 4, lastRunAtMs: NOW, now: NOW, cwd: CWD,
46
- });
47
- assert.equal(v.kind, 'saturated');
48
- assert.equal(v.runningCount, 1);
49
- assert.equal(v.totalSlots, 4);
50
- // Dispatchable/blocked counts stay real numbers even in a kind whose
51
- // headline cause is slot saturation, not dependency shape — the header
52
- // must still be able to show them alongside the verdict.
53
- assert.equal(v.pending, 1);
54
- assert.equal(v.dispatchable, 1);
55
- assert.deepEqual(v.blockedChains, []);
56
- });
57
-
58
- test('blocked: every pending row terminates in a failed dependency', () => {
59
- const jobs = [
60
- { slug: '1-a', cwd: CWD, status: 'failed', dependsOn: [] },
61
- { slug: '2-b', cwd: CWD, status: 'pending', dependsOn: ['1-a'] },
62
- ];
63
- const v = classifyQueueHealth({
64
- jobs, paused: null, launchBlocks: {}, runningSet: new Set(),
65
- freeSlots: 4, totalSlots: 4, lastRunAtMs: NOW - 5_000, now: NOW, cwd: CWD,
66
- });
67
- assert.equal(v.kind, 'blocked');
68
- assert.equal(v.pending, 1);
69
- assert.equal(v.dispatchable, 0);
70
- assert.equal(v.blockedChains.length, 1);
71
- assert.deepEqual(v.blockedChains[0].blockedBy, ['1-a']);
72
- });
73
-
74
- test('idle: nothing pending', () => {
75
- const jobs = [
76
- { slug: '1-a', cwd: CWD, status: 'completed', dependsOn: [] },
77
- ];
78
- const v = classifyQueueHealth({
79
- jobs, paused: null, launchBlocks: {}, runningSet: new Set(),
80
- freeSlots: 4, totalSlots: 4, lastRunAtMs: NOW, now: NOW, cwd: CWD,
81
- });
82
- assert.equal(v.kind, 'idle');
83
- assert.equal(v.pending, 0);
84
- assert.equal(v.dispatchable, 0);
85
- });
86
-
87
- test('stalled: dispatchable work, nothing running, driver idle past threshold', () => {
88
- const jobs = [
89
- { slug: '1-a', cwd: CWD, status: 'pending', dependsOn: [] },
90
- ];
91
- const v = classifyQueueHealth({
92
- jobs, paused: null, launchBlocks: {}, runningSet: new Set(),
93
- freeSlots: 4, totalSlots: 4, lastRunAtMs: NOW - LONG, now: NOW, cwd: CWD,
94
- });
95
- assert.equal(v.kind, 'stalled');
96
- assert.equal(v.dispatchable, 1);
97
- assert.ok(v.idleMs >= QUEUE_STARVATION_MS);
98
- });
99
-
100
- test('running: dispatchable work, nothing running yet, but driver has not been idle long enough to call it a stall', () => {
101
- const jobs = [
102
- { slug: '1-a', cwd: CWD, status: 'pending', dependsOn: [] },
103
- ];
104
- const v = classifyQueueHealth({
105
- jobs, paused: null, launchBlocks: {}, runningSet: new Set(),
106
- freeSlots: 4, totalSlots: 4, lastRunAtMs: NOW - 5_000, now: NOW, cwd: CWD,
107
- });
108
- assert.equal(v.kind, 'running');
109
- });
110
-
111
- test('paused: a decision, not a stall — takes priority over everything else', () => {
112
- const jobs = [
113
- { slug: '1-a', cwd: CWD, status: 'pending', dependsOn: [] },
114
- ];
115
- const v = classifyQueueHealth({
116
- jobs, paused: { reason: 'manual', since: NOW }, launchBlocks: {}, runningSet: new Set(),
117
- freeSlots: 0, totalSlots: 4, lastRunAtMs: NOW - LONG, now: NOW, cwd: CWD,
118
- });
119
- assert.equal(v.kind, 'paused');
120
- assert.equal(v.reason, 'manual');
121
- assert.equal(v.pending, 1);
122
- assert.equal(v.dispatchable, 1);
123
- });
124
-
125
- test('launch-blocked: an open circuit breaker for a persona a pending row actually uses', () => {
126
- const jobs = [
127
- { slug: '1-a', cwd: CWD, status: 'pending', dependsOn: [], agentType: 'dev-lead' },
128
- ];
129
- const launchBlocks = {
130
- 'dev-lead': { kind: 'model_config_rejected', attempts: 3, hint: 'update the CLI', until: null },
131
- };
132
- const v = classifyQueueHealth({
133
- jobs, paused: null, launchBlocks, runningSet: new Set(),
134
- freeSlots: 4, totalSlots: 4, lastRunAtMs: NOW - LONG, now: NOW, cwd: CWD,
135
- });
136
- assert.equal(v.kind, 'launch-blocked');
137
- assert.equal(v.agentType, 'dev-lead');
138
- assert.equal(v.pending, 1);
139
- assert.equal(v.dispatchable, 1);
140
- });
141
-
142
- test('launch-blocked is scoped: a breaker for a persona nothing pending here uses does not fire', () => {
143
- const jobs = [
144
- { slug: '1-a', cwd: CWD, status: 'pending', dependsOn: [], agentType: 'dev-lead' },
145
- ];
146
- const launchBlocks = {
147
- 'architect': { kind: 'model_config_rejected', attempts: 3, hint: 'update the CLI', until: null },
148
- };
149
- const v = classifyQueueHealth({
150
- jobs, paused: null, launchBlocks, runningSet: new Set(),
151
- freeSlots: 4, totalSlots: 4, lastRunAtMs: NOW - 5_000, now: NOW, cwd: CWD,
152
- });
153
- assert.notEqual(v.kind, 'launch-blocked');
154
- });
155
-
156
- test('needsReviewCount and cwd scoping: another project\'s rows never leak into this scope\'s counts', () => {
157
- const other = '/home/bilko/Projects/session-manager';
158
- const jobs = [
159
- { slug: '1-a', cwd: CWD, status: 'needs_review', dependsOn: [] },
160
- { slug: '2-b', cwd: CWD, status: 'pending', dependsOn: [] },
161
- { slug: '3-c', cwd: other, status: 'pending', dependsOn: [] },
162
- { slug: '4-d', cwd: other, status: 'needs_review', dependsOn: [] },
163
- ];
164
- const v = classifyQueueHealth({
165
- jobs, paused: null, launchBlocks: {}, runningSet: new Set(),
166
- freeSlots: 4, totalSlots: 4, lastRunAtMs: NOW - 5_000, now: NOW, cwd: CWD,
167
- });
168
- assert.equal(v.pending, 1);
169
- assert.equal(v.needsReviewCount, 1);
170
- });
@@ -1,286 +0,0 @@
1
- /**
2
- * queue-starvation-dispatch-driver.test.cjs — two liveness gaps in the
3
- * queue-starvation watchdog itself, both observed live on 2026-09-11
4
- * (27 pending jobs across two projects, 0 running, for days):
5
- *
6
- * 1. classifyQueueStarvation/runQueueStarvationWatchdog used to measure
7
- * idleness from `lastRunAt`, which the poll loop kept looking fresh even
8
- * while nothing was actually dispatching — masking the very stall the
9
- * watchdog exists to catch. It must measure from `lastDispatchAttemptAt`
10
- * instead (stamped by tickQueue() the moment it reaches the picker,
11
- * regardless of outcome — see tickQueue's own comment).
12
- * 2. tickQueue()'s very first real guard returns {fired:false,
13
- * reason:'cancelled'} whenever the in-process cancelToken is wedged
14
- * true — historically only ever reset by runDueJobs() (force-tick/
15
- * run-now/resume-timer). A pause that clears WITHOUT going through
16
- * clearPause() (e.g. a direct queue-state write) leaves the token
17
- * wedged forever with nothing left to un-wedge it. The starvation
18
- * watchdog is the last line of defence, so it must reset the token
19
- * itself before forcing its tick.
20
- *
21
- * Run: timeout 180 npx vitest run src/main/__tests__/queue-starvation-dispatch-driver.test.cjs
22
- */
23
-
24
- 'use strict';
25
-
26
- import { test, expect, beforeAll, afterAll, vi } from 'vitest';
27
- const fs = require('node:fs');
28
- const os = require('node:os');
29
- const path = require('node:path');
30
- const claudeStub = require('../../../tests/helpers/claudeStub.cjs');
31
-
32
- let tmpHome;
33
- let originalHome;
34
- let originalClaudeBin;
35
- let originalAutofixDisable;
36
- let scheduler;
37
- let queueStore;
38
-
39
- // A forced tick actually dispatches the one pending job it finds. Without a
40
- // stub, that spawns the real `claude` binary (or fails trying to), leaving
41
- // scheduler.cjs's module-scoped runningSet non-empty by the time the next
42
- // test in this file runs — classifyQueueStarvation's `runningCount > 0`
43
- // guard then makes runQueueStarvationWatchdog return null for a reason that
44
- // has nothing to do with what that test is actually checking.
45
- function writeClaudeStub() {
46
- return claudeStub.writeClaudeStub({ body: `
47
- process.stdout.write(JSON.stringify({ type: 'result', subtype: 'success', result: 'ok\\nSCHEDULER_VERDICT: PASS' }) + '\\n');
48
- process.exit(0);
49
- ` });
50
- }
51
-
52
- beforeAll(() => {
53
- originalHome = process.env.HOME;
54
- originalClaudeBin = process.env.SM_CLAUDE_BIN;
55
- originalAutofixDisable = process.env.SM_AUTOFIX_DISABLE;
56
- tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-starve-driver-'));
57
- process.env.HOME = tmpHome;
58
- process.env.SM_CLAUDE_BIN = writeClaudeStub();
59
- // The forced tick's dispatched job has no PRD file and makes no commit, so
60
- // it lands 'needs_review' and (without this) immediately fires a
61
- // fire-and-forget auto-fix investigation — ANOTHER real claude spawn, under
62
- // its own queue row, that can still be in runningSet by the time the next
63
- // test in this file reads it (see writeClaudeStub's header comment; this
64
- // closes the same gap one layer further down the cascade).
65
- process.env.SM_AUTOFIX_DISABLE = '1';
66
- fs.mkdirSync(path.join(tmpHome, '.claude', 'session-manager'), { recursive: true });
67
- fs.mkdirSync(path.join(tmpHome, '.claude', 'projects'), { recursive: true });
68
- scheduler = require('../scheduler.cjs');
69
- queueStore = require('../lib/queueStore.cjs');
70
- });
71
-
72
- afterAll(() => {
73
- process.env.HOME = originalHome;
74
- if (originalClaudeBin === undefined) delete process.env.SM_CLAUDE_BIN;
75
- else process.env.SM_CLAUDE_BIN = originalClaudeBin;
76
- if (originalAutofixDisable === undefined) delete process.env.SM_AUTOFIX_DISABLE;
77
- else process.env.SM_AUTOFIX_DISABLE = originalAutofixDisable;
78
- fs.rmSync(tmpHome, { recursive: true, force: true });
79
- });
80
-
81
- // allProjectCwds()/activeProjectCwds() (queueStore.cjs's stateCwds) discover
82
- // project cwds by scanning ~/.claude/projects/*/*.jsonl for a `cwd` field —
83
- // fake one project transcript pointing at the fixture cwd, same pattern as
84
- // prdAdminRoutes.test.cjs / scheduler-verify-prd-path.test.cjs.
85
- function mkProject() {
86
- const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-starve-driver-project-'));
87
- const slug = `9002-starve-driver-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
88
- const projDir = fs.mkdtempSync(path.join(tmpHome, '.claude', 'projects', 'sm-starve-driver-fake-'));
89
- fs.writeFileSync(path.join(projDir, 'session.jsonl'), `${JSON.stringify({ cwd })}\n`, 'utf8');
90
- return { cwd, slug };
91
- }
92
-
93
- test('ticks that launch nothing do NOT refresh the clock — idleness reads lastRunAt, not lastDispatchAttemptAt', async () => {
94
- const { cwd, slug } = mkProject();
95
- await scheduler.writeQueue({
96
- jobs: [{ slug, title: 'x', cwd, status: 'pending', dependsOn: [] }],
97
- config: {},
98
- paused: null,
99
- });
100
-
101
- const now = Date.now();
102
- // The structural repeat of f18e161: the loop ticks every 30 s (so
103
- // lastDispatchAttemptAt is seconds old) but nothing has LAUNCHED in 40
104
- // minutes (lastRunAt). The boot stamp is aged out so it can't mask it.
105
- const bootedAtMs = now - 3 * 60 * 60_000;
106
- const state = {
107
- ...queueStore.readMergedSync(),
108
- lastRunAt: new Date(now - 40 * 60_000).toISOString(),
109
- lastDispatchAttemptAt: new Date(now - 5_000).toISOString(),
110
- };
111
-
112
- const verdict = await scheduler.runQueueStarvationWatchdog(state, { now, bootedAtMs });
113
- expect(verdict).not.toBeNull();
114
- expect(verdict.kind).toBe('starved');
115
-
116
- // The watchdog's forced tick above dispatches this job for real (see
117
- // writeClaudeStub's header comment) — tickQueue doesn't await the spawned
118
- // process to completion, so runningSet can still hold this slug for a
119
- // moment after this test's own `await` returns. Under a fast, unloaded
120
- // machine the stub exits before the NEXT test's watchdog call ever reads
121
- // runningSet, which is racy rather than deterministic — under a heavily
122
- // loaded machine (e.g. the full test:unit run) it reliably loses,
123
- // stranding a slug in runningSet and making
124
- // classifyQueueStarvation's `runningCount > 0` guard return null for the
125
- // next test, which has nothing to do with what that test actually checks.
126
- // Wait for this job to actually leave 'running' before moving on.
127
- const deadline = Date.now() + 5_000;
128
- while (Date.now() < deadline) {
129
- const row = queueStore.readMergedSync().jobs.find((j) => j.slug === slug);
130
- if (!row || row.status !== 'running') break;
131
- await new Promise((r) => setTimeout(r, 25));
132
- }
133
- });
134
-
135
- test('a wedged cancelToken does not block the watchdog from actually driving a tick', async () => {
136
- const { cwd, slug } = mkProject();
137
- await scheduler.writeQueue({
138
- jobs: [{ slug, title: 'x', cwd, status: 'pending', dependsOn: [] }],
139
- config: {},
140
- paused: null,
141
- });
142
-
143
- // Wedge the in-process cancel token exactly the way a pause does, then
144
- // clear the pause through a path that does NOT run clearPause()'s own
145
- // applyPauseCleared reset (a direct state write, not the clearPause()
146
- // helper) — leaving the token permanently cancelled with paused: null.
147
- await scheduler.setPaused('rate_limit', null);
148
- // setPaused's own broadcast({flush:true}) and a still-draining
149
- // fire-and-forget mutate() from the PRIOR test's forced dispatch (e.g.
150
- // tickQueue's own finally-chained call) both write through the SAME
151
- // serialized mutate() queue this raw writeQueue() call bypasses — one of
152
- // them can land AFTER this write and re-persist the stale paused:{...}
153
- // object it read before this clear, purely by losing the race. Re-assert
154
- // paused:null until it actually sticks, rather than trusting one write to
155
- // win a race against mutations this test doesn't control the timing of.
156
- const pausedClearDeadline = Date.now() + 5_000;
157
- do {
158
- await scheduler.writeQueue({
159
- jobs: [{ slug, title: 'x', cwd, status: 'pending', dependsOn: [] }],
160
- config: {},
161
- paused: null,
162
- });
163
- if (!queueStore.readMergedSync().paused) break;
164
- await new Promise((r) => setTimeout(r, 25));
165
- } while (Date.now() < pausedClearDeadline);
166
-
167
- const before = queueStore.readMergedSync().lastDispatchAttemptAt;
168
-
169
- const now = Date.now();
170
- const state = {
171
- ...queueStore.readMergedSync(),
172
- lastRunAt: new Date(now - 40 * 60_000).toISOString(),
173
- lastDispatchAttemptAt: new Date(now - 40 * 60_000).toISOString(),
174
- };
175
- const verdict = await scheduler.runQueueStarvationWatchdog(state, { now, bootedAtMs: now - 3 * 60 * 60_000 });
176
- expect(verdict.kind).toBe('starved');
177
-
178
- // tickQueue() only stamps lastDispatchAttemptAt once it gets PAST the
179
- // cancelToken guard — so this only advances if the watchdog actually
180
- // un-wedged the token before forcing its tick.
181
- const after = queueStore.readMergedSync().lastDispatchAttemptAt;
182
- expect(after).not.toBe(before);
183
- expect(after).not.toBeNull();
184
- });
185
-
186
- const NOW = Date.parse('2026-09-18T12:00:00.000Z');
187
- const CWD = '/tmp/sm-starve-clock-project';
188
- const pendingRow = (slug, extra = {}) => ({ slug, cwd: CWD, status: 'pending', dependsOn: [], ...extra });
189
- const idleFor = (ms) => NOW - ms;
190
-
191
- test('a real launch (fresh lastRunAt) refreshes the clock; so do a pause clear and a boot', () => {
192
- const jobs = [pendingRow('a')];
193
- const base = { jobs, paused: null, runningCount: 0, now: NOW };
194
- const stale = idleFor(3 * 60 * 60_000);
195
- expect(scheduler.classifyQueueStarvation({ ...base, lastRunAtMs: stale }).kind).toBe('starved');
196
- expect(scheduler.classifyQueueStarvation({ ...base, lastRunAtMs: idleFor(5_000) })).toBeNull();
197
- expect(scheduler.classifyQueueStarvation({ ...base, lastRunAtMs: stale, lastPauseClearedAtMs: idleFor(5_000) })).toBeNull();
198
- expect(scheduler.classifyQueueStarvation({ ...base, lastRunAtMs: stale, schedulerBootedAtMs: idleFor(5_000) })).toBeNull();
199
- expect(scheduler.dispatchIdleMs({ lastRunAtMs: 1000, lastPauseClearedAtMs: 4000, schedulerBootedAtMs: 2000, now: 10_000 })).toBe(6000);
200
- expect(scheduler.dispatchIdleMs({ now: 10_000 })).toBe(Infinity);
201
- });
202
-
203
- test('rows held by a launch breaker / lease read blocked, not starved (per project too)', () => {
204
- const jobs = [pendingRow('a'), pendingRow('b')];
205
- const args = { jobs, paused: null, runningCount: 0, lastRunAtMs: idleFor(3 * 60 * 60_000), now: NOW };
206
- expect(scheduler.classifyQueueStarvation(args).kind).toBe('starved');
207
- const partial = scheduler.classifyQueueStarvation({ ...args, heldSlugs: new Set(['a']) });
208
- expect(partial.kind).toBe('starved');
209
- expect(partial.dispatchable).toBe(1);
210
- const all = scheduler.classifyQueueStarvation({ ...args, heldSlugs: new Map([['a', 'x'], ['b', 'y']]) });
211
- expect(all.kind).toBe('blocked');
212
- expect(all.dispatchable).toBe(0);
213
- const byProject = scheduler.classifyQueueStarvationByProject({
214
- jobs, paused: null, runningSet: new Set(), lastRunAtMs: args.lastRunAtMs, heldSlugs: new Set(['a', 'b']), now: NOW,
215
- });
216
- expect(byProject.map((v) => v.kind)).toEqual(['blocked']);
217
- });
218
-
219
- test('a starved queue forces its tick with bypassLoadGate:true — the CPU load gate must not freeze the last-resort dispatch', async () => {
220
- // Regression for the 2026-09-23 freeze: 3 dispatchable jobs, 0 running,
221
- // lastTickReason 'load-deferred' unbroken 07:00→13:06. The watchdog is the
222
- // safety net for exactly that state, but it used to force its tick with
223
- // bypassLoadGate:false, so a box loaded by ANOTHER process held the net's
224
- // own tick behind the load gate and nothing ever ran. Spy the exported
225
- // tickQueue seam (the call is routed via module.exports precisely so this
226
- // is assertable without manufacturing real machine load, which
227
- // SM_LOAD_GATE_PER_CORE clamps at 0.25 and can't force deterministically).
228
- const cwd = '/tmp/sm-starve-bypass-project';
229
- const jobs = [{ slug: 'bypass-a', cwd, status: 'pending', dependsOn: [] }];
230
- const state = { jobs, paused: null, lastRunAt: new Date(NOW - 40 * 60_000).toISOString() };
231
- const opts = { now: NOW, bootedAtMs: NOW - 3 * 60 * 60_000 };
232
- const tickSpy = vi.spyOn(scheduler, 'tickQueue').mockResolvedValue({ fired: false, reason: 'drained' });
233
- try {
234
- const verdict = await scheduler.runQueueStarvationWatchdog(state, opts);
235
- expect(verdict.kind).toBe('starved');
236
- expect(tickSpy).toHaveBeenCalledTimes(1);
237
- expect(tickSpy).toHaveBeenCalledWith({ bypassLoadGate: true });
238
- } finally {
239
- tickSpy.mockRestore();
240
- }
241
- });
242
-
243
- test('the bypass is not unconditional — a queue with something already running never force-ticks at all', async () => {
244
- // Guard against the bypass becoming an escape hatch: the watchdog only
245
- // reaches its forced tick on a genuine 'starved' verdict (0 running). When
246
- // a job is already running, classifyQueueStarvation returns null and no
247
- // forced tick fires — so bypassLoadGate never even comes into play.
248
- const cwd = '/tmp/sm-running-nobypass-project';
249
- const jobs = [
250
- { slug: 'run-a', cwd, status: 'running', dependsOn: [] },
251
- { slug: 'pend-b', cwd, status: 'pending', dependsOn: [] },
252
- ];
253
- const state = { jobs, paused: null, lastRunAt: new Date(NOW - 40 * 60_000).toISOString() };
254
- const opts = { now: NOW, bootedAtMs: NOW - 3 * 60 * 60_000 };
255
- const tickSpy = vi.spyOn(scheduler, 'tickQueue').mockResolvedValue({ fired: false, reason: 'drained' });
256
- try {
257
- const verdict = await scheduler.runQueueStarvationWatchdog(state, opts);
258
- expect(verdict).toBeNull();
259
- expect(tickSpy).not.toHaveBeenCalled();
260
- } finally {
261
- tickSpy.mockRestore();
262
- }
263
- });
264
-
265
- test('the watchdog latches: one forced tick / audit per starve episode, re-armed by threshold or running-count change', async () => {
266
- const cwd = '/tmp/sm-starve-latch-project';
267
- const jobs = [{ slug: 'latch-a', cwd, status: 'pending', dependsOn: [] }];
268
- const opts = { bootedAtMs: NOW - 3 * 60 * 60_000 };
269
- const state = { jobs, paused: null, lastRunAt: new Date(NOW - 40 * 60_000).toISOString() };
270
- const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
271
- const starvedLogs = () => warn.mock.calls.filter((c) => String(c[0]).includes('QUEUE STARVED')).length;
272
- try {
273
- // The row exists only in this hand-built state, never in queue.json, so
274
- // the forced tick has nothing real to launch.
275
- expect((await scheduler.runQueueStarvationWatchdog(state, { now: NOW, ...opts })).kind).toBe('starved');
276
- expect(starvedLogs()).toBe(1);
277
- // Same episode, well inside QUEUE_STARVATION_MS: still reported, not re-forced.
278
- expect((await scheduler.runQueueStarvationWatchdog(state, { now: NOW + 60_000, ...opts })).kind).toBe('starved');
279
- expect(starvedLogs()).toBe(1);
280
- // QUEUE_STARVATION_MS elapsed again: a new episode.
281
- await scheduler.runQueueStarvationWatchdog(state, { now: NOW + scheduler.QUEUE_STARVATION_MS + 1, ...opts });
282
- expect(starvedLogs()).toBe(2);
283
- } finally {
284
- warn.mockRestore();
285
- }
286
- });
@@ -1,147 +0,0 @@
1
- /**
2
- * queue-starvation-per-project.test.cjs — classifyQueueStarvation's
3
- * runningCount used to be fed the MACHINE-WIDE runningSet.size, so one
4
- * long-lived job in ANY project disarmed the starvation watchdog for EVERY
5
- * other project on the box. Observed live 2026-09-12: a job in
6
- * starry-night-ships ran 80+ minutes while two other projects sat
7
- * starved/blocked for hours — the watchdog never fired once because "work is
8
- * flowing" was true somewhere else.
9
- *
10
- * classifyQueueStarvationByProject partitions jobs by cwd (the same grouping
11
- * computeBlockedChains already uses) and evaluates each project on its OWN
12
- * running/pending/idle evidence.
13
- *
14
- * Run: timeout 180 npx vitest run src/main/__tests__/queue-starvation-per-project.test.cjs
15
- */
16
-
17
- 'use strict';
18
-
19
- import { test, afterAll } from 'vitest';
20
- const assert = require('node:assert/strict');
21
- const fs = require('node:fs');
22
- const os = require('node:os');
23
- const path = require('node:path');
24
-
25
- // HOME before require: every state path is baked from os.homedir() at load.
26
- // beforeAll runs AFTER module evaluation, so this assignment must stay at
27
- // top level — see pollLoop-dispatch-on-failure.test.cjs:40-56 for the
28
- // beforeAll/afterAll pattern this restore mirrors.
29
- const originalHome = process.env.HOME;
30
- const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'starve-per-project-test-'));
31
- process.env.HOME = tmpHome;
32
- fs.mkdirSync(path.join(tmpHome, '.claude', 'session-manager'), { recursive: true });
33
- fs.mkdirSync(path.join(tmpHome, '.claude', 'projects'), { recursive: true });
34
-
35
- const {
36
- classifyQueueStarvation,
37
- classifyQueueStarvationByProject,
38
- QUEUE_STARVATION_MS,
39
- } = require('../scheduler.cjs');
40
-
41
- afterAll(() => {
42
- process.env.HOME = originalHome;
43
- fs.rmSync(tmpHome, { recursive: true, force: true });
44
- });
45
-
46
- const PROJECT_A = '/home/bilko/Projects/starry-night-ships';
47
- const PROJECT_B = '/home/bilko/Projects/session-manager';
48
- const NOW = Date.parse('2026-09-12T20:00:00.000Z');
49
- const LONG = QUEUE_STARVATION_MS + 60_000;
50
- const idleFor = (ms) => NOW - ms;
51
-
52
- test('one long-running job in project A no longer masks project B being starved', () => {
53
- const jobs = [
54
- { slug: '246-neptune', cwd: PROJECT_A, status: 'running', dependsOn: [] },
55
- { slug: '1169-x', cwd: PROJECT_B, status: 'pending', dependsOn: [] },
56
- { slug: '1170-y', cwd: PROJECT_B, status: 'pending', dependsOn: [] },
57
- ];
58
-
59
- // Pre-change shape: the machine-wide runningCount (1, from project A) hides
60
- // project B's starvation entirely. This is the bug this PRD fixes — assert
61
- // it still holds for the old, single-project entry point so a revert of
62
- // the new driver is caught by this test going red.
63
- const globalVerdict = classifyQueueStarvation({
64
- jobs,
65
- paused: false,
66
- runningCount: 1,
67
- lastRunAtMs: idleFor(LONG),
68
- now: NOW,
69
- });
70
- assert.equal(globalVerdict, null);
71
-
72
- // Per-project: project B has 0 of its OWN rows running and 2 dispatchable
73
- // pending rows idle past the threshold — it must be reported as starved
74
- // regardless of project A's running job.
75
- const verdicts = classifyQueueStarvationByProject({
76
- jobs,
77
- paused: false,
78
- runningSet: new Set(),
79
- lastRunAtMs: idleFor(LONG),
80
- now: NOW,
81
- });
82
- const forB = verdicts.find((v) => v.cwd === PROJECT_B);
83
- assert.ok(forB, 'expected a verdict naming project B cwd');
84
- assert.equal(forB.kind, 'starved');
85
- assert.equal(forB.pending, 2);
86
- assert.equal(forB.dispatchable, 2);
87
-
88
- // Project A itself has a running row — never starved on its own account.
89
- const forA = verdicts.find((v) => v.cwd === PROJECT_A);
90
- assert.equal(forA, undefined);
91
- });
92
-
93
- test('a project fully blocked behind a terminal dependency is "blocked", not "starved", even while another project is starved in the same pass', () => {
94
- const jobs = [
95
- // Project A: every pending row sits behind a failed dependency — a tick
96
- // cannot help this project, it needs a human/heal pass.
97
- { slug: '206-lava-ape', cwd: PROJECT_A, status: 'failed', dependsOn: [] },
98
- { slug: '207-body', cwd: PROJECT_A, status: 'pending', dependsOn: ['206-lava-ape'] },
99
- // Project B: dispatchable right now — genuinely starved.
100
- { slug: '1171-z', cwd: PROJECT_B, status: 'pending', dependsOn: [] },
101
- ];
102
-
103
- const verdicts = classifyQueueStarvationByProject({
104
- jobs,
105
- paused: false,
106
- runningSet: new Set(),
107
- lastRunAtMs: idleFor(LONG),
108
- now: NOW,
109
- });
110
-
111
- const forA = verdicts.find((v) => v.cwd === PROJECT_A);
112
- assert.ok(forA, 'expected a verdict for the blocked project');
113
- assert.equal(forA.kind, 'blocked');
114
-
115
- const forB = verdicts.find((v) => v.cwd === PROJECT_B);
116
- assert.ok(forB, 'expected a verdict for the starved project');
117
- assert.equal(forB.kind, 'starved');
118
- });
119
-
120
- test('the whole-machine paused short-circuit is preserved for every project', () => {
121
- const jobs = [
122
- { slug: '1169-x', cwd: PROJECT_A, status: 'pending', dependsOn: [] },
123
- { slug: '1170-y', cwd: PROJECT_B, status: 'pending', dependsOn: [] },
124
- ];
125
- const verdicts = classifyQueueStarvationByProject({
126
- jobs,
127
- paused: { reason: 'rate_limit', resumeAt: null },
128
- runningSet: new Set(),
129
- lastRunAtMs: idleFor(LONG),
130
- now: NOW,
131
- });
132
- assert.deepEqual(verdicts, []);
133
- });
134
-
135
- test('a running-set membership (not just a status:"running" row) counts as that project having flowing work', () => {
136
- const jobs = [
137
- { slug: '9001-a', cwd: PROJECT_A, status: 'pending', dependsOn: [] },
138
- ];
139
- const verdicts = classifyQueueStarvationByProject({
140
- jobs,
141
- paused: false,
142
- runningSet: new Set(['9001-a']),
143
- lastRunAtMs: idleFor(LONG),
144
- now: NOW,
145
- });
146
- assert.deepEqual(verdicts, []);
147
- });