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,92 @@
1
+ // PRD pn-01: procName primitive. HOME is redirected to a temp dir for the whole file
2
+ // (procnamesRoot() resolves lazily from HOME) — never the real ~/.claude.
3
+ import { describe, it, expect, afterAll } from 'vitest';
4
+ import fs from 'node:fs';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+
8
+ const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'procname-'));
9
+ const realHome = process.env.HOME;
10
+ process.env.HOME = tmpHome; // stays redirected: procnamesRoot() resolves lazily
11
+ const { aliasBinFor, smArgv0, procnamesRoot, pruneStaleAliases } = require('../procName.cjs');
12
+
13
+ afterAll(() => {
14
+ process.env.HOME = realHome;
15
+ try { fs.chmodSync(procnamesRoot(), 0o700); } catch { /* absent */ }
16
+ fs.rmSync(tmpHome, { recursive: true, force: true });
17
+ });
18
+
19
+ function fakeBin(name) {
20
+ const p = path.join(tmpHome, name);
21
+ fs.writeFileSync(p, '#!/bin/sh\nexit 0\n', { mode: 0o755 });
22
+ return p;
23
+ }
24
+
25
+ describe('procName', () => {
26
+ it('procnamesRoot() lives under the temp HOME machine-state root', () => {
27
+ expect(procnamesRoot()).toBe(path.join(tmpHome, '.claude', 'session-manager', 'procnames'));
28
+ });
29
+
30
+ it('creates a symlink and returns the alias path', () => {
31
+ const bin = fakeBin('bin-a');
32
+ const out = aliasBinFor(bin, 'sm-claude-job');
33
+ expect(out).toBe(path.join(procnamesRoot(), 'sm-claude-job'));
34
+ expect(fs.readlinkSync(out)).toBe(bin);
35
+ });
36
+
37
+ it('self-repairs a drifted link', () => {
38
+ const a = fakeBin('bin-b1');
39
+ const b = fakeBin('bin-b2');
40
+ aliasBinFor(a, 'sm-claude-chat');
41
+ const out = aliasBinFor(b, 'sm-claude-chat');
42
+ expect(fs.readlinkSync(out)).toBe(b);
43
+ });
44
+
45
+ it('throws on an alias longer than 15 chars', () => {
46
+ expect(() => aliasBinFor(fakeBin('bin-c'), 'sm-claude-toolong-x')).toThrow(/TASK_COMM_LEN/);
47
+ expect(() => aliasBinFor(fakeBin('bin-c'), 'session-manager')).not.toThrow();
48
+ });
49
+
50
+ it('returns the original bin for an unresolvable bare name', () => {
51
+ expect(aliasBinFor('definitely-not-a-real-cmd-xyz', 'sm-claude-aux')).toBe('definitely-not-a-real-cmd-xyz');
52
+ });
53
+
54
+ it.skipIf(process.getuid && process.getuid() === 0)('fails open when procnamesRoot() is read-only', () => {
55
+ const bin = fakeBin('bin-d');
56
+ fs.mkdirSync(procnamesRoot(), { recursive: true });
57
+ fs.chmodSync(procnamesRoot(), 0o500);
58
+ try {
59
+ expect(aliasBinFor(bin, 'sm-shell')).toBe(bin);
60
+ } finally {
61
+ fs.chmodSync(procnamesRoot(), 0o700);
62
+ }
63
+ });
64
+
65
+ it('smArgv0 shape, sanitisation, and length bound', () => {
66
+ expect(smArgv0('job', 'prd 12/x!')).toBe('sm-claude-job:prd12x');
67
+ expect(smArgv0('job', 'a'.repeat(500)).length).toBeLessThanOrEqual(120);
68
+ });
69
+
70
+ it('smArgv0 always matches the live /\\bclaude\\b/ gates for every role', () => {
71
+ // Replicated verbatim from scheduler.cjs killOrphanClaudePid & reaperHelpers.cjs claudePidAlive.
72
+ const gate = /\bclaude\b/;
73
+ for (const role of ['job', 'chat', 'aux', 'helper', '', undefined, '!!!', 'x'.repeat(300)]) {
74
+ for (const detail of ['abc', '', undefined, null, '///', 'y'.repeat(300)]) {
75
+ const label = smArgv0(role, detail);
76
+ expect(gate.test(label)).toBe(true);
77
+ expect(label.length).toBeLessThanOrEqual(120);
78
+ }
79
+ }
80
+ });
81
+
82
+ it('pruneStaleAliases removes dead links and keeps live ones', () => {
83
+ const live = fakeBin('bin-live');
84
+ const dead = fakeBin('bin-dead');
85
+ const liveAlias = aliasBinFor(live, 'sm-live');
86
+ const deadAlias = aliasBinFor(dead, 'sm-dead');
87
+ fs.rmSync(dead);
88
+ expect(() => pruneStaleAliases()).not.toThrow();
89
+ expect(fs.existsSync(liveAlias)).toBe(true);
90
+ expect(fs.lstatSync(deadAlias, { throwIfNoEntry: false })).toBeUndefined();
91
+ });
92
+ });
@@ -121,3 +121,70 @@ test('readMerged (async) recovers the same real fixture', async () => {
121
121
  expect(state.machineStateRecoveryMode).toBe('prefix');
122
122
  expect(state.lastRunAt).toBe('2026-09-11T10:00:14.441Z');
123
123
  });
