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
@@ -38,7 +38,7 @@ const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'reap-dead-running-jobs-te
38
38
  process.env.HOME = tmpHome;
39
39
 
40
40
  const { reapDeadRunningJobs, PIDLESS_SPAWN_GRACE_MS } = require('../scheduler.cjs');
41
- const { AUDIT_LOG_PATH } = require('../lib/auditLog.cjs');
41
+ const { auditLogPath } = require('../lib/auditLog.cjs');
42
42
  // queueStore's cwd discovery is cached for 30s (queueStore.cjs's CACHE_MS) —
43
43
  // a project registered by THIS test after an earlier test already populated
44
44
  // that cache (and didn't itself bust it, e.g. because it found nothing
@@ -68,10 +68,29 @@ function registerActiveProject(cwd) {
68
68
  fs.writeFileSync(path.join(slugDir, 'transcript.jsonl'), JSON.stringify({ cwd }) + '\n');
69
69
  }
70
70
 
71
+ // Every real scheduler row is born from a real PRD .md file — reconcile()'s
72
+ // auto-archive-drop path (a terminal job whose slug has no on-disk PRD
73
+ // anywhere is treated as "archived on purpose": dropped from queue.json and
74
+ // backfilled to history.jsonl, see scheduler-reconcile-history-backfill.
75
+ // test.cjs) fires the very next time broadcast({flush:true}) reconciles —
76
+ // which reapDeadRunningJobs() itself does on every reap. Without a matching
77
+ // PRD source, a row this test just reaped to a terminal status vanishes from
78
+ // queue.json before the assertions below ever read it back. Epic-scoped (not
79
+ // the legacy flat prds/ dir) so it's never swept by consolidateFlatPrds
80
+ // either.
81
+ function writeFixturePrd(cwd, slug) {
82
+ const prdsDir = path.join(cwd, 'session-manager-operations', 'scheduler', 'epics', 'test-fixture-epic', 'prds');
83
+ fs.mkdirSync(prdsDir, { recursive: true });
84
+ fs.writeFileSync(path.join(prdsDir, `${slug}.md`), 'Test fixture PRD.', 'utf8');
85
+ }
86
+
71
87
  function writeProjectQueue(cwd, jobs) {
72
88
  const stateDir = path.join(cwd, 'session-manager-operations', 'scheduler', 'state');
73
89
  fs.mkdirSync(stateDir, { recursive: true });
74
90
  fs.writeFileSync(path.join(stateDir, 'queue.json'), JSON.stringify({ jobs }, null, 2));
91
+ for (const job of jobs) {
92
+ if (job && job.slug) writeFixturePrd(job.cwd || cwd, job.slug);
93
+ }
75
94
  return path.join(stateDir, 'queue.json');
76
95
  }
77
96
 
@@ -131,7 +150,7 @@ test('reapDeadRunningJobs reaps a pidless row older than PIDLESS_SPAWN_GRACE_MS
131
150
  // Empty run dir: created, but never written to (the exact 2026-09-01 repro).
132
151
  fs.mkdirSync(path.join(tmpHome, '.claude', 'session-manager', 'scheduled-plans', 'runs', 'run-pidless-zombie'), { recursive: true });
133
152
 
134
- const auditSizeBefore = fs.existsSync(AUDIT_LOG_PATH) ? fs.statSync(AUDIT_LOG_PATH).size : 0;
153
+ const auditSizeBefore = fs.existsSync(auditLogPath()) ? fs.statSync(auditLogPath()).size : 0;
135
154
 
136
155
  await reapDeadRunningJobs();
137
156
 
@@ -142,12 +161,54 @@ test('reapDeadRunningJobs reaps a pidless row older than PIDLESS_SPAWN_GRACE_MS
142
161
  assert.equal(jobs[0].runtime, undefined);
143
162
  assert.equal(jobs[0].gateOutcome, 'never_ran', 'a pidless reap means the gate never had a chance to run');
144
163
 
145
- const auditText = fs.readFileSync(AUDIT_LOG_PATH, 'utf8').slice(auditSizeBefore);
164
+ const auditText = fs.readFileSync(auditLogPath(), 'utf8').slice(auditSizeBefore);
146
165
  const auditLines = auditText.trim().split('\n').filter(Boolean).map((l) => JSON.parse(l));
147
166
  const pidlessEvent = auditLines.find((e) => e.kind === 'job_reaped_pidless' && e.slug === 'pidless-zombie');
148
167
  assert.ok(pidlessEvent, 'reaping a pidless row must leave an audit trace');
149
168
  });
150
169
 
