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
@@ -16,6 +16,7 @@
16
16
  'use strict';
17
17
 
18
18
  import { test, expect, vi, afterEach } from 'vitest';
19
+ const fs = require('node:fs');
19
20
  const scheduler = require('../scheduler.cjs');
20
21
  const { evaluateSharedTreeGuard, checkSharedTreeGuard } = scheduler;
21
22
 
@@ -210,3 +211,170 @@ test('checkSharedTreeGuard never throws when the underlying git calls reject', a
210
211
  });
211
212
  expect(result).toBeNull();
212
213
  });
214
+
215
+ // --- 2026-09-12 false-positive: an untracked path that merely became
216
+ // IGNORED during the run (a .gitignore edit) is not a revert. See the
217
+ // evaluateSharedTreeGuard doc comment for the full incident writeup. Each
218
+ // test below is written to FAIL against the pre-fix implementation (which
219
+ // computed `reverted = dirtyBefore - dirtyAfter - committed` with no
220
+ // existence/tracked-status distinction at all) and PASS after it.
221
+
222
+ test('evaluateSharedTreeGuard: an untracked path that disappears from git status but STILL EXISTS on disk is nowIgnored, not reverted', () => {
223
+ // Pre-fix behavior: this path would land in `reverted` (dirtyBefore minus
224
+ // dirtyAfter minus committed, no existence check at all) — FAILS pre-fix.
225
+ const result = evaluateSharedTreeGuard({
226
+ stashBefore: [],
227
+ stashAfter: [],
228
+ dirtyBefore: [{ code: '??', path: 'session-manager-operations/logs/errors-2026-09-12.jsonl' }],
229
+ dirtyAfter: [],
230
+ pathsCommittedDuringRun: ['.gitignore'],
231
+ existsAfter: ['session-manager-operations/logs/errors-2026-09-12.jsonl'],
232
+ });
233
+ expect(result.reverted).toEqual([]);
234
+ expect(result.nowIgnored).toEqual(['session-manager-operations/logs/errors-2026-09-12.jsonl']);
235
+ });
236
+
237
+ test('evaluateSharedTreeGuard: an untracked path that disappears from git status AND no longer exists on disk is still reverted', () => {
238
+ // Proves the fix is not a blanket "stop reporting reverted": a genuinely
239
+ // deleted untracked file (the guard's real purpose — catching a job that
240
+ // discarded uncommitted work) must still surface. If the fix had simply
241
+ // stopped reporting any untracked-and-missing path as reverted, this test
242
+ // would fail (reverted would come back empty instead of containing the
243
+ // path).
244
+ const result = evaluateSharedTreeGuard({
245
+ stashBefore: [],
246
+ stashAfter: [],
247
+ dirtyBefore: [{ code: '??', path: 'data/scratch.txt' }],
248
+ dirtyAfter: [],
249
+ pathsCommittedDuringRun: [],
250
+ existsAfter: [], // file no longer exists on disk
251
+ });
252
+ expect(result.reverted).toEqual(['data/scratch.txt']);
253
+ expect(result.nowIgnored).toEqual([]);
254
+ });
255
+
256
+ test('evaluateSharedTreeGuard: a TRACKED path clean after the run is still reverted even though it exists on disk', () => {
257
+ // A tracked file leaving the dirty set means its content was restored to
258
+ // HEAD — exactly the discard this guard exists to catch — regardless of
259
+ // whether the file is still present. Pre-fix this already worked by
260
+ // accident (no existence check existed at all); this test pins the
261
+ // behavior now that existence checks exist for the untracked branch, so a
262
+ // future change can't accidentally start trusting `existsAfter` for
263
+ // tracked paths too.
264
+ const result = evaluateSharedTreeGuard({
265
+ stashBefore: [],
266
+ stashAfter: [],
267
+ dirtyBefore: [{ code: ' M', path: 'src/config.js' }],
268
+ dirtyAfter: [],
269
+ pathsCommittedDuringRun: [],
270
+ existsAfter: ['src/config.js'], // file exists, but content was reset to HEAD
271
+ });
272
+ expect(result.reverted).toEqual(['src/config.js']);
273
+ expect(result.nowIgnored).toEqual([]);
274
+ });
275
+
276
+ test('checkSharedTreeGuard end-to-end: the real 2026-09-12 incident shape — untracked logs path ignored by a committed .gitignore change, file still on disk — must NOT verdict shared_tree_reverted', async () => {
277
+ // PRD 1181's exact shape: a bare `logs/` pattern lands in .gitignore during
278
+ // the run (committed), eleven untracked log paths vanish from `git status`,
279
+ // and the files are untouched on disk. Pre-fix, checkSharedTreeGuard's
280
+ // `reverted` would contain this path (no existsAfter computation existed),
281
+ // driving the caller's `verdict: 'shared_tree_reverted'` downgrade — FAILS
282
+ // pre-fix.
283
+ const logPath = 'session-manager-operations/logs/errors-2026-09-12.jsonl';
284
+ vi.spyOn(scheduler, 'stashList').mockResolvedValue([]);
285
+ vi.spyOn(scheduler, 'gitHead').mockResolvedValue('sha-after');
286
+ vi.spyOn(scheduler, 'pathsChangedSince').mockResolvedValue(['.gitignore']);
287
+ vi.spyOn(scheduler, 'uncommittedChanges').mockResolvedValue([]); // the log path no longer shows up at all
288
+ // checkSharedTreeGuard's existence check runs concurrently via
289
+ // fs.promises.access (fsp === fs.promises — same object) rather than
290
+ // fs.existsSync, so a large untracked baseline doesn't block the event
291
+ // loop with hundreds of synchronous stats back-to-back.
292
+ vi.spyOn(fs.promises, 'access').mockImplementation((p) => (
293
+ String(p).includes('errors-2026-09-12.jsonl') ? Promise.resolve() : Promise.reject(new Error('ENOENT'))
294
+ ));
295
+
296
+ const result = await checkSharedTreeGuard({
297
+ cwd: '/repo',
298
+ stashBaseline: [],
299
+ dirtyBaseline: [{ code: '??', path: logPath }],
300
+ headBefore: 'sha-before',
301
+ slug: 'job-1181',
302
+ });
303
+
304
+ expect(result.reverted).toBeUndefined();
305
+ expect(result.nowIgnored).toEqual([logPath]);
306
+ });
307
+
308
+ // --- landedCommit ground truth (2026-09-18, 1229-fo-03 false positive) ---
309
+ // Real temp git fixture, never the live repo. Baseline-dirty path `cfg.txt`
310
+ // goes clean with no commit covering it (the shape that makes the baseline
311
+ // diff say "reverted"); only the landedCommit ancestry decides the verdict.
312
+ const { execFileSync } = require('node:child_process');
313
+ const os = require('node:os');
314
+ const nodePath = require('node:path');
315
+
316
+ function gitIn(dir, ...args) {
317
+ return execFileSync('git', args, { cwd: dir, encoding: 'utf8' }).trim();
318
+ }
319
+
320
+ function fixtureRepo() {
321
+ const dir = fs.mkdtempSync(nodePath.join(os.tmpdir(), 'shared-tree-landed-'));
322
+ gitIn(dir, 'init', '-q');
323
+ gitIn(dir, 'config', 'user.email', 't@example.com');
324
+ gitIn(dir, 'config', 'user.name', 'T');
325
+ fs.writeFileSync(nodePath.join(dir, 'cfg.txt'), 'base\n');
326
+ gitIn(dir, 'add', '-A');
327
+ gitIn(dir, 'commit', '-q', '-m', 'base');
328
+ const base = gitIn(dir, 'rev-parse', 'HEAD');
329
+ fs.writeFileSync(nodePath.join(dir, 'cfg.txt'), 'human wip\n'); // baseline-dirty
330
+ return { dir, base };
331
+ }
332
+
333
+ function commitPath(dir, rel, msg) {
334
+ fs.writeFileSync(nodePath.join(dir, rel), `${msg}\n`);
335
+ gitIn(dir, 'add', rel);
336
+ gitIn(dir, 'commit', '-q', '-m', msg);
337
+ return gitIn(dir, 'rev-parse', 'HEAD');
338
+ }
339
+
340
+ test('checkSharedTreeGuard: landedCommit still an ancestor of HEAD plus an unrelated later commit is NOT reverted', async () => {
341
+ const { dir, base } = fixtureRepo();
342
+ const landed = commitPath(dir, 'job.txt', 'job work');
343
+ gitIn(dir, 'checkout', '--', 'cfg.txt'); // baseline-dirty path goes clean, no commit covers it
344
+ commitPath(dir, 'later.txt', 'unrelated human commit');
345
+
346
+ const result = await checkSharedTreeGuard({
347
+ cwd: dir, stashBaseline: [], dirtyBaseline: [{ code: ' M', path: 'cfg.txt' }],
348
+ headBefore: base, slug: 'job-a', landedCommit: landed,
349
+ });
350
+ expect(result).toBeNull();
351
+ });
352
+
353
+ test('checkSharedTreeGuard: landedCommit absent from history still parks as reverted', async () => {
354
+ const { dir, base } = fixtureRepo();
355
+ const landed = commitPath(dir, 'job.txt', 'job work');
356
+ gitIn(dir, 'reset', '-q', '--hard', base); // job commit discarded
357
+ commitPath(dir, 'later.txt', 'unrelated human commit');
358
+
359
+ const result = await checkSharedTreeGuard({
360
+ cwd: dir, stashBaseline: [], dirtyBaseline: [{ code: ' M', path: 'cfg.txt' }],
361
+ headBefore: base, slug: 'job-b', landedCommit: landed,
362
+ });
363
+ expect(result).toEqual({ reverted: ['cfg.txt'] });
364
+ });
365
+
366
+ test('checkSharedTreeGuard: no landedCommit at all still parks as reverted', async () => {
367
+ const { dir, base } = fixtureRepo();
368
+ gitIn(dir, 'checkout', '--', 'cfg.txt');
369
+ const result = await checkSharedTreeGuard({
370
+ cwd: dir, stashBaseline: [], dirtyBaseline: [{ code: ' M', path: 'cfg.txt' }],
371
+ headBefore: base, slug: 'job-c', landedCommit: null,
372
+ });
373
+ expect(result).toEqual({ reverted: ['cfg.txt'] });
374
+ });
375
+
376
+ test('landedCommitIsAncestorOfHead: empty/unknown sha is false, never throws', async () => {
377
+ const { dir } = fixtureRepo();
378
+ expect(await scheduler.landedCommitIsAncestorOfHead(dir, '')).toBe(false);
379
+ expect(await scheduler.landedCommitIsAncestorOfHead(dir, 'deadbeefdeadbeef')).toBe(false);
380
+ });
@@ -12,21 +12,26 @@
12
12
  * Exercises selectStarveEscalations() (pure) directly, and