124
+
125
+ // ---------- per-cwd shard quarantine (never recover, never write) ----------
126
+
127
+ function makeProject(name, jobs) {
128
+ const cwd = path.join(tmpHome, 'Projects', name);
129
+ const stateDir = path.join(cwd, 'session-manager-operations', 'scheduler', 'state');
130
+ fs.mkdirSync(stateDir, { recursive: true });
131
+ const file = path.join(stateDir, 'queue.json');
132
+ if (jobs) fs.writeFileSync(file, JSON.stringify({ jobs }));
133
+ const slugDir = path.join(tmpHome, '.claude', 'projects', `slug-${name}`);
134
+ fs.mkdirSync(slugDir, { recursive: true });
135
+ fs.writeFileSync(path.join(slugDir, 't.jsonl'), JSON.stringify({ cwd }) + '\n');
136
+ return { cwd, file };
137
+ }
138
+
139
+ const TORN = '{"jobs":[{"slug":"a","status":"running"}]} {"jobs":[{"slug":"a","stat';
140
+ const goodJob = (slug, cwd) => ({
141
+ slug, status: 'pending', prdPath: `/x/${slug}.md`, cwd, scheduledFor: null, createdAt: new Date().toISOString(),
142
+ });
143
+
144
+ test('a torn shard is quarantined per cwd; the machine-level `unreadable` stays unset and the sibling shard still loads', () => {
145
+ const good = makeProject('good', [goodJob('g1', path.join(tmpHome, 'Projects', 'good'))]);
146
+ const torn = makeProject('torn');
147
+ fs.writeFileSync(torn.file, TORN);
148
+ const opts = { projectsDir: path.join(tmpHome, '.claude', 'projects') };
149
+
150
+ const state = queueStore.readMergedSync(opts);
151
+
152
+ expect(state.unreadable).toBeUndefined();
153
+ expect(state.unreadableCwds).toHaveLength(1);
154
+ expect(state.unreadableCwds[0]).toMatchObject({ cwd: torn.cwd, file: torn.file });
155
+ expect(typeof state.unreadableCwds[0].error).toBe('string');
156
+ expect(state.jobs.map((j) => j.slug)).toEqual(['g1']);
157
+ expect(state.sourceCwds).not.toContain(torn.cwd);
158
+ });
159
+
160
+ test('readMerged (async) quarantines the same way', async () => {
161
+ const torn = makeProject('torn2');
162
+ fs.writeFileSync(torn.file, TORN);
163
+ const state = await queueStore.readMerged({ projectsDir: path.join(tmpHome, '.claude', 'projects') });
164
+ expect(state.unreadable).toBeUndefined();
165
+ expect(state.unreadableCwds.map((u) => u.cwd)).toEqual([torn.cwd]);
166
+ });
167
+
168
+ test('writeSplit leaves a torn shard byte-identical even when merged rows carry its cwd, and still writes healthy shards', async () => {
169
+ const good = makeProject('good3', [goodJob('g1', path.join(tmpHome, 'Projects', 'good3'))]);
170
+ const torn = makeProject('torn3');
171
+ fs.writeFileSync(torn.file, TORN);
172
+ const before = fs.readFileSync(torn.file);
173
+ const opts = { projectsDir: path.join(tmpHome, '.claude', 'projects') };
174
+
175
+ const state = await queueStore.readMerged(opts);
176
+ state.jobs.push(goodJob('intruder', torn.cwd)); // a row that claims the torn cwd
177
+ state.jobs[0].status = 'running';
178
+ await queueStore.writeSplit(state, good.cwd);
179
+
180
+ expect(fs.readFileSync(torn.file).equals(before)).toBe(true);
181
+ const written = JSON.parse(fs.readFileSync(good.file, 'utf8'));
182
+ expect(written.jobs.map((j) => j.status)).toEqual(['running']);
183
+ });
184
+
185
+ test('writeSplit never re-creates a quarantined shard that has no file at all', async () => {
186
+ const ghost = makeProject('ghost');
187
+ const state = { config: {}, jobs: [goodJob('x', ghost.cwd)], sourceCwds: [], unreadableCwds: [{ cwd: ghost.cwd, file: ghost.file, error: 'x' }] };
188
+ await queueStore.writeSplit(state, ghost.cwd);
189
+ expect(fs.existsSync(ghost.file)).toBe(false);
190
+ });
@@ -23,7 +23,8 @@ const { spawn } = require('node:child_process');
23
23
  const {
24
24
  selectReapableJobs, mapOutcomeToGateOutcome, classifyRunOutcome,
25
25
  findLiveProcessForJob, logHasOutput, resolvePidlessGateOutcome,
26
- isAlreadySatisfiedOnMain,
26
+ isAlreadySatisfiedOnMain, resolvePidlessFailureOverride,
27
+ readSpawnedPidFromLog, isLogFresh,
27
28
  } = require('../reaperHelpers.cjs');