170
+ test('reapDeadRunningJobs: a pidless row past grace with a landedCommit already recorded is routed to needs_review, not failed, naming the commit sha (PRD 1173)', async () => {
171
+ const projectCwd = path.join(tmpHome, 'c3-project-pidless-with-landed-commit');
172
+ fs.mkdirSync(projectCwd, { recursive: true });
173
+ registerActiveProject(projectCwd);
174
+
175
+ const staleStartedAt = new Date(Date.now() - PIDLESS_SPAWN_GRACE_MS - 60_000).toISOString();
176
+ const landedSha = 'f3e35a7f9e49f13a9eb8c338b0d086160e32a87f';
177
+ const queuePath = writeProjectQueue(projectCwd, [
178
+ {
179
+ slug: 'shipped-but-pidless',
180
+ status: 'running',
181
+ cwd: projectCwd,
182
+ runId: 'run-shipped-but-pidless',
183
+ startedAt: staleStartedAt,
184
+ landedCommit: landedSha,
185
+ // no runtime key at all — the spawn never got far enough to record one
186
+ },
187
+ ]);
188
+ // Empty run dir, same shape as the sibling 'failed' test below — the
189
+ // landedCommit evidence must be what changes the outcome, not the log.
190
+ fs.mkdirSync(path.join(tmpHome, '.claude', 'session-manager', 'scheduled-plans', 'runs', 'run-shipped-but-pidless'), { recursive: true });
191
+
192
+ await reapDeadRunningJobs();
193
+
194
+ const jobs = JSON.parse(fs.readFileSync(queuePath, 'utf8')).jobs;
195
+ const row = jobs.find((j) => j.slug === 'shipped-but-pidless');
196
+ assert.equal(row.status, 'needs_review', 'a row with proof of landed work must never be transitioned to failed by the pidless branch');
197
+ assert.equal(row.landedCommit, landedSha, 'the pre-existing landedCommit must be preserved on the row');
198
+ assert.match(row.error, new RegExp(landedSha), 'the reason must name the recorded commit sha');
199
+ assert.equal(row.verifierVerdict, 'pidless_reap_with_landed_commit');
200
+
201
+ // Scan the whole audit log for THIS slug's event rather than slicing by a
202
+ // byte offset from statSync — a prior test's audit entries can contain
203
+ // multi-byte characters (e.g. an em dash in a reaper reason string), and
204
+ // slicing a utf8-decoded JS string (UTF-16 code units) at a byte offset
205
+ // then silently corrupts the boundary. The slug is unique to this test.
206
+ const auditLines = fs.readFileSync(auditLogPath(), 'utf8').trim().split('\n').filter(Boolean).map((l) => JSON.parse(l));
207
+ const pidlessEvent = auditLines.find((e) => e.kind === 'job_reaped_pidless' && e.slug === 'shipped-but-pidless');
208
+ assert.ok(pidlessEvent, 'reaping this row must still leave an audit trace');
209
+ assert.equal(pidlessEvent.landedCommit, landedSha, 'the audit event must carry the landed-commit evidence so the decision is reconstructable from the audit log alone');
210
+ });
211
+
151
212
  test('reapDeadRunningJobs clears runId on a pidless reap whose run dir holds only a sibling slug\'s files', async () => {
152
213
  const projectCwd = path.join(tmpHome, 'c2-project-batch-sibling');
153
214
  fs.mkdirSync(projectCwd, { recursive: true });
@@ -573,3 +634,121 @@ test('reapDeadRunningJobs: a non-git cwd skips the integration check entirely, p
573
634
  assert.equal(row.status, 'completed', 'a non-git cwd must keep today\'s behaviour — the check is meaningless there');
574
635
  assert.equal(row.verifierVerdict, undefined);
575
636
  });
637
+
638
+ // ---------- Reaper landed-commit evidence gate (job 1192 incident) ----------
639
+ //
640
+ // job 1192 shipped a real 3-file commit (7bf5fa3f...) and was still
641
+ // stamped 'failed' by reapDeadRunningJobs because a `landedCommit` field
642
+ // being present was never actually verified against the repo. These cover
643
+ // the dead-pid branch specifically (runtime.pid recorded, process
644
+ // confirmed dead) rather than the pidless branch: a pidless row with a
645
+ // non-empty landedCommit is already fully owned by PRD 1173's
646
+ // resolvePidlessFailureOverride (routes to needs_review, tested above) —
647
+ // this PRD's gate exists for the shape 1173 doesn't cover, where the
648
+ // reaper would otherwise fall straight through to 'failed' with no
649
+ // evidence check of any kind.
650
+
651
+ test('reapDeadRunningJobs: a dead-pid reap with a git-resolvable landedCommit transitions to completed, naming the reap cause and the evidence, source reapDeadRunningJobs:landed', async () => {
652
+ const projectCwd = path.join(tmpHome, 'n-project-landed-commit-evidence');
653
+ initRepo(projectCwd);
654
+ registerActiveProject(projectCwd);
655
+
656
+ fs.writeFileSync(path.join(projectCwd, 'shipped.txt'), 'done\n', 'utf8');
657
+ git(['add', '-A'], projectCwd);
658
+ git(['commit', '-q', '-m', 'ship the PRD work'], projectCwd);
659
+ const landedSha = git(['rev-parse', 'HEAD'], projectCwd).trim();
660
+
661
+ const runId = 'run-landed-commit-evidence';
662
+ const queuePath = writeProjectQueue(projectCwd, [
663
+ {
664
+ slug: 'landed-but-reaped',
665
+ status: 'running',
666
+ cwd: projectCwd,
667
+ runId,
668
+ runtime: { pid: 999999 }, // guaranteed-dead pid
669
+ landedCommit: landedSha,
670
+ },
671
+ ]);
672
+ // Empty run dir → classifyRunOutcome finds no result event → 'no_result'
673
+ // → without this PRD's gate this would stamp 'failed' despite the real
674
+ // commit already sitting on HEAD.
675
+ fs.mkdirSync(path.join(tmpHome, '.claude', 'session-manager', 'scheduled-plans', 'runs', runId), { recursive: true });
676
+
677
+ bustCwdCache();
678
+ await reapDeadRunningJobs();
679
+
680
+ const jobs = JSON.parse(fs.readFileSync(queuePath, 'utf8')).jobs;
681
+ const row = jobs.find((j) => j.slug === 'landed-but-reaped');
682
+ assert.equal(row.status, 'completed', 'a resolvable landedCommit must promote the reap to completed, not failed');
683
+ assert.equal(row.landedCommit, landedSha);
684
+ assert.equal(row.exitCode, 0);
685
+ assert.equal(row.error, null);
686
+ const lastTransition = row.statusHistory[row.statusHistory.length - 1];
687
+ assert.equal(lastTransition.source, 'reapDeadRunningJobs:landed');
688
+ assert.match(lastTransition.reason, /process gone/, 'the reap cause must still be named');
689
+ assert.match(lastTransition.reason, new RegExp(landedSha), 'the evidence sha must be named');
690
+ assert.match(lastTransition.reason, /resolves/);
691
+ });
692
+
693
+ test('reapDeadRunningJobs: a dead-pid reap with an unresolvable (stale) landedCommit keeps today\'s behaviour exactly — failed, same reason shape, same source', async () => {
694
+ const projectCwd = path.join(tmpHome, 'o-project-stale-landed-commit');
695
+ initRepo(projectCwd);
696
+ registerActiveProject(projectCwd);
697
+
698
+ const staleSha = '0123456789abcdef0123456789abcdef01234567'; // well-formed sha, never committed anywhere
699
+ const runId = 'run-stale-landed-commit';
700
+ const queuePath = writeProjectQueue(projectCwd, [
701
+ {
702
+ slug: 'stale-landed-commit',
703
+ status: 'running',
704
+ cwd: projectCwd,
705
+ runId,
706
+ runtime: { pid: 999999 },
707
+ landedCommit: staleSha,
708
+ },
709
+ ]);
710
+ fs.mkdirSync(path.join(tmpHome, '.claude', 'session-manager', 'scheduled-plans', 'runs', runId), { recursive: true });
711
+
712
+ bustCwdCache();
713
+ await reapDeadRunningJobs();
714
+
715
+ const jobs = JSON.parse(fs.readFileSync(queuePath, 'utf8')).jobs;
716
+ const row = jobs.find((j) => j.slug === 'stale-landed-commit');
717
+ assert.equal(row.status, 'failed', 'a landedCommit that does not resolve in the repo must never be trusted as evidence');
718
+ assert.equal(row.landedCommit, staleSha, 'the stale field is left on the row untouched, never cleared');
719
+ assert.match(row.error, /reaped: process gone \(outcome=no_result\)/);
720
+ assert.doesNotMatch(row.error, /resolves/);
721
+ const lastTransition = row.statusHistory[row.statusHistory.length - 1];
722
+ assert.equal(lastTransition.source, 'reapDeadRunningJobs', 'no evidence found — source must stay the plain reaper source, not :landed');
723
+ });
724
+
725
+ test('reapDeadRunningJobs: a landedCommit that cannot be git-resolved because the row cwd is not a usable git repo falls back to failed without throwing out of the reaper', async () => {
726
+ const projectCwd = path.join(tmpHome, 'p-project-unresolvable-cwd');
727
+ // Deliberately NOT initRepo(projectCwd) — no `.git` at all, the same
728
+ // "cwd git resolution cannot possibly succeed" shape a deleted/torn-down
729
+ // worktree checkout would produce (git itself errors identically for
730
+ // "not a git repository" and "no such directory" — both must be caught
731
+ // and treated as no-evidence, never thrown out of the reaper).
732
+ fs.mkdirSync(projectCwd, { recursive: true });
733
+ registerActiveProject(projectCwd);
734
+
735
+ const runId = 'run-unresolvable-cwd';
736
+ const queuePath = writeProjectQueue(projectCwd, [
737
+ {
738
+ slug: 'unresolvable-cwd-job',
739
+ status: 'running',
740
+ cwd: projectCwd,
741
+ runId,
742
+ runtime: { pid: 999999 },
743
+ landedCommit: '89abcdef89abcdef89abcdef89abcdef89abcdef',
744
+ },
745
+ ]);
746
+ fs.mkdirSync(path.join(tmpHome, '.claude', 'session-manager', 'scheduled-plans', 'runs', runId), { recursive: true });
747
+
748
+ bustCwdCache();
749
+ await assert.doesNotReject(() => reapDeadRunningJobs(), 'a bounded git spawn failure/timeout must never escape the reaper as an unhandled rejection');
750
+
751
+ const jobs = JSON.parse(fs.readFileSync(queuePath, 'utf8')).jobs;
752
+ const row = jobs.find((j) => j.slug === 'unresolvable-cwd-job');
753
+ assert.equal(row.status, 'failed', 'an unresolvable cwd must fall back to failed, exactly like no evidence at all');
754
+ });
@@ -0,0 +1,87 @@
1
+ 'use strict';
2
+
3
+ // Ported from the retired top-level test-root directory (a node:test suite,
4
+ // never runnable by this repo's vitest-only setup — see CLAUDE.md's Commands
5
+ // section). The existing src/main/lib/__tests__/reaperHelpers.test.cjs
6
+ // exercises classifyRunOutcome extensively via selectReapableJobs and the
7
+ // rate-limit paths, but never asserts claudePidAlive directly (only ever
8
+ // injects a stand-in `pidAlive` function), and never pins the bare
9
+ // no-result/empty-file/malformed-event edge cases below. These fill that
10
+ // gap.
11
+ //
12
+ // Run: timeout 120 npx vitest run src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs
13
+
14
+ const assert = require('node:assert/strict');
15
+ const os = require('node:os');
16
+ const path = require('node:path');
17
+ const fs = require('node:fs');
18
+ const { claudePidAlive, classifyRunOutcome } = require('../lib/reaperHelpers.cjs');
19
+
20
+ // ─────────────────────────────────────────── claudePidAlive
21
+
22
+ test('claudePidAlive: nonexistent pid (999999) returns false', () => {
23
+ assert.equal(claudePidAlive(999999), false);
24
+ });
25
+
26
+ test('claudePidAlive: pid 0 returns false', () => {
27
+ assert.equal(claudePidAlive(0), false);
28
+ });
29
+
30
+ test('claudePidAlive: pid 1 returns false', () => {
31
+ assert.equal(claudePidAlive(1), false);
32
+ });
33
+
34
+ test('claudePidAlive: null returns false', () => {
35
+ assert.equal(claudePidAlive(null), false);
36
+ });
37
+
38
+ test('claudePidAlive: undefined returns false', () => {
39
+ assert.equal(claudePidAlive(undefined), false);
40
+ });
41
+
42
+ // ─────────────────────────────────────────── classifyRunOutcome
43
+
44
+ function writeTempLog(content) {
45
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'reaper-basics-test-'));
46
+ const filePath = path.join(dir, 'test.log');
47
+ fs.writeFileSync(filePath, content, 'utf8');
48
+ return filePath;
49
+ }
50
+
51
+ test('classifyRunOutcome: success without an is_error field → success', () => {
52
+ const p = writeTempLog('{"type":"result","subtype":"success","result":"ok"}\n');
53
+ assert.equal(classifyRunOutcome(p), 'success');
54
+ });
55
+
56
+ test('classifyRunOutcome: subtype success but is_error:true → failed (error wins)', () => {
57
+ const p = writeTempLog('{"type":"result","subtype":"success","is_error":true}\n');
58
+ assert.equal(classifyRunOutcome(p), 'failed');
59
+ });
60
+
61
+ test('classifyRunOutcome: no result event in the tail → no_result', () => {
62
+ const log = [
63
+ '{"type":"assistant","message":"working"}',
64
+ '{"type":"tool_use","name":"bash"}',
65
+ 'some plain text log line',
66
+ ].join('\n');
67
+ const p = writeTempLog(log);
68
+ assert.equal(classifyRunOutcome(p), 'no_result');
69
+ });
70
+
71
+ test('classifyRunOutcome: empty file → no_result', () => {
72
+ const p = writeTempLog('');
73
+ assert.equal(classifyRunOutcome(p), 'no_result');
74
+ });
75
+
76
+ test('classifyRunOutcome: last result event wins when multiple exist', () => {
77
+ const log = [
78
+ '{"type":"result","subtype":"success"}',
79
+ '{"type":"result","subtype":"error","is_error":true}',
80
+ ].join('\n');
81
+ const p = writeTempLog(log);
82
+ assert.equal(classifyRunOutcome(p), 'failed');
83
+ });
84
+
85
+ test('classifyRunOutcome: nonexistent file → no_result (readTail swallows I/O errors)', () => {
86
+ assert.equal(classifyRunOutcome('/nonexistent/path/that/cannot/exist.log'), 'no_result');
87
+ });
@@ -0,0 +1,100 @@
1
+ /**
2
+ * scheduler-reconcile-cwd-preserve.test.cjs — regression test for the
3
+ * data-loss bug found while chasing PRD 1218's test:unit gate: reconcile()
4
+ * rebuilt a queue row's `cwd` unconditionally from its PRD's own
5
+ * frontmatter (`cwd: p.cwd`) at all three of its row-construction sites. A
6
+ * PRD file with no `cwd:` frontmatter key — every hand-written or legacy PRD,
7
+ * and every fixture in this test suite before this fix — parses to
8
+ * `p.cwd === undefined`, which nulled the row's real, already-correct `cwd`.
9
+ * queueStore.writeSplit's `job.cwd || defaultCwd` bucketing then relocated
10
+ * the row into schedulerBatch.cjs's DEFAULT_PROJECT_CWD shard
11
+ * (`~/Projects/session-manager`) — the WRONG project — while the row's own
12
+ * project's shard was rewritten without it. A PRD's frontmatter `cwd` must
13
+ * REFINE a row's existing `cwd`, never erase one.
14
+ *
15
+ * Covers the normal refresh path (an existing row matched to its PRD) and
16
+ * the fresh-discovery path (a PRD with no prior row, falling back to the
17
+ * project root it was actually found under via
18
+ * prdLocations.deriveProjectCwdFromPrdPath rather than nulling to
19
+ * DEFAULT_PROJECT_CWD).
20
+ *
21
+ * Run: timeout 120 npx vitest run src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs
22
+ */
23
+
24
+ 'use strict';
25
+
26
+ import { test, expect, beforeAll, afterAll } from 'vitest';
27
+ const fs = require('node:fs');
28
+ const os = require('node:os');
29
+ const path = require('node:path');
30
+
31
+ let tmpHome;
32
+ let originalHome;
33
+ let reconcile;
34
+ let DEFAULT_PROJECT_CWD;
35
+
36
+ beforeAll(() => {
37
+ originalHome = process.env.HOME;
38
+ tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-reconcile-cwd-preserve-'));
39
+ process.env.HOME = tmpHome;
40
+ ({ reconcile } = require('../scheduler.cjs'));
41
+ ({ DEFAULT_PROJECT_CWD } = require('../lib/schedulerBatch.cjs'));
42
+ });
43
+
44
+ afterAll(() => {
45
+ process.env.HOME = originalHome;
46
+ fs.rmSync(tmpHome, { recursive: true, force: true });
47
+ });
48
+
49
+ // allProjectCwds()/activeProjectCwds() (queueStore.cjs's stateCwds) discover
50
+ // project cwds by scanning ~/.claude/projects/*/*.jsonl for a `cwd` field —
51
+ // same pattern as scheduler-reap-dead-running-jobs.test.cjs and friends.
52
+ function registerActiveProject(cwd) {
53
+ const projectsDir = path.join(tmpHome, '.claude', 'projects');
54
+ const slugDir = path.join(projectsDir, `fake-project-slug-${path.basename(cwd)}`);
55
+ fs.mkdirSync(slugDir, { recursive: true });
56
+ fs.writeFileSync(path.join(slugDir, 'transcript.jsonl'), JSON.stringify({ cwd }) + '\n');
57
+ }
58
+
59
+ test('reconcile() never nulls an existing row\'s cwd because its PRD has no cwd: frontmatter', async () => {
60
+ const projectCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-reconcile-cwd-preserve-project-'));
61
+ registerActiveProject(projectCwd);
62
+
63
+ const slug = `1218-cwd-preserve-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
64
+ const prdsDir = path.join(projectCwd, 'session-manager-operations', 'scheduler', 'epics', 'test-fixture-epic', 'prds');
65
+ fs.mkdirSync(prdsDir, { recursive: true });
66
+ // Deliberately NO `cwd:` frontmatter key — the exact shape of every
67
+ // hand-written or legacy PRD that triggered the bug.
68
+ fs.writeFileSync(path.join(prdsDir, `${slug}.md`), '# Goal\n\nNo cwd frontmatter here.\n', 'utf8');
69
+
70
+ const job = { slug, title: 'x', cwd: projectCwd, status: 'pending' };
71
+ const state = { jobs: [job] };
72
+
73
+ await reconcile(state);
74
+
75
+ const row = state.jobs.find((j) => j.slug === slug);
76
+ expect(row).toBeDefined();
77
+ expect(row.cwd).toBe(projectCwd);
78
+ expect(row.cwd).not.toBe(DEFAULT_PROJECT_CWD);
79
+ });
80
+
81
+ test('reconcile() gives a freshly-discovered, frontmatter-less PRD the project root it was actually found under, never DEFAULT_PROJECT_CWD', async () => {
82
+ const projectCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-reconcile-cwd-preserve-fresh-'));
83
+ registerActiveProject(projectCwd);
84
+
85
+ const slug = `1218-cwd-preserve-fresh-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
86
+ const prdsDir = path.join(projectCwd, 'session-manager-operations', 'scheduler', 'epics', 'test-fixture-epic', 'prds');
87
+ fs.mkdirSync(prdsDir, { recursive: true });
88
+ fs.writeFileSync(path.join(prdsDir, `${slug}.md`), '# Goal\n\nNo cwd frontmatter here either.\n', 'utf8');
89
+
90
+ // No prior row at all — this PRD is discovered fresh by reconcile()'s own
91
+ // onDisk scan, not handed in via `state.jobs`.
92
+ const state = { jobs: [] };
93
+
94
+ await reconcile(state);
95
+
96
+ const row = state.jobs.find((j) => j.slug === slug);
97
+ expect(row).toBeDefined();
98
+ expect(row.cwd).toBe(projectCwd);
99
+ expect(row.cwd).not.toBe(DEFAULT_PROJECT_CWD);
100
+ });
@@ -127,8 +127,8 @@ test('reconcile() repairs a queue row with an invalid status back to pending, lo
127
127
  // require time, so spying on the module's export property after the
128
128
  // fact wouldn't intercept scheduler.cjs's already-bound reference —
129
129
  // assert against the real (tmpHome-isolated) audit log file instead.
130
- const { AUDIT_LOG_PATH } = require('../lib/auditLog.cjs');
131
- expect(AUDIT_LOG_PATH.startsWith(tmpHome)).toBe(true);
130
+ const { auditLogPath } = require('../lib/auditLog.cjs');
131
+ expect(auditLogPath().startsWith(tmpHome)).toBe(true);
132
132
 
133
133
  queueStore.bustCwdCache();
134
134
  await scheduler.reconcile(state);
@@ -142,7 +142,7 @@ test('reconcile() repairs a queue row with an invalid status back to pending, lo
142
142
  );
143
143
  expect(warnedRepair).toBe(true);
144
144
 
145
- const auditLines = fs.readFileSync(AUDIT_LOG_PATH, 'utf8').trim().split('\n').filter(Boolean).map((l) => JSON.parse(l));
145
+ const auditLines = fs.readFileSync(auditLogPath(), 'utf8').trim().split('\n').filter(Boolean).map((l) => JSON.parse(l));
146
146
  const auditedRepair = auditLines.some(
147
147
  (rec) => rec.kind === 'scheduler_row_repaired' && rec.slug === badRow.slug && rec.oldStatus === 'queued',
148
148
  );
@@ -80,7 +80,7 @@ test('reconcile() quarantines a newly-discovered PRD with no createdVia provenan
80
80
  fs.writeFileSync(path.join(stateDir, 'queue.json'), JSON.stringify({ jobs: [] }, null, 2), 'utf8');
81
81
 
82
82
  const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
83
- const { AUDIT_LOG_PATH } = require('../lib/auditLog.cjs');
83
+ const { auditLogPath } = require('../lib/auditLog.cjs');
84
84
 
85
85
  queueStore.bustCwdCache();
86
86
  const state = { jobs: [], invalidJobs: [], paused: null };
@@ -95,7 +95,7 @@ test('reconcile() quarantines a newly-discovered PRD with no createdVia provenan
95
95
  );
96
96
  expect(warned).toBe(true);
97
97
 
98
- const auditLines = fs.readFileSync(AUDIT_LOG_PATH, 'utf8').trim().split('\n').filter(Boolean).map((l) => JSON.parse(l));
98
+ const auditLines = fs.readFileSync(auditLogPath(), 'utf8').trim().split('\n').filter(Boolean).map((l) => JSON.parse(l));
99
99
  expect(auditLines.some((rec) => rec.kind === 'prd_quarantined' && rec.slug === '9001-hand-written')).toBe(true);
100
100
  } finally {
101
101
  fs.rmSync(cwd, { recursive: true, force: true });
@@ -230,7 +230,7 @@ test('reconcile() adopts a quarantined row to pending once its PRD file is stamp
230
230
  fm.issuedAt = '2026-08-07T01:00:00.000Z';
231
231
  fs.writeFileSync(prdPath, serializePrdFile(fm, body), 'utf8');
232
232
 
233
- const { AUDIT_LOG_PATH } = require('../lib/auditLog.cjs');
233
+ const { auditLogPath } = require('../lib/auditLog.cjs');
234
234
  queueStore.bustCwdCache();
235
235
  const state = { jobs: [quarantinedRow], invalidJobs: [], paused: null };
236
236
  await scheduler.reconcile(state);
@@ -239,7 +239,7 @@ test('reconcile() adopts a quarantined row to pending once its PRD file is stamp
239
239
  expect(row).toBeDefined();
240
240
  expect(row.status).toBe('pending');
241
241
 
242
- const auditLines = fs.readFileSync(AUDIT_LOG_PATH, 'utf8').trim().split('\n').filter(Boolean).map((l) => JSON.parse(l));
242
+ const auditLines = fs.readFileSync(auditLogPath(), 'utf8').trim().split('\n').filter(Boolean).map((l) => JSON.parse(l));
243
243
  expect(auditLines.some((rec) => rec.kind === 'scheduler_prd_adopted' && rec.slug === '9004-adopt-me')).toBe(true);
244
244
  } finally {
245
245
  fs.rmSync(cwd, { recursive: true, force: true });
@@ -0,0 +1,110 @@
1
+ /**
2
+ * scheduler-shard-quarantine.test.cjs — a torn per-project queue shard
3
+ * quarantines THAT project only. tickQueue keeps dispatching the healthy
4
+ * project, never scans/mints rows for the torn project's PRDs, never touches
5
+ * the torn file, and health names the quarantined cwd (non-GREEN).
6
+ *
7
+ * Everything runs under a temp HOME; the stub `claude` writes a marker into
8
+ * its own cwd so the test can prove where a spawn did (not) happen.
9
+ *
10
+ * Run: timeout 120 npx vitest run src/main/__tests__/scheduler-shard-quarantine.test.cjs
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ import { test, expect, beforeAll, afterAll } from 'vitest';
16
+ const fs = require('node:fs');
17
+ const os = require('node:os');
18
+ const path = require('node:path');
19
+ const claudeStub = require('../../../tests/helpers/claudeStub.cjs');
20
+
21
+ let tmpHome;
22
+ let originalHome;
23
+ let originalWt;
24
+ let scheduler;
25
+
26
+ function registerActiveProject(cwd) {
27
+ const slugDir = path.join(tmpHome, '.claude', 'projects', `fake-project-slug-${path.basename(cwd)}`);
28
+ fs.mkdirSync(slugDir, { recursive: true });
29
+ fs.writeFileSync(path.join(slugDir, 'transcript.jsonl'), JSON.stringify({ cwd }) + '\n');
30
+ }
31
+
32
+ function makeProject(name) {
33
+ const cwd = path.join(tmpHome, 'Projects', name);
34
+ const stateDir = path.join(cwd, 'session-manager-operations', 'scheduler', 'state');
35
+ const prdsDir = path.join(cwd, 'session-manager-operations', 'scheduler', 'epics', 'fixture-epic', 'prds');
36
+ fs.mkdirSync(stateDir, { recursive: true });
37
+ fs.mkdirSync(prdsDir, { recursive: true });
38
+ registerActiveProject(cwd);
39
+ return { cwd, prdsDir, queueFile: path.join(stateDir, 'queue.json') };
40
+ }
41
+
42
+ function writeClaudeStub() {
43
+ return claudeStub.writeClaudeStub({ body: `
44
+ require('fs').writeFileSync(require('path').join(process.cwd(), 'ran-here.marker'), 'yes');
45
+ process.stdout.write(JSON.stringify({ type: 'result', subtype: 'success', result: 'ok\\nSCHEDULER_VERDICT: PASS' }) + '\\n');
46
+ process.exit(0);
47
+ ` });
48
+ }
49
+
50
+ beforeAll(() => {
51
+ originalHome = process.env.HOME;
52
+ originalWt = process.env.SM_JOB_WORKTREE_DISABLE;
53
+ tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-shard-quarantine-'));
54
+ process.env.HOME = tmpHome;
55
+ process.env.SM_JOB_WORKTREE_DISABLE = '1';
56
+ process.env.SM_AUTOFIX_DISABLE = '1';
57
+ fs.mkdirSync(path.join(tmpHome, '.claude', 'session-manager'), { recursive: true });
58
+ scheduler = require('../scheduler.cjs');
59
+ });
60
+
61
+ afterAll(() => {
62
+ process.env.HOME = originalHome;
63
+ if (originalWt === undefined) delete process.env.SM_JOB_WORKTREE_DISABLE;
64
+ else process.env.SM_JOB_WORKTREE_DISABLE = originalWt;
65
+ delete process.env.SM_AUTOFIX_DISABLE;
66
+ delete process.env.SM_CLAUDE_BIN;
67
+ fs.rmSync(tmpHome, { recursive: true, force: true });
68
+ });
69
+
70
+ test('tickQueue dispatches the healthy project while the torn project is skipped; health names it', async () => {
71
+ const good = makeProject('good-proj');
72
+ const torn = makeProject('torn-proj');
73
+ const goodSlug = `9101-good-${process.pid}`;
74
+ const tornSlug = `9102-torn-${process.pid}`;
75
+ fs.writeFileSync(path.join(good.prdsDir, `${goodSlug}.md`), 'Healthy PRD.', 'utf8');
76
+ fs.writeFileSync(path.join(torn.prdsDir, `${tornSlug}.md`), 'PRD in the torn project.', 'utf8');
77
+ fs.writeFileSync(good.queueFile, JSON.stringify({
78
+ jobs: [{ slug: goodSlug, title: 'good', status: 'pending', cwd: good.cwd, dependsOn: [], createdAt: new Date().toISOString() }],
79
+ }));
80
+ // A running row was in flight when the shard tore — must never become pending.
81
+ const TORN = '{"jobs":[{"slug":"' + tornSlug + '","status":"running"}]}{"jobs":[{"slug":"';
82
+ fs.writeFileSync(torn.queueFile, TORN);
83
+ const before = fs.readFileSync(torn.queueFile);
84
+ process.env.SM_CLAUDE_BIN = writeClaudeStub();
85
+
86
+ const result = await scheduler.tickQueue({ bypassLoadGate: true });
87
+ expect(result.reason).not.toBe('unreadable');
88
+ expect(result.fired).toBe(true);
89
+ // flagUnreadable (via tickQueue's readQueue) snapshots the torn shard beside itself.
90
+ const snaps = fs.readdirSync(path.dirname(torn.queueFile)).filter((f) => f.startsWith('queue.json.corrupt-'));
91
+ expect(snaps).toHaveLength(1);
92
+
93
+ await new Promise((resolve, reject) => {
94
+ const t0 = Date.now();
95
+ const poll = () => {
96
+ if (fs.existsSync(path.join(good.cwd, 'ran-here.marker'))) return resolve();
97
+ if (Date.now() - t0 > 20_000) return reject(new Error('healthy project never dispatched'));
98
+ setTimeout(poll, 100);
99
+ };
100
+ poll();
101
+ });
102
+
103
+ expect(fs.existsSync(path.join(torn.cwd, 'ran-here.marker'))).toBe(false);
104
+ expect(fs.readFileSync(torn.queueFile).equals(before)).toBe(true);
105
+
106
+ const health = await require('../health.cjs').check();
107
+ expect(health.components.scheduler_queue.ok).toBe(false);
108
+ expect(health.components.scheduler_queue.quarantinedCwds).toEqual([torn.cwd]);
109
+ expect(health.issues.some((i) => i.includes(torn.cwd))).toBe(true);
110
+ }, 120_000); // health.check() alone can take tens of seconds