claude-code-session-manager 0.86.0 → 0.88.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/README.md +26 -53
  2. package/dist/assets/AgentLibrary-BZ6IY2g1.js +3 -0
  3. package/dist/assets/{DataModel-Q4jhl24R.js → DataModel-dAoGAODX.js} +1 -1
  4. package/dist/assets/{History-Cj2FejEo.js → History-CF157HyX.js} +2 -2
  5. package/dist/assets/{Hooks-CaelQI6t.js → Hooks-CCPPipBk.js} +3 -3
  6. package/dist/assets/{HostBilko--v7cMR8I.js → HostBilko-Dlmz2m06.js} +1 -1
  7. package/dist/assets/{Library-DgI9oCCZ.js → Library-BaXcboWX.js} +1 -1
  8. package/dist/assets/{ListDetail-DYUZN-x-.js → ListDetail-CoUCzG5S.js} +1 -1
  9. package/dist/assets/MarkdownEditor-DTg3JlOT.js +1 -0
  10. package/dist/assets/{McpServers-ypCYURh3.js → McpServers-DdjY1sA3.js} +2 -2
  11. package/dist/assets/{Memory-C2qYp-3M.js → Memory-BJ3Jlw0C.js} +4 -4
  12. package/dist/assets/{Panel-Cj2kw-Zv.js → Panel-ygRSgc41.js} +1 -1
  13. package/dist/assets/Permissions-m0KAnccF.js +3 -0
  14. package/dist/assets/{Plugins-C1Vj8_dU.js → Plugins-CJRl6B3Q.js} +2 -2
  15. package/dist/assets/{ProvenanceBadge-DczPNM5U.js → ProvenanceBadge-w8cnlVvW.js} +1 -1
  16. package/dist/assets/{SaveBar-Cd_7U6Gb.js → SaveBar-BpyPcUIk.js} +1 -1
  17. package/dist/assets/Scheduler-D2ppxSfy.js +14 -0
  18. package/dist/assets/{ScopeSwitcher-DVSyI44-.js → ScopeSwitcher-EyuIwt-j.js} +1 -1
  19. package/dist/assets/Settings-BHSGGFEc.js +3 -0
  20. package/dist/assets/{SkillReferenceGraph-CUv1_Q2c.js → SkillReferenceGraph-BNaZ2JCF.js} +1 -1
  21. package/dist/assets/{Skills-C_YHkAy-.js → Skills-Dl3q75QV.js} +2 -2
  22. package/dist/assets/{SystemPrompt-B8R7T9xn.js → SystemPrompt-Bg6F-Cyp.js} +1 -1
  23. package/dist/assets/{TagLibrary-dj9YHWyy.js → TagLibrary-BSutflCy.js} +1 -1
  24. package/dist/assets/{TiptapBody-DnSBUjHE.js → TiptapBody-CXih4xrc.js} +1 -1
  25. package/dist/assets/{Toggle-CjV_BJn6.js → Toggle-2qtUHhA9.js} +1 -1
  26. package/dist/assets/{index-CXFQIPhO.js → index-B3emy2oI.js} +583 -583
  27. package/dist/assets/{index-CDo9xBR9.css → index-BHTX4OTc.css} +1 -1
  28. package/dist/assets/settingsSchema-CdzxaqwM.js +3 -0
  29. package/dist/assets/{whisperWorker-Dbia1OpC.js → whisperWorker-C7ZGQwKg.js} +7 -7
  30. package/dist/index.html +2 -2
  31. package/dist/vad/ort-wasm-simd-threaded.asyncify.mjs +106 -110
  32. package/dist/vad/ort-wasm-simd-threaded.asyncify.wasm +0 -0
  33. package/dist/vad/ort-wasm-simd-threaded.jsep.mjs +98 -98
  34. package/dist/vad/ort-wasm-simd-threaded.jsep.wasm +0 -0
  35. package/dist/vad/ort-wasm-simd-threaded.jspi.mjs +99 -102
  36. package/dist/vad/ort-wasm-simd-threaded.jspi.wasm +0 -0
  37. package/dist/vad/ort-wasm-simd-threaded.mjs +46 -46
  38. package/dist/vad/ort-wasm-simd-threaded.wasm +0 -0
  39. package/package.json +14 -17
  40. package/plugins/CLAUDE.md +61 -0
  41. package/plugins/session-manager-dev/.claude-plugin/plugin.json +1 -1
  42. package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +1 -1
  43. package/plugins/session-manager-dev/skills/ops-sweep/SKILL.md +1 -1
  44. package/scripts/README.md +59 -0
  45. package/scripts/audit-ops-hygiene.cjs +350 -0
  46. package/scripts/hooks/guard-destructive-git.cjs +10 -34
  47. package/scripts/hooks/guard-inline-implementation.cjs +14 -8
  48. package/scripts/hooks/guard-prd-writes.cjs +7 -36
  49. package/scripts/hooks/guard-self-schedule.cjs +175 -0
  50. package/scripts/ops-sweep.cjs +355 -0
  51. package/scripts/scheduler-mcp-server.cjs +35 -71
  52. package/src/main/__tests__/agentModelResolve.test.cjs +100 -9
  53. package/src/main/__tests__/bilkoHost-integration.test.cjs +3 -3
  54. package/src/main/__tests__/broadcastCoalescer.test.cjs +18 -0
  55. package/src/main/__tests__/chat-cancel-terminal.test.cjs +6 -9
  56. package/src/main/__tests__/chat-exit-close-race.test.cjs +3 -3
  57. package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +4 -5
  58. package/src/main/__tests__/chat-queue.test.cjs +2 -2
  59. package/src/main/__tests__/chat-stop-signal.test.cjs +2 -2
  60. package/src/main/__tests__/dep-orphan-archive-health.test.cjs +77 -0
  61. package/src/main/__tests__/dod-batchkey.test.cjs +2 -2
  62. package/src/main/__tests__/dod-drain-hook.test.cjs +2 -2
  63. package/src/main/__tests__/dod-report.test.cjs +2 -2
  64. package/src/main/__tests__/dod-reverify.test.cjs +2 -2
  65. package/src/main/__tests__/epicMint.test.cjs +4 -4
  66. package/src/main/__tests__/exchanges.test.cjs +2 -2
  67. package/src/main/__tests__/extractJson.test.cjs +2 -2
  68. package/src/main/__tests__/files-reject-credentials.test.cjs +1 -1
  69. package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +556 -0
  70. package/src/main/__tests__/health-build-freshness.test.cjs +39 -0
  71. package/src/main/__tests__/health-delegation-chain.test.cjs +17 -2
  72. package/src/main/__tests__/health-queue-dispatch.test.cjs +58 -7
  73. package/src/main/__tests__/health-tick-liveness.test.cjs +11 -3
  74. package/src/main/__tests__/health-usage-poller.test.cjs +70 -23
  75. package/src/main/__tests__/historyRollup.test.cjs +2 -2
  76. package/src/main/__tests__/kg-augment.test.cjs +2 -2
  77. package/src/main/__tests__/mcpStatus.test.cjs +2 -2
  78. package/src/main/__tests__/memoryAggregate.test.cjs +1 -1
  79. package/src/main/__tests__/memoryStale.test.cjs +1 -1
  80. package/src/main/__tests__/needsReviewLedger.test.cjs +162 -0
  81. package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +28 -4
  82. package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +48 -4
  83. package/src/main/__tests__/prd-group-allocator.test.cjs +2 -2
  84. package/src/main/__tests__/prdAdminRouteParity.test.cjs +2 -0
  85. package/src/main/__tests__/prdAdminRoutes.test.cjs +14 -2
  86. package/src/main/__tests__/prdAuthoringSeed.test.cjs +39 -0
  87. package/src/main/__tests__/prdCreateDisposition.test.cjs +201 -0
  88. package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +125 -0
  89. package/src/main/__tests__/prdLocations.test.cjs +100 -2
  90. package/src/main/__tests__/prdLocationsArchived.test.cjs +50 -1
  91. package/src/main/__tests__/prdSetDisposition.test.cjs +222 -0
  92. package/src/main/__tests__/proc-role-env.test.cjs +125 -0
  93. package/src/main/__tests__/procname-claude-spawn-sites.test.cjs +304 -0
  94. package/src/main/__tests__/procname-sm-processes.test.cjs +127 -0
  95. package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +81 -401
  96. package/src/main/__tests__/projectPages.test.cjs +63 -149
  97. package/src/main/__tests__/queue-health-verdict.test.cjs +170 -0
  98. package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +117 -20
  99. package/src/main/__tests__/queue-starvation-per-project.test.cjs +14 -2
  100. package/src/main/__tests__/queueHistory.test.cjs +65 -2
  101. package/src/main/__tests__/rateLimitPollerStreak.test.cjs +38 -3
  102. package/src/main/__tests__/reconcileTiming.test.cjs +135 -0
  103. package/src/main/__tests__/runVerify-landed-commit-outranks.test.cjs +181 -0
  104. package/src/main/__tests__/runVerify.test.cjs +5 -5
  105. package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +3 -3
  106. package/src/main/__tests__/scheduleJobTransitions.test.cjs +103 -3
  107. package/src/main/__tests__/scheduler-adopted-run-supervision.test.cjs +143 -0
  108. package/src/main/__tests__/scheduler-autofix-select.test.cjs +2 -2
  109. package/src/main/__tests__/scheduler-autopromote.test.cjs +2 -2
  110. package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +3 -5
  111. package/src/main/__tests__/scheduler-boot-orphans.test.cjs +79 -98
  112. package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +121 -0
  113. package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +43 -0
  114. package/src/main/__tests__/scheduler-default-eligible-heal.test.cjs +161 -0
  115. package/src/main/__tests__/scheduler-dispatch-loop.test.cjs +58 -0
  116. package/src/main/__tests__/scheduler-epic-digest.test.cjs +3 -5
  117. package/src/main/__tests__/scheduler-force-tick-outcome.test.cjs +2 -2
  118. package/src/main/__tests__/scheduler-gate-shadow.test.cjs +119 -0
  119. package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +390 -0
  120. package/src/main/__tests__/scheduler-heartbeat-payload.test.cjs +80 -0
  121. package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +25 -18
  122. package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +4 -4
  123. package/src/main/__tests__/scheduler-job-budget.test.cjs +172 -0
  124. package/src/main/__tests__/scheduler-launch-failure.test.cjs +3 -5
  125. package/src/main/__tests__/scheduler-looks-done.test.cjs +119 -10
  126. package/src/main/__tests__/scheduler-manual-pause.test.cjs +118 -0
  127. package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +26 -3
  128. package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +164 -0
  129. package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +17 -2
  130. package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +3 -5
  131. package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +41 -6
  132. package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +62 -6
  133. package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +3 -5
  134. package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +182 -3
  135. package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +87 -0
  136. package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +100 -0
  137. package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +3 -3
  138. package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +4 -4
  139. package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +110 -0
  140. package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +168 -0
  141. package/src/main/__tests__/scheduler-starve-escalation.test.cjs +18 -8
  142. package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +245 -0
  143. package/src/main/__tests__/scheduler-supervisor-record.test.cjs +81 -0
  144. package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +2 -2
  145. package/src/main/__tests__/scheduler-tick-wedge.test.cjs +172 -0
  146. package/src/main/__tests__/scheduler-transient-failure.test.cjs +2 -2
  147. package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +51 -11
  148. package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +3 -5
  149. package/src/main/__tests__/usageSingleFlight.test.cjs +157 -0
  150. package/src/main/__tests__/workTypeLibrary.test.cjs +1 -1
  151. package/src/main/bilkoHost.cjs +16 -7
  152. package/src/main/build-info.json +8 -0
  153. package/src/main/chatRunner.cjs +12 -7
  154. package/src/main/config.cjs +2 -3
  155. package/src/main/docEdit.cjs +4 -2
  156. package/src/main/health.cjs +268 -52
  157. package/src/main/heapSnapshot.cjs +2 -2
  158. package/src/main/historyAggregator.cjs +6 -1
  159. package/src/main/index.cjs +152 -50
  160. package/src/main/ipcSchemas.cjs +53 -17
  161. package/src/main/lib/__tests__/active-sessions.test.cjs +251 -0
  162. package/src/main/lib/__tests__/auditLog.test.cjs +38 -0
  163. package/src/main/lib/__tests__/bootSelfHeal.test.cjs +107 -0
  164. package/src/main/lib/__tests__/buildIdentity.test.cjs +121 -0
  165. package/src/main/lib/__tests__/cwdClassify.test.cjs +111 -0
  166. package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +95 -0
  167. package/src/main/lib/__tests__/delegationReadiness.test.cjs +323 -44
  168. package/src/main/lib/__tests__/dispatchLoop.test.cjs +63 -0
  169. package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +239 -0
  170. package/src/main/lib/__tests__/gateFixtures.json +20 -0
  171. package/src/main/lib/__tests__/gitWorktree.test.cjs +97 -1
  172. package/src/main/lib/__tests__/guardShims.test.cjs +151 -0
  173. package/src/main/lib/__tests__/instanceLock.test.cjs +93 -7
  174. package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +78 -0
  175. package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +11 -0
  176. package/src/main/lib/__tests__/localAdminHttp.test.cjs +1 -1
  177. package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +6 -1
  178. package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +5 -5
  179. package/src/main/lib/__tests__/prdDisposition.test.cjs +224 -0
  180. package/src/main/lib/__tests__/procIdentity.test.cjs +119 -0
  181. package/src/main/lib/__tests__/procName.test.cjs +92 -0
  182. package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +67 -0
  183. package/src/main/lib/__tests__/reaperHelpers.test.cjs +179 -1
  184. package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +6 -7
  185. package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +37 -204
  186. package/src/main/lib/__tests__/schedulerPaths.test.cjs +226 -0
  187. package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +94 -0
  188. package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +56 -0
  189. package/src/main/lib/__tests__/sessionSlots.test.cjs +45 -0
  190. package/src/main/lib/__tests__/telemetryBacklog.test.cjs +1 -1
  191. package/src/main/lib/__tests__/upgradeDrain.test.cjs +130 -0
  192. package/src/main/lib/__tests__/usageCircuit.test.cjs +285 -0
  193. package/src/main/lib/__tests__/watchdog-helpers.test.cjs +375 -0
  194. package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +266 -0
  195. package/src/main/lib/activeIndexRebuild.cjs +1 -3
  196. package/{scripts → src/main}/lib/activeSessions.cjs +67 -89
  197. package/src/main/lib/adoptedRunSupervisor.cjs +136 -0
  198. package/src/main/lib/agentModelResolve.cjs +94 -24
  199. package/src/main/lib/agentPersonaSchema.cjs +2 -2
  200. package/src/main/lib/auditLog.cjs +30 -5
  201. package/src/main/lib/bootSelfHeal.cjs +88 -0
  202. package/src/main/lib/buildIdentity.cjs +113 -0
  203. package/src/main/lib/classifyPromptTicket.cjs +3 -2
  204. package/src/main/lib/claudeBin.cjs +37 -2
  205. package/src/main/lib/cleanEnv.cjs +27 -1
  206. package/src/main/lib/credentials.cjs +4 -2
  207. package/src/main/lib/cwdClassify.cjs +185 -0
  208. package/src/main/lib/definitionOfDone.cjs +296 -52
  209. package/src/main/lib/delegationReadiness.cjs +290 -225
  210. package/src/main/lib/dispatchLoop.cjs +40 -0
  211. package/src/main/lib/effectiveModelInfo.cjs +332 -0
  212. package/src/main/lib/ephemeralCwd.cjs +7 -28
  213. package/src/main/lib/epicMint.cjs +3 -3
  214. package/src/main/lib/gitWorktree.cjs +136 -19
  215. package/src/main/lib/guardShims.cjs +156 -0
  216. package/src/main/lib/historyRollup.cjs +6 -7
  217. package/src/main/lib/instanceLock.cjs +32 -5
  218. package/src/main/lib/jobDirtFilter.cjs +7 -2
  219. package/src/main/lib/jobSupervisorRecord.cjs +147 -0
  220. package/src/main/lib/jobWorktreeBootLive.cjs +9 -5
  221. package/src/main/lib/launchFailure.cjs +2 -1
  222. package/src/main/lib/localAdminHttp.cjs +9 -27
  223. package/src/main/lib/mcpToolCatalog.cjs +33 -78
  224. package/src/main/lib/needsReviewLedger.cjs +205 -0
  225. package/src/main/lib/opsErrorLog.cjs +1 -1
  226. package/src/main/lib/opsOwnership.cjs +27 -19
  227. package/src/main/lib/prdAuthoringSeed.cjs +36 -0
  228. package/src/main/lib/prdCreate.cjs +56 -1
  229. package/src/main/lib/prdDisposition.cjs +199 -0
  230. package/src/main/lib/prdFrontmatter.cjs +8 -2
  231. package/src/main/lib/prdLocations.cjs +214 -45
  232. package/src/main/lib/procIdentity.cjs +126 -0
  233. package/src/main/lib/procName.cjs +98 -0
  234. package/src/main/lib/projectHomeAdminRoutes.cjs +56 -327
  235. package/src/main/lib/projectRootResolve.cjs +1 -1
  236. package/src/main/lib/queueHistory.cjs +27 -8
  237. package/src/main/lib/queueStore.cjs +63 -38
  238. package/src/main/lib/quietMachineLease.cjs +19 -2
  239. package/src/main/lib/reaperHelpers.cjs +181 -15
  240. package/src/main/lib/reservationExpiry.cjs +30 -0
  241. package/src/main/lib/runClaudeP.cjs +4 -2
  242. package/src/main/lib/runLogRetention.cjs +3 -2
  243. package/src/main/lib/scheduleJobSchema.cjs +10 -2
  244. package/src/main/lib/scheduleJobTransitions.cjs +60 -0
  245. package/src/main/lib/schedulerBatch.cjs +27 -2
  246. package/src/main/lib/schedulerConfig.cjs +24 -0
  247. package/src/main/lib/schedulerPaths.cjs +175 -0
  248. package/src/main/lib/schedulerRuntimeState.cjs +59 -0
  249. package/src/main/lib/sessionSlots.cjs +47 -9
  250. package/src/main/lib/smProcNames.cjs +51 -0
  251. package/src/main/lib/upgradeDrain.cjs +188 -0
  252. package/src/main/lib/usageCircuit.cjs +206 -0
  253. package/{scripts → src/main}/lib/watchdogHelpers.cjs +71 -38
  254. package/src/main/lib/withTimeout.cjs +33 -0
  255. package/src/main/mcpStatus.cjs +4 -2
  256. package/src/main/pluginInstall.cjs +6 -2
  257. package/src/main/projectPages.cjs +41 -145
  258. package/src/main/pty.cjs +8 -1
  259. package/src/main/queueOps.cjs +10 -10
  260. package/src/main/runVerify.cjs +69 -3
  261. package/src/main/scheduler/prdParser.cjs +13 -0
  262. package/src/main/scheduler.cjs +2841 -482
  263. package/src/main/seedAgentPersonas.cjs +1 -1
  264. package/src/main/seedDevPlugin.cjs +1 -1
  265. package/src/main/seedSchedulerMcp.cjs +7 -6
  266. package/src/main/seedStatus.cjs +1 -1
  267. package/src/main/supervisor.cjs +5 -3
  268. package/src/main/templates/PRD_AUTHORING.md +50 -0
  269. package/src/main/usage.cjs +140 -58
  270. package/src/preload/api.d.ts +110 -65
  271. package/src/preload/index.cjs +12 -4
  272. package/src/seed/agents/project-home-builder.md +31 -48
  273. package/dist/assets/AgentLibrary-DTFL7y8G.js +0 -3
  274. package/dist/assets/MarkdownEditor-DCIubYWf.js +0 -1
  275. package/dist/assets/Permissions-BiYZNGYW.js +0 -3
  276. package/dist/assets/Scheduler-DcLBiJBq.js +0 -14
  277. package/dist/assets/Settings-Cv-pRyms.js +0 -3
  278. package/dist/assets/settingsSchema-BJVciriw.js +0 -3
  279. package/screenshots/.gitkeep +0 -0
  280. package/screenshots/README-screenshots.md +0 -13
  281. package/scripts/project-pages-logic/dist/logic.cjs +0 -4709
  282. package/scripts/render-project-pages/dist/renderer.cjs +0 -18900
  283. package/scripts/render-project-pages.cjs +0 -70
  284. package/scripts/validate-project-pages-summary.cjs +0 -62
  285. package/src/main/lib/projectPageSummarySchema.cjs +0 -181
  286. package/src/main/teams.cjs +0 -95
  287. package/src/main/templates/project-pages-catalog.json +0 -741
  288. package/src/main/templates/project-pages-default-home.html +0 -123
  289. package/src/main/templates/project-pages-pipeline.md +0 -417
  290. package/src/seed/prompts/code-review/ac-coverage-check.md +0 -8
  291. package/src/seed/prompts/code-review/correctness-only.md +0 -8
  292. package/src/seed/prompts/code-review/full-spectrum-high.md +0 -8
  293. package/src/seed/prompts/code-review/hallucination-check.md +0 -8
  294. package/src/seed/prompts/code-review/public-api-compat.md +0 -8
  295. package/src/seed/prompts/code-review/readability-naming.md +0 -8
  296. package/src/seed/prompts/debugging/bug-as-failing-test.md +0 -8
  297. package/src/seed/prompts/debugging/git-bisect-regression.md +0 -8
  298. package/src/seed/prompts/debugging/instrument-intermittent-bug.md +0 -8
  299. package/src/seed/prompts/debugging/localize-pipeline-failure.md +0 -8
  300. package/src/seed/prompts/debugging/reproduce-then-diagnose.md +0 -8
  301. package/src/seed/prompts/documentation/adr-from-change.md +0 -8
  302. package/src/seed/prompts/documentation/module-readme.md +0 -8
  303. package/src/seed/prompts/documentation/onboarding-plan.md +0 -8
  304. package/src/seed/prompts/documentation/refresh-claude-md.md +0 -8
  305. package/src/seed/prompts/documentation/tsdoc-public-exports.md +0 -8
  306. package/src/seed/prompts/git-pr/conventional-commit.md +0 -8
  307. package/src/seed/prompts/git-pr/draft-pr-title-body.md +0 -8
  308. package/src/seed/prompts/git-pr/pre-commit-safety-sweep.md +0 -8
  309. package/src/seed/prompts/git-pr/release-notes-block.md +0 -8
  310. package/src/seed/prompts/git-pr/split-large-pr.md +0 -8
  311. package/src/seed/prompts/performance/bundle-startup-audit.md +0 -8
  312. package/src/seed/prompts/performance/complexity-audit.md +0 -8
  313. package/src/seed/prompts/performance/cpu-profile-hot-path.md +0 -8
  314. package/src/seed/prompts/performance/db-query-plan-review.md +0 -8
  315. package/src/seed/prompts/performance/memory-leak-hunt.md +0 -8
  316. package/src/seed/prompts/qa/api-contract-tests.md +0 -8
  317. package/src/seed/prompts/qa/e2e-critical-path.md +0 -8
  318. package/src/seed/prompts/qa/failing-test-for-bug.md +0 -8
  319. package/src/seed/prompts/qa/find-missing-test-coverage.md +0 -8
  320. package/src/seed/prompts/qa/stabilize-flaky-test.md +0 -8
  321. package/src/seed/prompts/qa/tdd-red-first.md +0 -8
  322. package/src/seed/prompts/qa/visual-regression-review.md +0 -8
  323. package/src/seed/prompts/qa/wcag-axe-scan.md +0 -8
  324. package/src/seed/prompts/refactoring/dead-code-sweep.md +0 -8
  325. package/src/seed/prompts/refactoring/extract-duplicated-pattern.md +0 -8
  326. package/src/seed/prompts/refactoring/modernize-legacy-file.md +0 -8
  327. package/src/seed/prompts/refactoring/reduce-cyclomatic-complexity.md +0 -8
  328. package/src/seed/prompts/refactoring/tighten-module-boundaries.md +0 -8
  329. package/src/seed/prompts/security/authz-audit.md +0 -8
  330. package/src/seed/prompts/security/crypto-correctness.md +0 -8
  331. package/src/seed/prompts/security/cwe-top-25-hunt.md +0 -8
  332. package/src/seed/prompts/security/dependency-audit.md +0 -8
  333. package/src/seed/prompts/security/ipc-boundary-hardening.md +0 -8
  334. package/src/seed/prompts/security/owasp-top-10-staged-diff.md +0 -8
  335. package/src/seed/prompts/security/secret-credential-scan.md +0 -8