28
29
  const { detectRateLimitInLog } = require('../rateLimitDetect.cjs');
29
30
 
@@ -339,6 +340,28 @@ test('findLiveProcessForJob: no live process anywhere → null', () => {
339
340
  assert.strictEqual(pid, null);
340
341
  });
341
342
 
343
+ // findLiveProcessForJob: runCwd — the 2026-09-13 in-place/shared-tree fix.
344
+ // A run with no dedicated worktree (no worktreeDir) and no slug in argv
345
+ // (dispatch never puts it there) is invisible to both existing matches; the
346
+ // runCwd+cmdline-claude combo match exists for exactly this shape. `sleep`
347
+ // is used as the /proc-visible process here (not a real `claude` binary), so
348
+ // this exercises the cwd-plus-cmdline-substring code path directly rather
349
+ // than depending on a real claude binary being installed.
350
+ test('findLiveProcessForJob: runCwd match requires BOTH cwd AND a claude-looking cmdline — bare cwd match is never sufficient', async () => {
351
+ if (process.platform !== 'linux' || !fs.existsSync('/proc')) return;
352
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-reaper-runcwd-'));
353
+ // Runs in `dir` but its cmdline has no "claude" in it — must NOT match.
354
+ const child = spawn('sleep', ['5'], { cwd: dir, stdio: 'ignore' });
355
+ try {
356
+ await new Promise((resolve) => setTimeout(resolve, 200));
357
+ const pid = findLiveProcessForJob({ slug: 'unrelated-slug' }, { runCwd: dir });
358
+ assert.strictEqual(pid, null, 'a bare cwd match without a claude-looking cmdline must never match — shared-tree cross-match hazard');
359
+ } finally {
360
+ child.kill('SIGKILL');
361
+ fs.rmSync(dir, { recursive: true, force: true });
362
+ }
363
+ });
364
+
342
365
  // logHasOutput — the literal "did the run dir produce any log output" check
343
366
  // that gates whether a pidless reap may assert gateOutcome: 'never_ran'.
344
367
 