13
13
  * runStarveEscalationSweep() (the side-effecting wrapper) against a real,
14
14
  * scratch audit-log.jsonl and a fake attached window — matching
15
- * queue-starvation-per-project.test.cjs's pattern of setting HOME before
16
- * require() since every state path is baked from os.homedir() at load.
15
+ * pollLoop-dispatch-on-failure.test.cjs's pattern of setting HOME before
16
+ * require() (beforeAll runs AFTER module evaluation) since every state path
17
+ * is baked from os.homedir() at load, and restoring it in afterAll.
17
18
  *
18
19
  * Run: timeout 180 npx vitest run src/main/__tests__/scheduler-starve-escalation.test.cjs
19
20
  */
20
21
 
21
22
  'use strict';
22
23
 
23
- import { test, beforeEach } from 'vitest';
24
+ import { test, beforeEach, afterAll } from 'vitest';
24
25
  const assert = require('node:assert/strict');
25
26
  const fs = require('node:fs');
26
27
  const os = require('node:os');
27
28
  const path = require('node:path');
28
29
 
29
- process.env.HOME = fs.mkdtempSync(path.join(os.tmpdir(), 'starve-escalation-test-'));
30
+ const originalHome = process.env.HOME;
31
+ const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'starve-escalation-test-'));
32
+ process.env.HOME = tmpHome;
33
+ fs.mkdirSync(path.join(tmpHome, '.claude', 'session-manager'), { recursive: true });
34
+ fs.mkdirSync(path.join(tmpHome, '.claude', 'projects'), { recursive: true });
30
35
 