@@ -0,0 +1,39 @@
1
+ /**
2
+ * health-build-freshness.test.cjs — evaluateBuildFreshness separates
3
+ * restart-needed (installed differs from running) from publish-needed
4
+ * (repo HEAD differs from installed); both reported, never critical.
5
+ *
6
+ * Run: timeout 120 npx vitest run src/main/__tests__/health-build-freshness.test.cjs
7
+ */
8
+ 'use strict';
9
+
10
+ import { test, expect } from 'vitest';
11
+ const { evaluateBuildFreshness } = require('../health.cjs');
12
+
13
+ test('all shas equal → nothing needed', () => {
14
+ const r = evaluateBuildFreshness({ running: 'abc1234', installedOnDisk: 'abc1234', repoHead: 'abc1234' });
15
+ expect(r).toMatchObject({ ok: true, restartNeeded: false, publishNeeded: false });
16
+ });
17
+
18
+ test('installed differs from running → restart needed only', () => {
19
+ const r = evaluateBuildFreshness({ running: 'aaa1111', installedOnDisk: 'bbb2222', repoHead: 'bbb2222' });
20
+ expect(r).toMatchObject({ ok: true, restartNeeded: true, publishNeeded: false });
21
+ });
22
+
23
+ test('repo HEAD differs from installed → publish needed only', () => {
24
+ const r = evaluateBuildFreshness({ running: 'bbb2222', installedOnDisk: 'bbb2222', repoHead: 'ccc3333' });
25
+ expect(r).toMatchObject({ ok: true, restartNeeded: false, publishNeeded: true });
26
+ });
27
+
28
+ test('both differ → both reported, still ok (non-critical)', () => {
29
+ const r = evaluateBuildFreshness({ running: 'aaa1111', installedOnDisk: 'bbb2222', repoHead: 'ccc3333' });
30
+ expect(r).toMatchObject({ ok: true, restartNeeded: true, publishNeeded: true });
31
+ expect(r.note).toMatch(/restart needed/);
32
+ expect(r.note).toMatch(/publish needed/);
33
+ });
34
+
35
+ test('unknown inputs never claim a difference; short/long sha prefixes match', () => {
36
+ expect(evaluateBuildFreshness({})).toMatchObject({ restartNeeded: false, publishNeeded: false });
37
+ expect(evaluateBuildFreshness({ running: null, installedOnDisk: 'abc1234', repoHead: 'abc1234567890' }))
38
+ .toMatchObject({ restartNeeded: false, publishNeeded: false });
39
+ });
@@ -84,9 +84,23 @@ test('failing check with no fix omits the "(fix: ...)" suffix', () => {
84
84
  });