@@ -397,3 +420,158 @@ test('isAlreadySatisfiedOnMain: no satisfying commits → null — the gate must
397
420
  assert.strictEqual(isAlreadySatisfiedOnMain(null), null);
398
421
  assert.strictEqual(isAlreadySatisfiedOnMain(undefined), null);
399
422
  });
423
+
424
+ // resolvePidlessFailureOverride — PRD 1173. A pidless reap must never stamp
425
+ // 'failed' on a row that already carries proof (landedCommit) that its work
426
+ // shipped, purely because reproting bookkeeping (runtime.pid) was lost.
427
+
428
+ test('resolvePidlessFailureOverride: row with a landedCommit → override naming the recorded commit sha', () => {
429
+ const override = resolvePidlessFailureOverride({ slug: 'shipped-but-pidless', landedCommit: 'f3e35a7f9e49f13a9eb8c338b0d086160e32a87f' });
430
+ assert.ok(override, 'a row with landedCommit must produce a failure override');
431
+ assert.strictEqual(override.verdict, 'pidless_reap_with_landed_commit');
432
+ assert.strictEqual(override.landedCommit, 'f3e35a7f9e49f13a9eb8c338b0d086160e32a87f');
433
+ assert.match(override.reason, /f3e35a7f9e49f13a9eb8c338b0d086160e32a87f/, 'the reason string must name the recorded commit sha so a human can go straight to `git show <sha>`');
434
+ });
435
+
436
+ test('resolvePidlessFailureOverride: row with no landedCommit → null, existing failed transition untouched', () => {
437
+ assert.strictEqual(resolvePidlessFailureOverride({ slug: 'genuinely-never-ran' }), null);
438
+ assert.strictEqual(resolvePidlessFailureOverride({ slug: 'genuinely-never-ran', landedCommit: null }), null);
439
+ assert.strictEqual(resolvePidlessFailureOverride({ slug: 'genuinely-never-ran', landedCommit: '' }), null);
440
+ });
441
+
442
+ // readSpawnedPidFromLog — recovers the pid a pidless row's spawn actually
443
+ // produced from the run's own log (2026-09-13 incident: runId
444
+ // 2026-09-13T16-15-56-818Z spawned pid=3138458, logged it, but the
445
+ // runtime.pid persistence mutate failed silently and the pidless-grace
446
+ // reaper terminalized a live job it could have found here).
447
+
448
+ test('readSpawnedPidFromLog: parses the scheduler\'s own spawned-pid line', () => {
449
+ const p = writeTmpLog('some preamble\n[scheduler] spawned pid=3138458 sessionId=345ca5b1-c7a7-4167-9b55-f4905e3afe01 (process group)\n\nmore output\n');
450
+ assert.strictEqual(readSpawnedPidFromLog(p), 3138458);
451
+ });
452
+
453
+ test('readSpawnedPidFromLog: missing file → null', () => {
454
+ assert.strictEqual(readSpawnedPidFromLog('/nonexistent/path/does-not-exist.log'), null);
455
+ });
456
+
457
+ test('readSpawnedPidFromLog: empty file → null', () => {
458
+ const p = writeTmpLog('');
459
+ assert.strictEqual(readSpawnedPidFromLog(p), null);
460
+ });
461
+
462
+ test('readSpawnedPidFromLog: log with no spawned-pid line → null', () => {
463
+ const p = writeTmpLog('{"type":"result","subtype":"success","is_error":false,"result":"done"}\n');
464
+ assert.strictEqual(readSpawnedPidFromLog(p), null);
465
+ });
466
+
467
+ test('readSpawnedPidFromLog: null path → null', () => {
468
+ assert.strictEqual(readSpawnedPidFromLog(null), null);
469
+ });
470
+
471
+ // isLogFresh — pure predicate: a log modified within the window is positive
472
+ // proof of life, independent of runtime.pid bookkeeping.
473
+
474
+ test('isLogFresh: mtime within window → true', () => {
475
+ assert.strictEqual(isLogFresh(NOW - 60_000, NOW, 20 * 60_000), true);
476
+ });
477
+
478
+ test('isLogFresh: mtime outside window → false', () => {
479
+ assert.strictEqual(isLogFresh(NOW - 30 * 60_000, NOW, 20 * 60_000), false);
480
+ });
481
+
482
+ test('isLogFresh: missing/invalid mtime or window → false', () => {
483
+ assert.strictEqual(isLogFresh(null, NOW, 20 * 60_000), false);
484
+ assert.strictEqual(isLogFresh(NaN, NOW, 20 * 60_000), false);
485
+ assert.strictEqual(isLogFresh(NOW - 60_000, NOW, null), false);
486
+ });
487
+
488
+ // selectReapableJobs — Part B wiring: a pidless row past grace now consults
489
+ // the run log for a pid, and a fresh log skips the row entirely, BEFORE
490
+ // falling back to findLiveProcess or the existing terminal reap.
491
+
492
+ test('selectReapableJobs: pidless + past grace + log yields a LIVE pid → recovered, never reapable, row stays running', () => {
493
+ const jobs = [{ slug: 'zombie-log-alive', status: 'running', startedAt: agoMin(464) }];
494
+ const pidAlive = (pid) => pid === 3138458;
495
+ const getLogPid = () => 3138458;
496
+ const { reapable, recovered, warnings } = selectReapableJobs(jobs, NOW, {
497
+ pidAlive, grace: GRACE, getLogPid,
498
+ });
499
+ assert.deepStrictEqual(reapable, []);
500
+ assert.deepStrictEqual(warnings, []);
501
+ assert.strictEqual(recovered.length, 1);
502
+ assert.strictEqual(recovered[0].slug, 'zombie-log-alive');
503
+ assert.strictEqual(recovered[0].pid, 3138458);
504
+ });
505
+
506
+ test('selectReapableJobs: pidless + past grace + log yields a DEAD pid → still reaps (no regression)', () => {
507
+ const jobs = [{ slug: 'zombie-log-dead', status: 'running', startedAt: agoMin(464) }];
508
+ const getLogPid = () => 3138458;
509
+ const { reapable, recovered } = selectReapableJobs(jobs, NOW, {
510
+ pidAlive: alwaysDead, grace: GRACE, getLogPid,
511
+ });
512
+ assert.deepStrictEqual(recovered, []);
513
+ assert.strictEqual(reapable.length, 1);
514
+ assert.strictEqual(reapable[0].pidless, true);
515
+ });
516
+
517
+ test('selectReapableJobs: pidless + past grace + FRESH log mtime → skipped entirely (neither reapable nor recovered)', () => {
518
+ const jobs = [{ slug: 'chatty-pidless', status: 'running', startedAt: agoMin(464) }];
519
+ const getLogMtimeMs = () => NOW - 60_000; // modified 1 minute ago
520
+ const { reapable, recovered, warnings } = selectReapableJobs(jobs, NOW, {
521
+ pidAlive: alwaysAlive, grace: GRACE, getLogMtimeMs, logFreshWindowMs: 20 * 60_000,
522
+ });
523
+ assert.deepStrictEqual(reapable, []);
524
+ assert.deepStrictEqual(recovered, []);
525
+ assert.strictEqual(warnings.length, 1);
526
+ assert.strictEqual(warnings[0].slug, 'chatty-pidless');
527
+ assert.match(warnings[0].reason, /log evidence/);
528
+ });
529
+
530
+ test('selectReapableJobs: pidless + past grace + STALE log mtime + no recoverable pid → reaps with the existing reason string, byte-identical', () => {
531
+ const jobs = [{ slug: 'zombie', status: 'running', startedAt: agoMin(464) }];
532
+ const getLogMtimeMs = () => NOW - 25 * 60_000; // stale relative to a 20m window
533
+ const { reapable } = selectReapableJobs(jobs, NOW, {
534
+ pidAlive: alwaysAlive, grace: GRACE, getLogMtimeMs, logFreshWindowMs: 20 * 60_000,
535
+ });
536
+ assert.strictEqual(reapable.length, 1);
537
+ assert.strictEqual(reapable[0].reason, 'reaped: no runtime.pid recorded after 10m — spawn never completed');
538
+ });
539
+
540
+ test('selectReapableJobs: a row WITH a live runtime.pid is untouched by every new code path', () => {
541
+ const jobs = [{ slug: 'live', status: 'running', runtime: { pid: 1 }, startedAt: agoMin(500) }];
542
+ const getLogPid = () => { throw new Error('must never be called for a live-pid row'); };
543
+ const getLogMtimeMs = () => { throw new Error('must never be called for a live-pid row'); };
544
+ const { reapable, recovered, warnings } = selectReapableJobs(jobs, NOW, {
545
+ pidAlive: alwaysAlive, grace: GRACE, getLogPid, getLogMtimeMs, logFreshWindowMs: 20 * 60_000,
546
+ });
547
+ assert.deepStrictEqual(reapable, []);
548
+ assert.deepStrictEqual(recovered, []);
549
+ assert.deepStrictEqual(warnings, []);
550
+ });
551
+
552
+ test('selectReapableJobs: pidless + past grace + landedCommit on the row → reapable entry carries a failureOverride', () => {
553
+ const jobs = [{
554
+ slug: 'shipped-but-pidless',
555
+ status: 'running',
556
+ startedAt: agoMin(464),
557
+ landedCommit: 'f3e35a7f9e49f13a9eb8c338b0d086160e32a87f',
558
+ }];
559
+ const { reapable } = selectReapableJobs(jobs, NOW, { pidAlive: alwaysAlive, grace: GRACE });
560
+ assert.strictEqual(reapable.length, 1);
561
+ assert.strictEqual(reapable[0].pidless, true);
562
+ assert.ok(reapable[0].failureOverride, 'the reapable entry must carry the evidence-based override');
563
+ assert.strictEqual(reapable[0].failureOverride.landedCommit, 'f3e35a7f9e49f13a9eb8c338b0d086160e32a87f');
564
+ // The existing byte-for-byte reason string is untouched by this PRD — the
565
+ // override is carried as a separate field, not folded into `reason`.
566
+ assert.strictEqual(
567
+ reapable[0].reason,
568
+ 'reaped: no runtime.pid recorded after 10m — spawn never completed',
569
+ );
570
+ });
571
+
572
+ test('selectReapableJobs: pidless + past grace + NO landedCommit on the row → failureOverride is null, unchanged from today', () => {
573
+ const jobs = [{ slug: 'genuinely-never-ran', status: 'running', startedAt: agoMin(464) }];
574
+ const { reapable } = selectReapableJobs(jobs, NOW, { pidAlive: alwaysAlive, grace: GRACE });
575
+ assert.strictEqual(reapable.length, 1);
576
+ assert.strictEqual(reapable[0].failureOverride, null);
577
+ });
@@ -6,11 +6,11 @@
6
6
  * when the admin API is unreachable — only the readiness section may report