31
36
  const {
32
37
  selectStarveEscalations,
@@ -34,7 +39,12 @@ const {
34
39
  STARVE_ESCALATION_MS,
35
40
  attachWindow,
36
41
  } = require('../scheduler.cjs');
37
- const { AUDIT_LOG_PATH } = require('../lib/auditLog.cjs');
42
+ const { auditLogPath } = require('../lib/auditLog.cjs');
43
+
44
+ afterAll(() => {
45
+ process.env.HOME = originalHome;
46
+ fs.rmSync(tmpHome, { recursive: true, force: true });
47
+ });
38
48
 
39
49
  const CWD_A = '/home/bilko/Projects/Bilko';
40
50
  const CWD_B = '/home/bilko/Projects/session-manager';
@@ -42,7 +52,7 @@ const CWD_B = '/home/bilko/Projects/session-manager';
42
52
  function readAuditKind(kind) {
43
53
  let lines;
44
54
  try {
45
- lines = fs.readFileSync(AUDIT_LOG_PATH, 'utf8').split('\n').filter(Boolean);
55
+ lines = fs.readFileSync(auditLogPath(), 'utf8').split('\n').filter(Boolean);
46
56
  } catch {
47
57
  return [];
48
58
  }
@@ -65,8 +75,8 @@ function fakeWindow() {
65
75
  beforeEach(() => {
66
76
  // Fresh scratch audit log per test so escalation counts never bleed across
67
77
  // tests in this file.
68
- fs.mkdirSync(path.dirname(AUDIT_LOG_PATH), { recursive: true });
69
- fs.writeFileSync(AUDIT_LOG_PATH, '');
78
+ fs.mkdirSync(path.dirname(auditLogPath()), { recursive: true });
79
+ fs.writeFileSync(auditLogPath(), '');
70
80
  attachWindow(null);
71
81
  });
72
82
 
@@ -0,0 +1,245 @@
1
+ /**
2
+ * scheduler-stranded-autofix-park.test.cjs — PRD "1218-fo-01 stranded
3
+ * auto-fix park" (2026-09-13, findings filed at session-manager-operations/
4
+ * reviews/2026-09-13-scheduler-stability-investigation.md, "post-run
5
+ * adjudication" section).
6
+ *
7
+ * spawnInvestigation restores a job's status from 'investigating' back to
8
+ * needs_review in ONE mutate() call (scheduler.cjs, source
9
+ * 'spawnInvestigation:onExit') and stamps autoFixOutcome in a SEPARATE,
10
+ * later mutate() call. A crash/restart between the two leaves a row with
11
+ * autoFixAttempted: true and autoFixOutcome permanently undefined —
12
+ * isExhaustedAutoFix wants autoFixRetries >= 1 (never incremented here) and
13
+ * isGuardParkedWithoutAutoFix wants autoFixAttempted === false, so the row
14
+ * falls through every resolving door and the periodic reverify pass just
15
+ * re-scans the same frozen transcript forever. Job 1218-fo-01 sat exactly
16
+ * like this with a landed commit while every dependent PRD waited.
17
+ *
18
+ * This file covers the new isStrandedAutoFixPark predicate and its wiring
19
+ * into isEligibleForNeedsReviewAutoResolve / selectExhaustedNeedsReviewTargets
20
+ * / applyNeedsReviewAutoResolve — the SAME bounded ladder the exhausted-
21
+ * auto-fix and guard-parked doors already use, unchanged.
22
+ *
23
+ * HOME is overridden to a tmp dir BEFORE requiring scheduler.cjs — same
24
+ * reason as scheduler-looks-done.test.cjs (appendAuditEvent writes under
25
+ * $HOME/.claude/session-manager/audit-log.jsonl).
26
+ *
27
+ * Run: timeout 120 npx vitest run src/main/__tests__/scheduler-stranded-autofix-park.test.cjs
28
+ */
29
+
30
+ 'use strict';
31
+
32
+ const assert = require('node:assert/strict');
33
+ const fs = require('node:fs');
34
+ const os = require('node:os');
35
+ const path = require('node:path');
36
+ const { execFileSync } = require('node:child_process');
37
+
38
+ const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'stranded-autofix-park-test-'));
39
+ process.env.HOME = tmpHome;
40
+
41
+ const {
42
+ isStrandedAutoFixPark,
43
+ isExhaustedAutoFix,
44
+ isGuardParkedWithoutAutoFix,
45
+ isEligibleForNeedsReviewAutoResolve,
46
+ selectExhaustedNeedsReviewTargets,
47
+ applyNeedsReviewAutoResolve,
48
+ computeLooksDone,
49
+ fixSlugFor,
50
+ NEEDS_REVIEW_RESOLVE_CAP,
51
+ } = require('../scheduler.cjs');
52
+ const { resolvePrdWriteDir } = require('../lib/prdLocations.cjs');
53
+ const { bustCwdCache } = require('../lib/queueStore.cjs');
54
+
55
+ const MIN_MS = 60_000;
56
+ const THRESHOLD_MS = 30 * MIN_MS;
57
+
58
+ // findPrdDir (via resolvePrdsDirs -> allProjectCwds) only discovers a PRD
59
+ // dir under a cwd it already knows about — same registration computeLooksDone
60
+ // needs in scheduler-looks-done.test.cjs / scheduler-guard-verdict-autoresolve.test.cjs.
61
+ function registerActiveProject(cwd, slug) {
62
+ const slugDir = path.join(tmpHome, '.claude', 'projects', slug);
63
+ fs.mkdirSync(slugDir, { recursive: true });
64
+ fs.writeFileSync(path.join(slugDir, 'transcript.jsonl'), JSON.stringify({ cwd }) + '\n');
65
+ bustCwdCache();
66
+ }
67
+
68
+ // Replays the live 1218-fo-01 tuple: needs_review, verifierVerdict
69
+ // transcript_errors, exitCode 0, a landed commit, autoFixAttempted true,
70
+ // autoFixOutcome undefined, statusHistory ending in the exact
71
+ // spawnInvestigation:onExit restore reason.
72
+ function strandedJob(overrides = {}) {
73
+ return {
74
+ slug: '1218-fo-01-move-scripts-lib-into-src-main-lib',
75
+ cwd: '/home/user/project',
76
+ status: 'needs_review',
77
+ exitCode: 0,
78
+ verifierVerdict: 'transcript_errors',
79
+ landedCommit: 'd1edf15babc1234',
80
+ autoFixAttempted: true,
81
+ statusHistory: [
82
+ { to: 'needs_review', at: new Date(Date.now() - 45 * MIN_MS).toISOString() },
83
+ { to: 'investigating', at: new Date(Date.now() - 40 * MIN_MS).toISOString(), reason: 'spawning investigation probe' },
84
+ {
85
+ to: 'needs_review',
86
+ at: new Date(Date.now() - 35 * MIN_MS).toISOString(),
87
+ reason: 'investigation probe exited — restoring prior status',
88
+ },
89
+ ],
90
+ ...overrides,
91
+ };
92
+ }
93
+
94
+ // --- isStrandedAutoFixPark ---
95
+
96
+ test('the 1218 tuple (autoFixAttempted true, autoFixOutcome undefined, no child row) is a stranded auto-fix park', () => {
97
+ const job = strandedJob();
98
+ assert.equal(isStrandedAutoFixPark(job, [job]), true);
99
+ assert.equal(isExhaustedAutoFix(job), false, 'never accumulated a retry — must not double-count as exhausted');
100
+ assert.equal(isGuardParkedWithoutAutoFix(job), false, 'autoFixAttempted is true — the guard-parked door requires false');
101
+ });
102
+
103
+ test('autoFixOutcome "error" or "no-plan" with an unspent retry is NOT stranded — selectAutoFixTargets still owns it', () => {
104
+ // Durably stamped (unlike the 1218 tuple's permanently-unset outcome), so
105
+ // its one bounded retry (autoFixRetries < 1) is still selectAutoFixTargets's
106
+ // to spend — pulling it into this ladder instead would race it away from
107
+ // that retry (see scheduler-needs-review-autoresolve.test.cjs's "a
108
+ // non-exhausted needs_review row … is left alone").
109
+ assert.equal(isStrandedAutoFixPark(strandedJob({ autoFixOutcome: 'error' }), []), false);
110
+ assert.equal(isStrandedAutoFixPark(strandedJob({ autoFixOutcome: 'no-plan' }), []), false);
111
+ });
112
+
113
+ test('autoFixOutcome "plan" is never a stranded park — that is isPlanUnqueued/isFixPlanDead territory', () => {
114
+ assert.equal(isStrandedAutoFixPark(strandedJob({ autoFixOutcome: 'plan' }), []), false);
115
+ });
116
+
117
+ test('a row that never attempted auto-fix at all is not a stranded park', () => {
118
+ assert.equal(isStrandedAutoFixPark(strandedJob({ autoFixAttempted: undefined }), []), false);
119
+ });
120
+
121
+ test('a row not in needs_review is never a stranded park, whatever its autoFix* fields say', () => {
122
+ assert.equal(isStrandedAutoFixPark(strandedJob({ status: 'failed' }), []), false);
123
+ });
124
+
125
+ test('a live fix-plan child (running) means there IS something in flight — not stranded', () => {
126
+ const job = strandedJob();
127
+ const child = { slug: fixSlugFor(job), status: 'running' };
128
+ assert.equal(isStrandedAutoFixPark(job, [job, child]), false);
129
+ });
130
+
131
+ test('a row with a live investigating status (the fix-plan child mid-probe) is NOT selected', () => {
132
+ const job = strandedJob();
133
+ const child = { slug: fixSlugFor(job), status: 'investigating' };
134
+ assert.equal(isStrandedAutoFixPark(job, [job, child]), false);
135
+ assert.equal(selectExhaustedNeedsReviewTargets([job, child], Date.now(), THRESHOLD_MS).length, 0);
136
+ });
137
+
138
+ test('a queued (pending) fix-plan child also blocks the stranded-park door', () => {
139
+ const job = strandedJob();
140
+ const child = { slug: fixSlugFor(job), status: 'pending' };
141
+ assert.equal(isStrandedAutoFixPark(job, [job, child]), false);
142
+ });
143
+
144
+ test('a DEAD fix-plan child (needs_review/failed/quarantined/skipped) does not block it — nothing left to wait on', () => {
145
+ const job = strandedJob();
146
+ for (const deadStatus of ['needs_review', 'failed', 'quarantined', 'skipped']) {
147
+ const child = { slug: fixSlugFor(job), status: deadStatus };
148
+ assert.equal(isStrandedAutoFixPark(job, [job, child]), true, `child status ${deadStatus} must not block`);
149
+ }
150
+ });
151
+
152
+ test('a COMPLETED fix-plan child does not block it either', () => {
153
+ const job = strandedJob();
154
+ const child = { slug: fixSlugFor(job), status: 'completed' };
155
+ assert.equal(isStrandedAutoFixPark(job, [job, child]), true);
156
+ });
157
+
158
+ test('isEligibleForNeedsReviewAutoResolve admits the stranded-park door alongside the other two', () => {
159
+ const job = strandedJob();
160
+ assert.equal(isEligibleForNeedsReviewAutoResolve(job, [job]), true);
161
+ });
162
+
163
+ // --- selectExhaustedNeedsReviewTargets: age-gated selection ---
164
+
165
+ test('a fresh stranded park (under threshold) is not selected', () => {
166
+ const job = strandedJob({
167
+ statusHistory: [
168
+ { to: 'needs_review', at: new Date(Date.now() - 2 * MIN_MS).toISOString(), reason: 'investigation probe exited — restoring prior status' },
169
+ ],
170
+ });
171
+ assert.equal(selectExhaustedNeedsReviewTargets([job], Date.now(), THRESHOLD_MS).length, 0);
172
+ });
173
+
174
+ test('a stranded park past NEEDS_REVIEW_RESOLVE_MS, under the cap, IS selected', () => {
175
+ const job = strandedJob();
176
+ const found = selectExhaustedNeedsReviewTargets([job], Date.now(), THRESHOLD_MS);
177
+ assert.equal(found.length, 1);
178
+ assert.equal(found[0].slug, job.slug);
179
+ });
180
+
181
+ // --- applyNeedsReviewAutoResolve: same bounded ladder, entered via the new door ---
182
+
183
+ test('computeLooksDone evidence completes the stranded row via the existing looksDone branch', async () => {
184
+ const projectCwd = fs.mkdtempSync(path.join(tmpHome, 'proj-stranded-'));
185
+ fs.mkdirSync(projectCwd, { recursive: true });
186
+ const git = (args) => execFileSync('git', args, { cwd: projectCwd, encoding: 'utf8' });
187
+ git(['init', '-q']);
188
+ git(['config', 'user.email', 'test@example.com']);
189
+ git(['config', 'user.name', 'Test']);
190
+ fs.writeFileSync(path.join(projectCwd, 'README.md'), 'hello\n');
191
+ git(['add', '-A']);
192
+ git(['commit', '-q', '-m', 'initial']);
193
+ registerActiveProject(projectCwd, path.basename(projectCwd));
194
+
195
+ const dir = resolvePrdWriteDir(projectCwd);
196
+ fs.mkdirSync(dir, { recursive: true });
197
+ const slug = '1218-fo-01-move-scripts-lib-into-src-main-lib';
198
+ fs.writeFileSync(
199
+ path.join(dir, `${slug}.md`),
200
+ `---\ntitle: Test PRD\ncwd: ${projectCwd}\nestimateMinutes: 30\n---\n# Implementation notes\nEdit \`src/thing.js\`.\n`,
201
+ );
202
+
203
+ const startedAt = new Date(Date.now() - 60 * MIN_MS).toISOString();
204
+ await new Promise((r) => setTimeout(r, 1100)); // git --since has 1s resolution
205
+ fs.mkdirSync(path.join(projectCwd, 'src'), { recursive: true });
206
+ fs.writeFileSync(path.join(projectCwd, 'src', 'thing.js'), 'module.exports = {};\n');
207
+ git(['add', '-A']);
208
+ git(['commit', '-q', '-m', `the actual fix for ${slug} landed`]);
209
+
210
+ const job = strandedJob({ slug, cwd: projectCwd, startedAt });
211
+ assert.equal(isStrandedAutoFixPark(job, [job]), true);
212
+
213
+ const looksDone = await computeLooksDone(job);
214
+ assert.ok(looksDone, 'expected computeLooksDone to find the attributable commit');
215
+ job.looksDone = looksDone;
216
+
217
+ const outcome = applyNeedsReviewAutoResolve(job, [job]);
218
+ assert.equal(outcome, 'completed');
219
+ assert.equal(job.status, 'completed');
220
+ });
221
+
222
+ test('with no completion evidence yet, the stranded row gets one bounded requeue, same cap/logic as the other doors', () => {
223
+ const job = strandedJob({ exhaustedResolveAttempts: 0 });
224
+ assert.equal(job.looksDone, undefined);
225
+ const outcome = applyNeedsReviewAutoResolve(job, [job]);
226
+ assert.equal(outcome, 'requeued');
227
+ assert.equal(job.status, 'pending');
228
+ assert.equal(job.exhaustedResolveAttempts, 1);
229
+ });
230
+
231
+ test('once the cap is spent, the stranded row auto-skips like every other door', () => {
232
+ const job = strandedJob({ exhaustedResolveAttempts: NEEDS_REVIEW_RESOLVE_CAP });
233
+ const outcome = applyNeedsReviewAutoResolve(job, [job]);
234
+ assert.equal(outcome, 'skipped');
235
+ assert.equal(job.status, 'skipped');
236
+ assert.equal(job.needsReviewAutoResolvedSkip, true);
237
+ });
238
+
239
+ test('a live fix-plan child also blocks applyNeedsReviewAutoResolve, not just selection', () => {
240
+ const job = strandedJob();
241
+ const child = { slug: fixSlugFor(job), status: 'running' };
242
+ const outcome = applyNeedsReviewAutoResolve(job, [job, child]);
243
+ assert.equal(outcome, null, 'nothing to resolve while the fix-plan child is still live');
244
+ assert.equal(job.status, 'needs_review', 'must stay parked while the child is in flight');
245
+ });
@@ -0,0 +1,81 @@
1
+ /**
2
+ * scheduler-supervisor-record.test.cjs — executeJob writes
3
+ * <runDir>/<slug>.supervisor.json synchronously at the `spawned pid=` point,
4
+ * i.e. before the child's first output and before onPid's mutate runs.
5
+ *
6
+ * Run: timeout 120 npx vitest run src/main/__tests__/scheduler-supervisor-record.test.cjs
7
+ */
8
+ 'use strict';
9
+
10
+ import { test, expect, beforeAll, afterAll, afterEach } from 'vitest';
11
+ const fs = require('node:fs');
12
+ const os = require('node:os');
13
+ const path = require('node:path');
14
+
15
+ let tmpHome;
16
+ let originalHome;
17
+ let executeJob;
18
+
19
+ beforeAll(() => {
20
+ originalHome = process.env.HOME;
21
+ tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-supervisor-rec-'));
22
+ process.env.HOME = tmpHome;
23
+ ({ executeJob } = require('../scheduler.cjs'));
24
+ });
25
+ afterAll(() => {
26
+ process.env.HOME = originalHome;
27
+ fs.rmSync(tmpHome, { recursive: true, force: true });
28
+ });
29
+ afterEach(() => { delete process.env.SM_CLAUDE_BIN; });
30
+
31
+ function writeSlowStub() {
32
+ const stubPath = path.join(os.tmpdir(), `sm-sup-stub-${process.pid}-${Math.floor(Math.random() * 1e9)}.cjs`);
33
+ const body = `
34
+ setTimeout(() => {
35
+ process.stdout.write(JSON.stringify({ type: 'result', subtype: 'success', result: 'ok' }) + '\\n');
36
+ process.exit(0);
37
+ }, 500);
38
+ `;
39
+ fs.writeFileSync(stubPath, `#!${process.execPath}\n${body}\n`, { mode: 0o755 });
40
+ return stubPath;
41
+ }
42
+
43
+ test('the supervisor record exists by the time onPid fires, before the child prints anything', async () => {
44
+ const mainCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-sup-main-'));
45
+ const worktreeCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-sup-wt-'));
46
+ fs.mkdirSync(path.join(tmpHome, '.claude'), { recursive: true });
47
+ const runDir = fs.mkdtempSync(path.join(tmpHome, '.claude', 'sm-sup-run-'));
48
+ try {
49
+ const slug = `sup-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
50
+ const prdsDir = path.join(mainCwd, 'session-manager-operations', 'scheduler', 'prds');
51
+ fs.mkdirSync(prdsDir, { recursive: true });
52
+ fs.writeFileSync(path.join(prdsDir, `${slug}.md`), 'body', 'utf8');
53
+ process.env.SM_CLAUDE_BIN = writeSlowStub();
54
+
55
+ const seen = {};
56
+ const result = await executeJob({ slug, cwd: mainCwd }, runDir, mainCwd, (pid) => {
57
+ const file = path.join(runDir, `${slug}.supervisor.json`);
58
+ seen.exists = fs.existsSync(file);
59
+ seen.record = seen.exists ? JSON.parse(fs.readFileSync(file, 'utf8')) : null;
60
+ seen.pid = pid;
61
+ seen.logHasOutput = fs.readFileSync(path.join(runDir, `${slug}.log`), 'utf8').includes('"type":"result"');
62
+ seen.metaExists = fs.existsSync(path.join(runDir, `${slug}.meta.json`));
63
+ return Promise.resolve();
64
+ }, worktreeCwd);
65
+
66
+ expect(result.exitCode).toBe(0);
67
+ expect(seen.exists).toBe(true);
68
+ expect(seen.logHasOutput).toBe(false);
69
+ expect(seen.metaExists).toBe(false);
70
+ expect(seen.record).toMatchObject({
71
+ kind: 'job', slug, cwd: mainCwd, runId: path.basename(runDir), pid: seen.pid, pgid: seen.pid,
72
+ execCwd: worktreeCwd, worktreeDir: worktreeCwd, worktreeBranch: `sm-job/${slug}`, schedulerPid: process.pid,
73
+ });
74
+ expect(typeof seen.record.startedAt).toBe('number');
75
+ expect(seen.record.idleKillMs).toBeGreaterThan(0);
76
+ } finally {
77
+ fs.rmSync(mainCwd, { recursive: true, force: true });
78
+ fs.rmSync(worktreeCwd, { recursive: true, force: true });
79
+ fs.rmSync(runDir, { recursive: true, force: true });
80
+ }
81
+ });
@@ -8,12 +8,12 @@
8
8
  * subsequent tickQueue() call short-circuited on the stale flag forever,
9
9
  * even though queue.json's `paused` field was correctly null.
10
10
  *
11
- * Run: timeout 120 node --test src/main/__tests__/scheduler-tick-cancel-token.test.cjs
11
+ * Run: timeout 300 npx vitest run src/main/__tests__/scheduler-tick-cancel-token.test.cjs
12
12
  */
13
13
 
14
14
  'use strict';
15
15
 
16
- const { test } = require('node:test');
16
+ import { test } from 'vitest';
17
17
  const assert = require('node:assert/strict');
18
18
  const { applyPauseCleared, pickNextBatch } = require('../scheduler.cjs');
19
19