85
85
 
86
86
  test('end-to-end: check() reports delegation_chain for this repo, expected to pass here', async () => {
87
- const status = await check();
87
+ // Probes THIS machine's real user-scope config (~/.claude.json MCP registration, guard
88
+ // shims), so it reads the real home — not the per-run sandbox HOME. Read-only: scheduler
89
+ // state stays redirected to the sandbox (SM_SCHEDULER_HOME re-pinned after the HOME swap).
90
+ const sandboxHome = process.env.HOME;
91
+ const sandboxSchedulerHome = process.env.SM_SCHEDULER_HOME;
92
+ process.env.HOME = require('node:os').userInfo().homedir;
93
+ if (sandboxSchedulerHome !== undefined) process.env.SM_SCHEDULER_HOME = sandboxSchedulerHome;
94
+ process.env.SM_ALLOW_LIVE_ROOT_READS = '1';
95
+ let status;
96
+ try {
97
+ status = await check();
98
+ } finally {
99
+ delete process.env.SM_ALLOW_LIVE_ROOT_READS;
100
+ process.env.HOME = sandboxHome;
101
+ }
88
102
  expect(status.components.delegation_chain).toBeDefined();
89
- expect(status.components.delegation_chain.checks).toHaveLength(8);
103
+ expect(status.components.delegation_chain.checks).toHaveLength(9);
90
104
  const ids = status.components.delegation_chain.checks.map((c) => c.id);
91
105
  expect(ids).toEqual([
92
106
  'scheduler-mcp',
@@ -97,6 +111,7 @@ test('end-to-end: check() reports delegation_chain for this repo, expected to pa
97
111
  'prd-write-guard',
98
112
  'destructive-git-guard',
99
113
  'inline-implementation-guard',
114
+ 'self-schedule-guard',
100
115
  ]);
101
116
  // This repo registers the scheduler MCP at user scope (~/.claude.json) and
102
117
  // wires guard-prd-writes in .claude/settings.json, and no longer carries a
@@ -2,7 +2,7 @@
2
2
  * health-queue-dispatch.test.cjs — `npm run health` must report non-GREEN
3
3
  * when there is at least one DISPATCHABLE pending job (reusing
4
4
  * classifyQueueStarvation's own 'starved' verdict, not merely "pending"),
5
- * nothing running, and no dispatch attempt in a long time — the exact shape
5
+ * nothing running, and no launch in a long time — the exact shape
6
6
  * of the 2026-09-11 incident (27 pending across two projects, 0 running, for
7
7
  * days, every other health surface reporting green). A dependsOn-blocked
8
8
  * queue must NOT trip this the same way: ticking can't fix a blocked chain,
@@ -18,7 +18,7 @@
18
18
  'use strict';
19
19
 
20
20
  import { test, expect } from 'vitest';
21
- const { evaluateQueueDispatchHealth, DISPATCH_STALL_THRESHOLD_MS } = require('../health.cjs');
21
+ const { evaluateQueueDispatchHealth, DISPATCH_STALL_THRESHOLD_MS, DISPATCH_WARN_THRESHOLD_MS } = require('../health.cjs');
22
22
 
23
23
  const CWD = '/home/bilko/Projects/starry-night-ships';
24
24
  const NOW = Date.parse('2026-09-11T12:00:00.000Z');
@@ -28,7 +28,7 @@ test('a dispatchable pending job, 0 running, stale past the threshold — non-GR
28
28
  const state = {
29
29
  jobs: [{ slug: 'a', cwd: CWD, status: 'pending', dependsOn: [] }],
30
30
  paused: null,
31
- lastDispatchAttemptAt: idleFor(DISPATCH_STALL_THRESHOLD_MS + 60_000),
31
+ lastRunAt: idleFor(DISPATCH_STALL_THRESHOLD_MS + 60_000),
32
32
  };
33
33
  const result = evaluateQueueDispatchHealth(state, 0, NOW);
34
34
  expect(result.ok).toBe(false);
@@ -44,7 +44,7 @@ test('every pending row behind a blocked dependsOn chain stays GREEN, reported a
44
44
  { slug: 'blocked-child', cwd: CWD, status: 'pending', dependsOn: ['dead'] },
45
45
  ],
46
46
  paused: null,
47
- lastDispatchAttemptAt: idleFor(DISPATCH_STALL_THRESHOLD_MS + 60_000),
47
+ lastRunAt: idleFor(DISPATCH_STALL_THRESHOLD_MS + 60_000),
48
48
  };
49
49
  const result = evaluateQueueDispatchHealth(state, 0, NOW);
50
50
  expect(result.ok).toBe(true);
@@ -57,7 +57,7 @@ test('healthy queue (recent dispatch attempt) is GREEN with no verdict', () => {
57
57
  const state = {
58
58
  jobs: [{ slug: 'a', cwd: CWD, status: 'pending', dependsOn: [] }],
59
59
  paused: null,
60
- lastDispatchAttemptAt: idleFor(60_000),
60
+ lastRunAt: idleFor(60_000),
61
61
  };
62
62
  const result = evaluateQueueDispatchHealth(state, 0, NOW);
63
63
  expect(result).toEqual({ ok: true });
@@ -67,7 +67,7 @@ test('nothing pending is GREEN regardless of how stale the dispatch timestamp is
67
67
  const state = {
68
68
  jobs: [{ slug: 'a', cwd: CWD, status: 'completed' }],
69
69
  paused: null,
70
- lastDispatchAttemptAt: idleFor(DISPATCH_STALL_THRESHOLD_MS + 60_000),
70
+ lastRunAt: idleFor(DISPATCH_STALL_THRESHOLD_MS + 60_000),
71
71
  };
72
72
  const result = evaluateQueueDispatchHealth(state, 0, NOW);
73
73
  expect(result).toEqual({ ok: true });
@@ -77,8 +77,59 @@ test('work already running is GREEN — the queue is flowing', () => {
77
77
  const state = {
78
78
  jobs: [{ slug: 'a', cwd: CWD, status: 'pending', dependsOn: [] }],
79
79
  paused: null,
80
- lastDispatchAttemptAt: idleFor(DISPATCH_STALL_THRESHOLD_MS + 60_000),
80
+ lastRunAt: idleFor(DISPATCH_STALL_THRESHOLD_MS + 60_000),
81
81
  };
82
82
  const result = evaluateQueueDispatchHealth(state, 1, NOW);
83
83
  expect(result).toEqual({ ok: true });
84
84
  });
85
+
86
+ test('the two tiers are exported: 15-minute warn, 2-hour cold report', () => {
87
+ expect(DISPATCH_WARN_THRESHOLD_MS).toBe(15 * 60_000);
88
+ expect(DISPATCH_STALL_THRESHOLD_MS).toBe(2 * 60 * 60_000);
89
+ });
90
+
91
+ test('starved between the warn and cold thresholds — ok:true but flagged warn', () => {
92
+ const state = {
93
+ jobs: [{ slug: 'a', cwd: CWD, status: 'pending', dependsOn: [] }],
94
+ paused: null,
95
+ lastRunAt: idleFor(DISPATCH_WARN_THRESHOLD_MS + 60_000),
96
+ };
97
+ const result = evaluateQueueDispatchHealth(state, 0, NOW);
98
+ expect(result.ok).toBe(true);
99
+ expect(result.warn).toBe(true);
100
+ expect(result.starved).toBeUndefined();
101
+ expect(result.dispatchable).toBe(1);
102
+ });
103
+
104
+ test('below the warn threshold is plain GREEN', () => {
105
+ const state = {
106
+ jobs: [{ slug: 'a', cwd: CWD, status: 'pending', dependsOn: [] }],
107
+ paused: null,
108
+ lastRunAt: idleFor(DISPATCH_WARN_THRESHOLD_MS - 60_000),
109
+ };
110
+ expect(evaluateQueueDispatchHealth(state, 0, NOW)).toEqual({ ok: true });
111
+ });
112
+
113
+ test('ticks that launch nothing (fresh lastDispatchAttemptAt, stale lastRunAt) still go non-GREEN', () => {
114
+ const state = {
115
+ jobs: [{ slug: 'a', cwd: CWD, status: 'pending', dependsOn: [] }],
116
+ paused: null,
117
+ lastDispatchAttemptAt: idleFor(5_000),
118
+ lastRunAt: idleFor(DISPATCH_STALL_THRESHOLD_MS + 60_000),
119
+ };
120
+ const result = evaluateQueueDispatchHealth(state, 0, NOW);
121
+ expect(result.ok).toBe(false);
122
+ expect(result.starved).toBe(true);
123
+ });
124
+
125
+ test('rows held by an open launch breaker read blocked, not starved', () => {
126
+ const state = {
127
+ jobs: [{ slug: 'a', cwd: CWD, status: 'pending', dependsOn: [] }],
128
+ paused: null,
129
+ lastRunAt: idleFor(DISPATCH_STALL_THRESHOLD_MS + 60_000),
130
+ launchBlocks: { default: { kind: 'auth', at: NOW - 1000 } },
131
+ };
132
+ const result = evaluateQueueDispatchHealth(state, 0, NOW);
133
+ expect(result.ok).toBe(true);
134
+ expect(result.blocked).toBe(true);
135
+ });
@@ -6,12 +6,12 @@
6
6
  * since they're pure/fs-isolated — no need to mock os.homedir() through the
7
7
  * full check() to cover the liveness logic.
8
8
  *
9
- * Run: timeout 120 node --test src/main/__tests__/health-tick-liveness.test.cjs
9
+ * Run: timeout 300 npx vitest run src/main/__tests__/health-tick-liveness.test.cjs
10
10
  */
11
11
 
12
12
  'use strict';
13
13
 
14
- const { test } = require('node:test');
14
+ import { test } from 'vitest';
15
15
  const assert = require('node:assert/strict');
16
16
  const fs = require('node:fs');
17
17
  const os = require('node:os');
@@ -72,6 +72,7 @@ test('no false positive: running is already at concurrencyCap', () => {
72
72
  { slug: '544-stuck-job', status: 'pending' },
73
73
  ],
74
74
  });
75
+ q.slotCap = 3; // machine-wide sessionSlots pool is the only cap now
75
76
  const result = evaluateTickLiveness(q, null, NOW);
76
77
  assert.strictEqual(result.stalled, false);
77
78
  assert.strictEqual(result.reason, 'at-capacity');
@@ -105,9 +106,16 @@ test('degrades honestly: heartbeat missing/stale means utilization cannot be rul
105
106
  assert.strictEqual(result.reason, 'cannot-verify-utilization');
106
107
  });
107
108
 
108
- test('does not caveat when firePolicy is not when-available (utilization is irrelevant, so no heartbeat needed)', () => {
109
+ test('manual firePolicy: pending work with free capacity is configured behaviour, not a stall', () => {
109
110
  const q = baseQueue({ config: { ...baseQueue().config, firePolicy: 'manual' } });
110
111
  const result = evaluateTickLiveness(q, null, NOW);
112
+ assert.strictEqual(result.stalled, false);
113
+ assert.strictEqual(result.reason, 'manual-fire-policy');
114
+ });
115
+
116
+ test('does not caveat when firePolicy is neither when-available nor manual (no heartbeat needed)', () => {
117
+ const q = baseQueue({ config: { ...baseQueue().config, firePolicy: 'always' } });
118
+ const result = evaluateTickLiveness(q, null, NOW);
111
119
  assert.strictEqual(result.stalled, true);
112
120
  });
113
121
 
@@ -1,12 +1,15 @@
1
1
  /**
2
- * health-usage-poller.test.cjs — `npm run health` must report non-GREEN when
3
- * the usage/rate-limit poller's (scheduler.cjs pollLoop) consecutiveFailures
4
- * streak, persisted to ~/.claude/session-manager/scheduler-state.json,
5
- * reaches FAILURE_STREAK_WARN_THRESHOLD — the SAME count that gates the
6
- * one-time opsErrorLog WARN (rateLimitPollerStreak.test.cjs shouldWarnFailureStreak
7
- * fires at `>= threshold`), so the log line and the health-GREEN flip happen
8
- * on the exact same failure, not one apart. Exercises evaluateUsagePollerHealth()
9
- * and loadUsagePollerState() directly since both are pure/file-scoped, matching
2
+ * health-usage-poller.test.cjs — `npm run health` must report GREEN / YELLOW
3
+ * / RED for the usage/rate-limit poller (scheduler.cjs pollLoop), driven by
4
+ * the shared usageCircuit's OWN open/closed state and how long it's been
5
+ * open (usageCircuitState/usageCircuitOpenedAt, persisted to
6
+ * ~/.claude/session-manager/scheduler-state.json by persistSchedulerState —
7
+ * this is a separate `npm run health` process, so it reads the PERSISTED
8
+ * circuit fields rather than holding the live in-memory breaker) — not the
9
+ * old binary consecutiveFailures<5 check, which read flatly non-GREEN
10
+ * forever after the fifth failure with no way to distinguish "just tripped"
11
+ * from "down for hours." Exercises evaluateUsagePollerHealth() and
12
+ * loadUsagePollerState() directly since both are pure/file-scoped, matching
10
13
  * every other evaluate* helper's test pattern in this file — deliberately NOT
11
14
  * driving the full (slow, machine-state-coupled) check() for this.
12
15
  *
@@ -20,44 +23,88 @@ import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
20
23
  import { tmpdir } from 'node:os';
21
24
  import { join } from 'node:path';
22
25
  const { evaluateUsagePollerHealth, loadUsagePollerState } = require('../health.cjs');
23
- const { FAILURE_STREAK_WARN_THRESHOLD } = require('../scheduler.cjs');
26
+ const { FAILURE_STREAK_ESCALATION_MS } = require('../scheduler.cjs');
24
27
 
25
28
  test('missing state (fresh install / poller never ran) is ok, not applicable', () => {
26
29
  expect(evaluateUsagePollerHealth(null)).toEqual({ ok: true, applicable: false });
27
30
  expect(evaluateUsagePollerHealth(undefined)).toEqual({ ok: true, applicable: false });
28
31
  });
29
32
 
30
- test('consecutiveFailures below the threshold is GREEN', () => {
31
- const result = evaluateUsagePollerHealth({ consecutiveFailures: FAILURE_STREAK_WARN_THRESHOLD - 1, backoffMs: 240_000, lastPollAt: Date.now() });
33
+ test('circuit closed is GREEN, regardless of a nonzero consecutiveFailures count', () => {
34
+ const result = evaluateUsagePollerHealth({ usageCircuitState: 'closed', consecutiveFailures: 2, backoffMs: 60_000, lastPollAt: Date.now() });
32
35
  expect(result.ok).toBe(true);
33
36
  expect(result.applicable).toBe(true);
37
+ expect(result.color).toBe('GREEN');
34
38
  expect(result.message).toBeUndefined();
35
39
  });
36
40
 
37
- test('consecutiveFailures AT the threshold is already non-GREEN — same count the WARN fires at', () => {
38
- // Must match shouldWarnFailureStreak's `>= threshold` exactly (scheduler.cjs)
39
- // so the opsErrorLog WARN and this health flip happen on the same failure.
40
- const result = evaluateUsagePollerHealth({ consecutiveFailures: FAILURE_STREAK_WARN_THRESHOLD, backoffMs: 480_000, lastPollAt: Date.now() });
41
- expect(result.ok).toBe(false);
42
- expect(result.message).toMatch(new RegExp(`${FAILURE_STREAK_WARN_THRESHOLD} consecutive failures`));
41
+ test('no usageCircuitState field at all (pre-upgrade sidecar) defaults to closed -> GREEN', () => {
42
+ const result = evaluateUsagePollerHealth({ consecutiveFailures: 0, backoffMs: null, lastPollAt: Date.now() });
43
+ expect(result.ok).toBe(true);
44
+ expect(result.color).toBe('GREEN');
43
45
  });
44
46
 
45
- test('consecutiveFailures well past the threshold is non-GREEN with a diagnostic message', () => {
46
- const result = evaluateUsagePollerHealth({ consecutiveFailures: 57, backoffMs: 480_000, lastPollAt: Date.now() });
47
+ test('circuit open for under FAILURE_STREAK_ESCALATION_MS (30 min) is YELLOW, not GREEN — but still ok:true, not yet critical', () => {
48
+ const now = Date.now();
49
+ const result = evaluateUsagePollerHealth({
50
+ usageCircuitState: 'open',
51
+ usageCircuitOpenedAt: now - 5 * 60_000, // 5 minutes ago
52
+ consecutiveFailures: 6,
53
+ backoffMs: 480_000,
54
+ lastPollAt: now,
55
+ });
56
+ // ok:true is deliberate — the circuit's own open threshold (3 consecutive
57
+ // failures) is LOWER than the old 5-failure WARN, so an ok:false YELLOW
58
+ // would silently tighten the overall `npm run health` rollup's failure bar
59
+ // from 5 down to 3. Graceful degradation must not itself read as critical.
60
+ expect(result.ok).toBe(true);
61
+ expect(result.color).toBe('YELLOW');
62
+ expect(result.message).toMatch(/YELLOW/);
63
+ });
64
+
65
+ test('circuit open for AT LEAST FAILURE_STREAK_ESCALATION_MS (30 min) is RED — ok:false, this is the critical state', () => {
66
+ const now = Date.now();
67
+ const result = evaluateUsagePollerHealth({
68
+ usageCircuitState: 'open',
69
+ usageCircuitOpenedAt: now - FAILURE_STREAK_ESCALATION_MS,
70
+ consecutiveFailures: 57,
71
+ backoffMs: 480_000,
72
+ lastPollAt: now,
73
+ });
47
74
  expect(result.ok).toBe(false);
75
+ expect(result.color).toBe('RED');
48
76
  expect(result.consecutiveFailures).toBe(57);
49
77
  expect(result.backoffMs).toBe(480_000);
50
- expect(result.message).toMatch(/57 consecutive failures/);
78
+ expect(result.message).toMatch(/RED/);
79
+ });
80
+
81
+ test('half_open (mid-probe) is treated the same as open — not GREEN just because a probe is in flight', () => {
82
+ const now = Date.now();
83
+ const result = evaluateUsagePollerHealth({
84
+ usageCircuitState: 'half_open',
85
+ usageCircuitOpenedAt: now - 60_000,
86
+ consecutiveFailures: 5,
87
+ lastPollAt: now,
88
+ });
89
+ expect(result.ok).toBe(true); // still YELLOW-shaped (under the red threshold), so still ok:true
90
+ expect(result.color).toBe('YELLOW');
51
91
  });
52
92
 
53
- test('respects a custom threshold argument', () => {
54
- expect(evaluateUsagePollerHealth({ consecutiveFailures: 3 }, 3).ok).toBe(false);
55
- expect(evaluateUsagePollerHealth({ consecutiveFailures: 2 }, 3).ok).toBe(true);
93
+ test('respects a custom redThresholdMs argument', () => {
94
+ const now = Date.now();
95
+ const openedAt = now - 10_000;
96
+ const red = evaluateUsagePollerHealth({ usageCircuitState: 'open', usageCircuitOpenedAt: openedAt, consecutiveFailures: 5 }, 5_000);
97
+ expect(red.color).toBe('RED');
98
+ expect(red.ok).toBe(false);
99
+ const yellow = evaluateUsagePollerHealth({ usageCircuitState: 'open', usageCircuitOpenedAt: openedAt, consecutiveFailures: 5 }, 60_000);
100
+ expect(yellow.color).toBe('YELLOW');
101
+ expect(yellow.ok).toBe(true);
56
102
  });
57
103
 
58
104
  test('tolerates a malformed/partial state object without throwing', () => {
59
105
  const result = evaluateUsagePollerHealth({});
60
106
  expect(result.ok).toBe(true);
107
+ expect(result.color).toBe('GREEN');
61
108
  expect(result.consecutiveFailures).toBe(0);
62
109
  });
63
110
 
@@ -135,7 +135,7 @@ test('appendRollupDays: compacts (dedupes) once file exceeds threshold', async (
135
135
  { date: '2026-07-01', projectDir: 'p', modelId: 'sonnet', inputTokens: 2 },
136
136
  ]);
137
137
  await historyRollup.compact();
138
- const raw = fs.readFileSync(historyRollup.ROLLUP_PATH, 'utf8');
138
+ const raw = fs.readFileSync(historyRollup.historyRollupPath(), 'utf8');
139
139
  const nonEmptyLines = raw.split('\n').filter((l) => l.trim());
140
140
  expect(nonEmptyLines.length).toBe(1);
141
141
  expect(JSON.parse(nonEmptyLines[0]).inputTokens).toBe(2);
@@ -305,7 +305,7 @@ test('refreshIntradayToday: upserts a provisional (non-finalized) line for today
305
305
  // duplicates — today's line for the same key once the file is compacted.
306
306
  await historyAggregator.refreshIntradayToday();
307
307
  await historyRollup.compact();
308
- const raw = fs.readFileSync(historyRollup.ROLLUP_PATH, 'utf8');
308
+ const raw = fs.readFileSync(historyRollup.historyRollupPath(), 'utf8');
309
309
  const matchingLines = raw.split('\n').filter((l) => {
310
310
  if (!l.trim()) return false;
311
311
  const obj = JSON.parse(l);
@@ -14,10 +14,10 @@
14
14
  * differences between log sources don't break the match.
15
15
  *
16
16
  * Does NOT spawn claude -p. Run:
17
- * timeout 120 node --test src/main/__tests__/kg-augment.test.cjs
17
+ * timeout 300 npx vitest run src/main/__tests__/kg-augment.test.cjs
18
18
  */
19
19
 
20
- const { test, describe, before, after } = require('node:test');
20
+ import { test, describe, beforeAll as before, afterAll as after } from 'vitest';
21
21
  const assert = require('node:assert/strict');
22
22
  const fs = require('node:fs');
23
23
  const fsp = require('node:fs/promises');
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * mcpStatus.test.cjs — unit tests for the `claude mcp list` output parser.
3
3
  *
4
- * Run: timeout 120 node --test src/main/__tests__/mcpStatus.test.cjs
4
+ * Run: timeout 300 npx vitest run src/main/__tests__/mcpStatus.test.cjs
5
5
  */
6
6
 
7
7
  'use strict';
8
8
 
9
- const { test } = require('node:test');
9
+ import { test } from 'vitest';
10
10
  const assert = require('node:assert/strict');
11
11
  const { parseMcpList } = require('../mcpStatus.cjs');
12
12
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const test = require('node:test');
3
+ import { test } from 'vitest';
4
4
  const assert = require('node:assert/strict');
5
5
  const { parseClusters } = require('../memoryAggregate.cjs');
6
6
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const test = require('node:test');
3
+ import { test } from 'vitest';
4
4
  const assert = require('node:assert/strict');
5
5
  const { scoreMemories } = require('../lib/memoryStale.cjs');
6
6
 
@@ -0,0 +1,162 @@
1
+ /**
2
+ * needsReviewLedger.test.cjs — unit tests for src/main/lib/needsReviewLedger.cjs's
3
+ * pure ladder-rung classification, line builders, and rollup reduction.
4
+ *
5
+ * Run: timeout 120 npx vitest run src/main/__tests__/needsReviewLedger.test.cjs
6
+ */
7
+ 'use strict';
8
+
9
+ import { test, expect } from 'vitest';
10
+ const {
11
+ LADDER_RUNGS,
12
+ classifyLadderRung,
13
+ buildNeedsReviewEntryLine,
14
+ buildNeedsReviewResolutionLine,
15
+ reduceNeedsReviewLedger,
16
+ } = require('../lib/needsReviewLedger.cjs');
17
+
18
+ // ---------- classifyLadderRung ----------
19
+
20
+ test('classifyLadderRung maps every known automated source to its named rung', () => {
21
+ expect(classifyLadderRung({ source: 'scheduler:mechanicalRecovery' })).toBe('mechanical-recovery');
22
+ expect(classifyLadderRung({ source: 'spawnJob:dispatch', reason: 'dispatched for resume-recovery' })).toBe('resume-recovery');
23
+ expect(classifyLadderRung({ source: 'spawnInvestigation:start' })).toBe('auto-fix');
24
+ expect(classifyLadderRung({ source: 'spawnJob:auto-promote' })).toBe('auto-fix');
25
+ expect(classifyLadderRung({ source: 'reverifyNeedsReview:auto-promote' })).toBe('auto-fix');
26
+ expect(classifyLadderRung({ source: 'reverifyNeedsReview:heal' })).toBe('reverify');
27
+ expect(classifyLadderRung({ source: 'needsReviewAutoResolve' })).toBe('reverify');
28
+ });
29
+
30
+ test('classifyLadderRung: ordinary spawnJob:dispatch (no resume-recovery reason) is NOT resume-recovery', () => {
31
+ // 'spawnJob:dispatch' is shared with the everyday pending->running dispatch
32
+ // — only the resume-recovery REASON text distinguishes the two.
33
+ expect(classifyLadderRung({ source: 'spawnJob:dispatch', reason: 'dispatched for execution' })).toBe('manual-reset');
34
+ });
35
+
36
+ test('classifyLadderRung: unrecognized/explicit-reset sources default to manual-reset', () => {
37
+ expect(classifyLadderRung({ source: 'ipc:schedule:reset-job' })).toBe('manual-reset');
38
+ expect(classifyLadderRung({ source: 'remote:resetJob' })).toBe('manual-reset');
39
+ expect(classifyLadderRung({ source: 'clear-queue' })).toBe('manual-reset');
40
+ expect(classifyLadderRung({})).toBe('manual-reset');
41
+ });
42
+
43
+ test('LADDER_RUNGS names exactly the 6 documented rungs', () => {
44
+ expect(LADDER_RUNGS).toEqual(['mechanical-recovery', 'resume-recovery', 'quarantine', 'auto-fix', 'reverify', 'manual-reset']);
45
+ });
46
+
47
+ // ---------- buildNeedsReviewEntryLine / buildNeedsReviewResolutionLine ----------
48
+
49
+ test('buildNeedsReviewEntryLine: park reason prefers verifierVerdict, then heldReason, then error', () => {
50
+ const entry = { to: 'needs_review', source: 'spawnJob:finalize', at: '2026-09-13T00:00:00.000Z' };
51
+ expect(buildNeedsReviewEntryLine({ slug: 's1', verifierVerdict: 'uncommitted_changes', heldReason: 'held', error: 'err' }, entry).reason).toBe('uncommitted_changes');
52
+ expect(buildNeedsReviewEntryLine({ slug: 's1', heldReason: 'held', error: 'err' }, entry).reason).toBe('held');
53
+ expect(buildNeedsReviewEntryLine({ slug: 's1', error: 'err' }, entry).reason).toBe('err');
54
+ expect(buildNeedsReviewEntryLine({ slug: 's1' }, entry).reason).toBeNull();
55
+ });
56
+
57
+ test('buildNeedsReviewEntryLine: carries slug/cwd/epicId/runId/at/source verbatim', () => {
58
+ const job = { slug: '42-x', cwd: '/proj', epicId: 'epic-1', runId: 'run-9' };
59
+ const entry = { to: 'needs_review', source: 'spawnJob:finalize', at: '2026-09-13T00:00:00.000Z' };
60
+ const line = buildNeedsReviewEntryLine(job, entry);
61
+ expect(line).toEqual({
62
+ kind: 'needs_review_entry',
63
+ slug: '42-x',
64
+ cwd: '/proj',
65
+ epicId: 'epic-1',
66
+ runId: 'run-9',
67
+ at: '2026-09-13T00:00:00.000Z',
68
+ reason: null,
69
+ source: 'spawnJob:finalize',
70
+ });
71
+ });
72
+
73
+ test('buildNeedsReviewResolutionLine: references the EPISODE runId, not job.runId, and computes dwellMs', () => {
74
+ const job = { slug: '42-x', cwd: '/proj', epicId: 'epic-1', runId: 'run-LATER' };
75
+ const entry = { to: 'completed', source: 'reverifyNeedsReview:heal', at: '2026-09-13T00:10:00.000Z' };
76
+ const episode = { runId: 'run-ORIGINAL', enteredAt: '2026-09-13T00:00:00.000Z' };
77
+ const line = buildNeedsReviewResolutionLine(job, entry, episode);
78
+ expect(line.runId).toBe('run-ORIGINAL');
79
+ expect(line.resolvedTo).toBe('completed');
80
+ expect(line.ladderRung).toBe('reverify');
81
+ expect(line.dwellMs).toBe(10 * 60_000);
82
+ });
83
+
84
+ test('buildNeedsReviewResolutionLine: dwellMs is null when the episode has no enteredAt (defensive — should not happen in practice)', () => {
85
+ const job = { slug: '42-x', runId: 'run-1' };
86
+ const entry = { to: 'failed', source: 'ipc:schedule:reset-job', at: '2026-09-13T00:00:00.000Z' };
87
+ const line = buildNeedsReviewResolutionLine(job, entry, { runId: 'run-1', enteredAt: null });
88
+ expect(line.dwellMs).toBeNull();
89
+ });
90
+
91
+ // ---------- reduceNeedsReviewLedger ----------
92
+
93
+ function entryLine({ slug, runId, at, reason = 'uncommitted_changes', source = 'spawnJob:finalize' }) {
94
+ return { kind: 'needs_review_entry', slug, cwd: null, epicId: null, runId, at, reason, source };
95
+ }
96
+ function resolutionLine({ slug, runId, at, resolvedTo = 'completed', ladderRung = 'reverify', source = 'reverifyNeedsReview:heal', dwellMs = 1000 }) {
97
+ return { kind: 'needs_review_resolution', slug, cwd: null, epicId: null, runId, at, resolvedTo, ladderRung, source, dwellMs };
98
+ }
99
+
100
+ test('reduceNeedsReviewLedger: entry-only (unresolved) episode counts toward byReason and unresolvedCount, not byLadderRung', () => {
101
+ const lines = [entryLine({ slug: 'a', runId: 'r1', at: '2026-09-13T00:00:00.000Z', reason: 'silent_no_op' })];
102
+ const rollup = reduceNeedsReviewLedger(lines);
103
+ expect(rollup.byReason).toEqual({ silent_no_op: 1 });
104
+ expect(rollup.byLadderRung).toEqual({});
105
+ expect(rollup.unresolvedCount).toBe(1);
106
+ expect(rollup.dwellMsP50).toBeNull();
107
+ expect(rollup.dwellMsP90).toBeNull();
108
+ });
109
+
110
+ test('reduceNeedsReviewLedger: entry+resolution pair resolves and contributes dwell', () => {
111
+ const lines = [
112
+ entryLine({ slug: 'a', runId: 'r1', at: '2026-09-13T00:00:00.000Z' }),
113
+ resolutionLine({ slug: 'a', runId: 'r1', at: '2026-09-13T00:00:05.000Z', dwellMs: 5000, ladderRung: 'mechanical-recovery' }),
114
+ ];
115
+ const rollup = reduceNeedsReviewLedger(lines);
116
+ expect(rollup.unresolvedCount).toBe(0);
117
+ expect(rollup.byLadderRung).toEqual({ 'mechanical-recovery': 1 });
118
+ expect(rollup.dwellMsP50).toBe(5000);
119
+ expect(rollup.dwellMsP90).toBe(5000);
120
+ });
121
+
122
+ test('reduceNeedsReviewLedger: repeated entries for the same slug (different runIds) count as independent episodes', () => {
123
+ const lines = [
124
+ entryLine({ slug: 'a', runId: 'r1', at: '2026-09-13T00:00:00.000Z', reason: 'uncommitted_changes' }),
125
+ resolutionLine({ slug: 'a', runId: 'r1', at: '2026-09-13T00:00:10.000Z', dwellMs: 10_000, ladderRung: 'manual-reset' }),
126
+ entryLine({ slug: 'a', runId: 'r2', at: '2026-09-13T01:00:00.000Z', reason: 'uncommitted_changes' }),
127
+ // r2 never resolves.
128
+ ];
129
+ const rollup = reduceNeedsReviewLedger(lines);
130
+ expect(rollup.byReason).toEqual({ uncommitted_changes: 2 });
131
+ expect(rollup.unresolvedCount).toBe(1);
132
+ expect(rollup.byLadderRung).toEqual({ 'manual-reset': 1 });
133
+ expect(rollup.dwellMsP50).toBe(10_000);
134
+ });
135
+
136
+ test('reduceNeedsReviewLedger: p50/p90 over several resolved episodes', () => {
137
+ const dwells = [1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10_000];
138
+ const lines = dwells.flatMap((d, i) => [
139
+ entryLine({ slug: `s${i}`, runId: `r${i}`, at: '2026-09-13T00:00:00.000Z' }),
140
+ resolutionLine({ slug: `s${i}`, runId: `r${i}`, at: '2026-09-13T00:00:00.000Z', dwellMs: d }),
141
+ ]);
142
+ const rollup = reduceNeedsReviewLedger(lines);
143
+ expect(rollup.dwellMsP50).toBe(5000);
144
+ expect(rollup.dwellMsP90).toBe(9000);
145
+ });
146
+
147
+ test('reduceNeedsReviewLedger: ignores unrelated (terminal) rows mixed into the same lines array', () => {
148
+ const lines = [
149
+ { kind: 'terminal', slug: 'a', status: 'completed', runId: 'r1' },
150
+ { slug: 'b', status: 'failed', runId: 'r2' }, // legacy row, no kind at all
151
+ entryLine({ slug: 'c', runId: 'r3', at: '2026-09-13T00:00:00.000Z' }),
152
+ ];
153
+ const rollup = reduceNeedsReviewLedger(lines);
154
+ expect(rollup.unresolvedCount).toBe(1);
155
+ expect(Object.keys(rollup.byReason)).toEqual(['uncommitted_changes']);
156
+ });
157
+
158
+ test('reduceNeedsReviewLedger: empty/non-array input returns a zeroed rollup, never throws', () => {
159
+ expect(reduceNeedsReviewLedger([])).toEqual({ byReason: {}, byLadderRung: {}, unresolvedCount: 0, dwellMsP50: null, dwellMsP90: null });
160
+ expect(reduceNeedsReviewLedger(null)).toEqual({ byReason: {}, byLadderRung: {}, unresolvedCount: 0, dwellMsP50: null, dwellMsP90: null });
161
+ expect(reduceNeedsReviewLedger(undefined)).toEqual({ byReason: {}, byLadderRung: {}, unresolvedCount: 0, dwellMsP50: null, dwellMsP90: null });
162
+ });