7
7
  * unavailable.
8
8
  *
9
- * TOKEN_PATH (scripts/scheduler-mcp-server.cjs) is computed once, at
10
- * require-time, from os.homedir(). Since os.homedir() reads process.env.HOME
11
- * on POSIX, each test temporarily repoints HOME at a fresh tmp dir, clears
12
- * the require cache, and re-requires the module so TOKEN_PATH resolves under
13
- * that tmp dir — never touching the real ~/.claude/session-manager/admin-api.json.
9
+ * The admin token path (schedulerPaths.adminTokenPath) resolves lazily from
10
+ * os.homedir(). Since os.homedir() reads process.env.HOME on POSIX, each test
11
+ * repoints HOME at a fresh tmp dir for its duration (restored in afterEach)
12
+ * so the token path resolves under that tmp dir, never the real
13
+ * ~/.claude/session-manager/admin-api.json.
14
14
  *
15
15
  * Run: timeout 120 npx vitest run src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs
16
16
  */
@@ -72,8 +72,7 @@ function requireServerWithHome(homeDir) {
72
72
  process.env.HOME = homeDir;
73
73
  delete require.cache[require.resolve(SERVER_PATH)];
74
74
  const mod = require(SERVER_PATH);
75
- process.env.HOME = originalHome;
76
- originalHome = undefined;
75
+ // HOME stays redirected until afterEach: admin-token path resolves lazily.
77
76
  return mod;
78
77
  }
79
78