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
@@ -6,7 +6,7 @@
6
6
  */
7
7
  'use strict';
8
8
 
9
- import { test, expect } from 'vitest';
9
+ import { test, expect, vi, beforeEach, afterEach } from 'vitest';
10
10
  const fs = require('node:fs');
11
11
  const crypto = require('node:crypto');
12
12
  const {
@@ -17,14 +17,15 @@ const {
17
17
  _resetRefusedTransitionCountForTests,
18
18
  } = require('../lib/scheduleJobTransitions.cjs');
19
19
  const auditLog = require('../lib/auditLog.cjs');
20
+ const queueHistory = require('../lib/queueHistory.cjs');
20
21
 
21
22
  function uniqueSlug(label) {
22
23
  return `test-${label}-${crypto.randomUUID().slice(0, 8)}`;
23
24
  }
24
25
 
25
26
  function readAuditRecordsForSlug(slug) {
26
- if (!fs.existsSync(auditLog.AUDIT_LOG_PATH)) return [];
27
- return fs.readFileSync(auditLog.AUDIT_LOG_PATH, 'utf8')
27
+ if (!fs.existsSync(auditLog.auditLogPath())) return [];
28
+ return fs.readFileSync(auditLog.auditLogPath(), 'utf8')
28
29
  .split('\n')
29
30
  .filter(Boolean)
30
31
  .map((line) => {
@@ -175,3 +176,102 @@ test('_resetRefusedTransitionCountForTests resets the counter', () => {
175
176
  _resetRefusedTransitionCountForTests();
176
177
  expect(getRefusedTransitionCount()).toBe(0);
177
178
  });
179
+
180
+ // ---------- needs_review ledger (PRD: needs_review durability) ----------
181
+ //
182
+ // transitionJob is the ONE place every needs_review entry/resolution is
183
+ // decided (this file's own header comment) — these tests exercise that
184
+ // hook directly rather than going through scheduler.cjs's many call sites.
185
+ // queueHistory.appendHistory is mocked so these tests never touch disk or
186
+ // race its own fire-and-forget promise; queueHistory.test.cjs already
187
+ // covers appendHistory's real I/O.
188
+
189
+ let appendHistorySpy;
190
+
191
+ beforeEach(() => {
192
+ appendHistorySpy = vi.spyOn(queueHistory, 'appendHistory').mockResolvedValue({ appended: 1 });
193
+ });
194
+
195
+ afterEach(() => {
196
+ appendHistorySpy.mockRestore();
197
+ });
198
+
199
+ test('needs_review entry: transitioning INTO needs_review appends a kind:needs_review_entry ledger line', () => {
200
+ const slug = uniqueSlug('nr-entry');
201
+ const job = { slug, cwd: '/tmp/proj', status: 'running', runId: 'run-abc', verifierVerdict: 'uncommitted_changes' };
202
+
203
+ transitionJob(job, 'needs_review', { reason: 'finish protocol incomplete', source: 'spawnJob:finalize' });
204
+
205
+ expect(appendHistorySpy).toHaveBeenCalledTimes(1);
206
+ const [lines] = appendHistorySpy.mock.calls[0];
207
+ expect(lines).toHaveLength(1);
208
+ expect(lines[0]).toMatchObject({
209
+ kind: 'needs_review_entry',
210
+ slug,
211
+ cwd: '/tmp/proj',
212
+ runId: 'run-abc',
213
+ reason: 'uncommitted_changes',
214
+ source: 'spawnJob:finalize',
215
+ });
216
+ expect(typeof lines[0].at).toBe('string');
217
+ // Episode bookkeeping stamped on the job so a later resolution can
218
+ // reference this SAME runId even if job.runId is reassigned meanwhile.
219
+ expect(job.needsReviewEntryRunId).toBe('run-abc');
220
+ expect(job.needsReviewEnteredAt).toBe(lines[0].at);
221
+ });
222
+
223
+ test('needs_review resolution: transitioning OUT of needs_review appends a paired line referencing the ENTRY runId, with dwellMs', () => {
224
+ const slug = uniqueSlug('nr-resolve');
225
+ const job = { slug, cwd: '/tmp/proj', status: 'running', runId: 'run-1' };
226
+
227
+ transitionJob(job, 'needs_review', { reason: 'park', source: 'spawnJob:finalize' });
228
+ const enteredAt = job.needsReviewEnteredAt;
229
+
230
+ // A resume-recovery re-dispatch (or any later attempt) mints a FRESH runId
231
+ // before this episode resolves — the resolution must still reference the
232
+ // ENTRY's runId, not whatever the job carries now.
233
+ job.runId = 'run-2';
234
+ transitionJob(job, 'completed', { reason: 'boot reverify: stale needs_review healed', source: 'reverifyNeedsReview:heal' });
235
+
236
+ expect(appendHistorySpy).toHaveBeenCalledTimes(2);
237
+ const [resolutionLines] = appendHistorySpy.mock.calls[1];
238
+ expect(resolutionLines).toHaveLength(1);
239
+ const resolution = resolutionLines[0];
240
+ expect(resolution.kind).toBe('needs_review_resolution');
241
+ expect(resolution.runId).toBe('run-1'); // the ENTRY's runId
242
+ expect(resolution.resolvedTo).toBe('completed');
243
+ expect(resolution.ladderRung).toBe('reverify');
244
+ expect(typeof resolution.dwellMs).toBe('number');
245
+ expect(resolution.dwellMs).toBeGreaterThanOrEqual(0);
246
+ expect(Date.parse(enteredAt) + resolution.dwellMs).toBe(Date.parse(resolution.at));
247
+
248
+ // Episode bookkeeping cleared once resolved.
249
+ expect(job.needsReviewEntryRunId).toBeUndefined();
250
+ expect(job.needsReviewEnteredAt).toBeUndefined();
251
+ });
252
+
253
+ test('a slug that parks twice produces two independent entry/resolution pairs, not one merged episode', () => {
254
+ const { reduceNeedsReviewLedger } = require('../lib/needsReviewLedger.cjs');
255
+ const slug = uniqueSlug('nr-twice');
256
+ const job = { slug, cwd: '/tmp/proj', status: 'running', runId: 'run-A' };
257
+
258
+ transitionJob(job, 'needs_review', { reason: 'park 1', source: 'spawnJob:finalize' });
259
+ transitionJob(job, 'pending', { reason: 'manual reset', source: 'ipc:schedule:reset-job' });
260
+ transitionJob(job, 'running', { reason: 'redispatched', source: 'spawnJob:dispatch' });
261
+ job.runId = 'run-B';
262
+ transitionJob(job, 'needs_review', { reason: 'park 2', source: 'spawnJob:finalize' });
263
+ transitionJob(job, 'completed', { reason: 'healed', source: 'reverifyNeedsReview:heal' });
264
+
265
+ const ledgerLines = appendHistorySpy.mock.calls.map((call) => call[0][0]);
266
+ const entries = ledgerLines.filter((l) => l.kind === 'needs_review_entry');
267
+ const resolutions = ledgerLines.filter((l) => l.kind === 'needs_review_resolution');
268
+ expect(entries).toHaveLength(2);
269
+ expect(resolutions).toHaveLength(2);
270
+ expect(entries.map((l) => l.runId).sort()).toEqual(['run-A', 'run-B']);
271
+ expect(resolutions.map((l) => l.runId).sort()).toEqual(['run-A', 'run-B']);
272
+
273
+ const rollup = reduceNeedsReviewLedger(ledgerLines);
274
+ expect(rollup.unresolvedCount).toBe(0);
275
+ expect(rollup.byLadderRung['manual-reset']).toBe(1);
276
+ expect(rollup.byLadderRung['reverify']).toBe(1);
277
+ });
@@ -0,0 +1,143 @@
1
+ /**
2
+ * scheduler-adopted-run-supervision.test.cjs — an executor the boot spared
3
+ * (`adoptedAtBoot`) is supervised like a freshly spawned one: budget, idle-tail
4
+ * and deadman re-armed from the durable record, quietMachine lease re-acquired,
5
+ * and a budget kill parks needs_review/budget_exceeded via classifyBudgetKill.
6
+ *
7
+ * Run: timeout 120 npx vitest run src/main/__tests__/scheduler-adopted-run-supervision.test.cjs
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ import { test, expect, vi, beforeEach, afterEach } from 'vitest';
13
+ const { superviseAdoptedRuns, evaluateAdoptedRun } = require('../lib/adoptedRunSupervisor.cjs');
14
+ const quietMachineLease = require('../lib/quietMachineLease.cjs');
15
+ const { classifyBudgetKill } = require('../scheduler.cjs');
16
+
17
+ const MIN = 60_000;
18
+ const T0 = Date.parse('2026-09-18T12:00:00Z');
19
+
20
+ beforeEach(() => {
21
+ vi.useFakeTimers();
22
+ vi.setSystemTime(T0);
23
+ quietMachineLease.__resetForTests();
24
+ });
25
+ afterEach(() => {
26
+ vi.useRealTimers();
27
+ quietMachineLease.__resetForTests();
28
+ });
29
+
30
+ function harness({ rows, records, logMtimeMs = () => Date.now(), alive = () => true }) {
31
+ const kills = [];
32
+ const stamps = [];
33
+ const supervised = [];
34
+ const registry = new Map();
35
+ const ctx = {
36
+ registry,
37
+ runDir: (j) => `/runs/${j.runId}`,
38
+ readRecord: (_dir, slug) => records[slug] ?? null,
39
+ lease: quietMachineLease,
40
+ markSupervised: async (j) => { supervised.push(j.slug); },
41
+ makeDeps: (j, record) => ({
42
+ logPath: `/runs/${j.runId}/${j.slug}.log`,
43
+ statLogMtimeMs: () => logMtimeMs(j.slug),
44
+ pidAlive: alive,
45
+ identityOf: () => record.identity,
46
+ isDifferentProcess: () => false,
47
+ killGroup: (pgid, signal) => { kills.push({ slug: j.slug, pgid, signal }); },
48
+ stampKill: async (kind, reason) => { stamps.push({ slug: j.slug, kind, reason }); },
49
+ checkIntervalMs: MIN,
50
+ sigkillAfterMs: 30_000,
51
+ defaultMaxDurationMs: 4 * 60 * MIN,
52
+ }),
53
+ };
54
+ return { ctx, kills, stamps, supervised, registry, run: () => superviseAdoptedRuns(rows, ctx) };
55
+ }
56
+
57
+ const row = (slug, extra = {}) => ({ slug, status: 'running', runId: 'r1', adoptedAtBoot: new Date(T0).toISOString(), ...extra });
58
+ const record = (slug, extra = {}) => ({
59
+ slug, pid: 4242, pgid: 4242, identity: { startTicks: 1 }, startedAt: T0, budgetMs: 45 * MIN, maxDurationMs: null, idleKillMs: 20 * MIN, ...extra,
60
+ });
61
+
62
+ test('an adopted run past its budget is killed and its budget kill classifies needs_review/budget_exceeded', async () => {
63
+ const h = harness({ rows: [row('over')], records: { over: record('over', { startedAt: T0 - 50 * MIN }) } });
64
+ expect(await h.run()).toEqual(['over']);
65
+ await vi.advanceTimersByTimeAsync(0);
66
+ expect(h.stamps).toHaveLength(1);
67
+ expect(h.stamps[0].kind).toBe('budget');
68
+ expect(h.kills).toEqual([{ slug: 'over', pgid: 4242, signal: 'SIGTERM' }]);
69
+ // SIGKILL follows if the group survives SIGTERM.
70
+ await vi.advanceTimersByTimeAsync(30_000);
71
+ expect(h.kills.map((k) => k.signal)).toEqual(['SIGTERM', 'SIGKILL']);
72
+ // Same classifier as a native budget kill.
73
+ const outcome = classifyBudgetKill({ killedByWatchdog: h.stamps[0].kind, budgetKillReason: h.stamps[0].reason, exitCode: null }, null);
74
+ expect(outcome.status).toBe('needs_review');
75
+ expect(outcome.reason).toMatch(/wall-clock budget exceeded/);
76
+ });
77
+
78
+ test('a run within budget is untouched until the interval crosses the budget', async () => {
79
+ const h = harness({ rows: [row('ok')], records: { ok: record('ok', { startedAt: T0 - 10 * MIN }) } });
80
+ await h.run();
81
+ await vi.advanceTimersByTimeAsync(5 * MIN);
82
+ expect(h.kills).toEqual([]);
83
+ expect(h.stamps).toEqual([]);
84
+ // 10m + 36m elapsed > 45m budget: the re-armed interval now fires.
85
+ await vi.advanceTimersByTimeAsync(31 * MIN);
86
+ expect(h.stamps.map((s) => s.kind)).toEqual(['budget']);
87
+ expect(h.kills[0].signal).toBe('SIGTERM');
88
+ });
89
+
90
+ test('idle-tail: a stalled log past idleKillMs is SIGTERMed; deadman SIGKILLs at maxDurationMs', async () => {
91
+ const idle = harness({
92
+ rows: [row('idle')], records: { idle: record('idle', { startedAt: T0 - 5 * MIN }) },
93
+ logMtimeMs: () => T0 - 25 * MIN,
94
+ });
95
+ await idle.run();
96
+ await vi.advanceTimersByTimeAsync(0);
97
+ expect(idle.stamps.map((s) => s.kind)).toEqual(['idle-tail']);
98
+ expect(idle.kills[0].signal).toBe('SIGTERM');
99
+
100
+ const dead = harness({
101
+ rows: [row('dm')], records: { dm: record('dm', { startedAt: T0 - 5 * MIN, budgetMs: null, maxDurationMs: 4 * 60 * MIN }) },
102
+ });
103
+ await dead.run();
104
+ await vi.advanceTimersByTimeAsync(4 * 60 * MIN);
105
+ expect(dead.stamps.map((s) => s.kind)).toEqual(['deadman']);
106
+ expect(dead.kills.map((k) => k.signal)).toEqual(['SIGKILL']);
107
+ });
108
+
109
+ test('a null record.maxDurationMs falls back to the native 4h deadman', () => {
110
+ const rec = record('x', { budgetMs: null, startedAt: T0 - 5 * 60 * MIN });
111
+ expect(evaluateAdoptedRun(rec, { now: T0, logMtimeMs: T0, defaultMaxDurationMs: 4 * 60 * MIN })?.kind).toBe('deadman');
112
+ });
113
+
114
+ test('the quietMachine lease is re-acquired, supervisedAt stamped once, and an exempt (null budget) run is not budget-killed', async () => {
115
+ const h = harness({
116
+ rows: [row('quiet', { quietMachine: true })],
117
+ records: { quiet: record('quiet', { budgetMs: null, startedAt: T0 - 90 * MIN }) },
118
+ });
119
+ expect(await h.run()).toEqual(['quiet']);
120
+ expect(quietMachineLease.holder()).toBe('quiet');
121
+ expect(h.supervised).toEqual(['quiet']);
122
+ await vi.advanceTimersByTimeAsync(10 * MIN);
123
+ expect(h.kills).toEqual([]);
124
+ // A second dispatch-loop pass does not re-arm or re-stamp.
125
+ expect(await h.run()).toEqual([]);
126
+ expect(h.supervised).toEqual(['quiet']);
127
+ });
128
+
129
+ test('a pid that is gone is never signalled; rows that stop running drop their supervisor', async () => {
130
+ const h = harness({
131
+ rows: [row('gone')], records: { gone: record('gone', { startedAt: T0 - 90 * MIN }) }, alive: () => false,
132
+ });
133
+ await h.run();
134
+ await vi.advanceTimersByTimeAsync(2 * MIN);
135
+ expect(h.kills).toEqual([]);
136
+ expect(h.stamps).toEqual([]);
137
+
138
+ const live = harness({ rows: [row('fin')], records: { fin: record('fin') } });
139
+ await live.run();
140
+ expect(live.registry.size).toBe(1);
141
+ await superviseAdoptedRuns([{ slug: 'fin', status: 'completed', runId: 'r1' }], live.ctx);
142
+ expect(live.registry.size).toBe(0);
143
+ });
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * scheduler-autofix-select.test.cjs — unit tests for selectAutoFixTargets.
3
3
  *
4
- * Run: timeout 120 node --test src/main/__tests__/scheduler-autofix-select.test.cjs
4
+ * Run: timeout 300 npx vitest run src/main/__tests__/scheduler-autofix-select.test.cjs
5
5
  */
6
6
 
7
7
  'use strict';
8
8
 
9
- const { test } = require('node:test');
9
+ import { test } from 'vitest';
10
10
  const assert = require('node:assert/strict');
11
11
  const {
12
12
  selectAutoFixTargets, isUnresolvableNeedsReview, isRescanCandidate,
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * scheduler-autopromote.test.cjs — unit tests for isPromotableOriginal.
3
3
  *
4
- * Run: timeout 120 node --test src/main/__tests__/scheduler-autopromote.test.cjs
4
+ * Run: timeout 300 npx vitest run src/main/__tests__/scheduler-autopromote.test.cjs
5
5
  */
6
6
 
7
7
  'use strict';
8
8
 
9
- const { test } = require('node:test');
9
+ import { test } from 'vitest';
10
10
  const assert = require('node:assert/strict');
11
11
  const { isPromotableOriginal, healTargetForFix } = require('../scheduler.cjs');
12
12
 
@@ -26,6 +26,7 @@ import { test, expect, beforeAll, afterAll, afterEach } from 'vitest';
26
26
  const fs = require('node:fs');
27
27
  const os = require('node:os');
28
28
  const path = require('node:path');
29
+ const claudeStub = require('../../../tests/helpers/claudeStub.cjs');
29
30
 
30
31
  let tmpHome;
31
32
  let originalHome;
@@ -53,8 +54,7 @@ afterEach(() => {
53
54
  // Stub `claude` binary: dumps the two env vars it received into a marker
54
55
  // file, then emits a stream-json success result and exits 0.
55
56
  function writeClaudeStub() {
56
- const stubPath = path.join(os.tmpdir(), `sm-claude-stub-env-${process.pid}-${Math.floor(Math.random() * 1e9)}.cjs`);
57
- const body = `
57
+ return claudeStub.writeClaudeStub({ body: `
58
58
  const fs = require('fs');
59
59
  const path = require('path');
60
60
  fs.writeFileSync(path.join(process.cwd(), 'env.marker'), JSON.stringify({
@@ -63,9 +63,7 @@ function writeClaudeStub() {
63
63
  }), 'utf8');
64
64
  process.stdout.write(JSON.stringify({ type: 'result', subtype: 'success', result: 'ok' }) + '\\n');
65
65
  process.exit(0);
66
- `;
67
- fs.writeFileSync(stubPath, `#!${process.execPath}\n${body}\n`, { mode: 0o755 });
68
- return stubPath;
66
+ ` });
69
67
  }
70
68
 
71
69
  test('BASH_MAX_TIMEOUT_MS stays strictly below IDLE_OUTPUT_KILL_MS with real margin', () => {
@@ -2,46 +2,108 @@
2
2
  * scheduler-boot-orphans.test.cjs — boot-time reconciliation of 'running' jobs
3
3
  * left behind by an app crash/restart (PRD 686: consolidated in from the
4
4
  * external watchdog's reconcileQueueOffline(), which is now deleted from
5
- * scripts/lib/watchdogHelpers.cjs).
5
+ * src/main/lib/watchdogHelpers.cjs).
6
6
  *
7
- * Covers the two behaviors most likely to drift in the move:
8
- * - a still-alive orphaned pid must be DEFERRED, never classified from its
9
- * (possibly still-being-written) log in the same pass (partitionBootOrphans)
7
+ * Covers:
8
+ * - a proven-alive row is ADOPTED (left running, never signalled) and only
9
+ * proven-dead / exited rows are finalized (partitionBootOrphans)
10
10
  * - the ORPHAN_REQUEUE_CAP re-queue/exhaustion boundary (applyOrphanOutcome)
11
11
  *
12
- * Run: timeout 120 node --test src/main/__tests__/scheduler-boot-orphans.test.cjs
12
+ * Run: timeout 120 npx vitest run src/main/__tests__/scheduler-boot-orphans.test.cjs
13
13
  */
14
14
 
15
15
  'use strict';
16
16
 
17
- const { test } = require('node:test');
17
+ const { spawn } = require('node:child_process');
18
18
  const assert = require('node:assert/strict');
19
19
  const fs = require('node:fs');
20
20
  const os = require('node:os');
21
21
  const path = require('node:path');
22
22
 
23
+ const {
24
+ writeSupervisorRecord,
25
+ } = require('../lib/jobSupervisorRecord.cjs');
23
26
  const {
24
27
  partitionBootOrphans,
25
28
  applyOrphanOutcome,
26
- feedbackSweepDue,
27
- FEEDBACK_SWEEP_TICK_INTERVAL,
28
- sweepFeedback,
29
29
  } = require('../scheduler.cjs');
30
30
  const { ORPHAN_REQUEUE_CAP } = require('../lib/reaperHelpers.cjs');
31
31
 
32
- test('partitionBootOrphans defers a running job whose pid is still alive', () => {
32
+ function spawnStub() {
33
+ return spawn(process.execPath, ['-e', 'setTimeout(() => {}, 30000)'], { stdio: 'ignore' });
34
+ }
35
+
36
+ function withRunsDir(fn) {
37
+ const runsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sched-boot-runs-'));
38
+ try { return fn(runsDir); } finally { fs.rmSync(runsDir, { recursive: true, force: true }); }
39
+ }
40
+
41
+ test('a running row with a live pid survives boot untouched — adopted, no kill issued', () => {
42
+ const child = spawnStub();
43
+ const killSpy = vi.spyOn(process, 'kill');
44
+ try {
45
+ const jobs = [{ slug: 'alive-orphan', status: 'running', runtime: { pid: child.pid } }];
46
+ const { immediate, adopted } = partitionBootOrphans(jobs, { pidAlive: (pid) => pid === child.pid, runsDir: os.tmpdir() });
47
+ assert.deepEqual(adopted, ['alive-orphan']);
48
+ assert.deepEqual(immediate, []);
49
+ assert.equal(killSpy.mock.calls.length, 0, 'boot classification must never signal an executor');
50
+ assert.equal(jobs[0].status, 'running', 'the row must not transition');
51
+ } finally {
52
+ killSpy.mockRestore();
53
+ child.kill();
54
+ }
55
+ });
56
+
57
+ test('a dead-pid row and a pidless row with no liveness evidence are finalized', () => {
33
58
  const jobs = [
34
- { slug: 'alive-orphan', status: 'running', runtime: { pid: 111 } },
35
59
  { slug: 'dead-orphan', status: 'running', runtime: { pid: 222 } },
36
60
  { slug: 'no-pid-orphan', status: 'running', runtime: {} },
37
61
  { slug: 'not-running', status: 'pending' },
38
62
  ];
39
- const isAlive = (pid) => pid === 111;
63
+ const { immediate, adopted } = partitionBootOrphans(jobs, { pidAlive: () => false, runsDir: os.tmpdir() });
64
+ assert.deepEqual(immediate.sort(), ['dead-orphan', 'no-pid-orphan']);
65
+ assert.deepEqual(adopted, []);
66
+ });
40
67
 
41
- const { immediate, deferred } = partitionBootOrphans(jobs, isAlive);
68
+ test('a pidless row is adopted on any one of the reaper liveness signals', () => {
69
+ const job = () => ({ slug: 'p', status: 'running', runtime: {} });
70
+ const base = { pidAlive: (pid) => pid === 77, runsDir: os.tmpdir(), now: 1_000_000 };
71
+ assert.deepEqual(partitionBootOrphans([job()], { ...base, getLogMtimeMs: () => 999_000, logFreshWindowMs: 5_000 }).adopted, ['p'], 'fresh log');
72
+ assert.deepEqual(partitionBootOrphans([job()], { ...base, getLogPid: () => 77 }).adopted, ['p'], 'log pid alive');
73
+ assert.deepEqual(partitionBootOrphans([job()], { ...base, findLiveProcess: () => 88 }).adopted, ['p'], '/proc scan');
74
+ assert.deepEqual(partitionBootOrphans([job()], { ...base, getLogMtimeMs: () => 1_000, logFreshWindowMs: 5_000 }).immediate, ['p'], 'stale log');
75
+ });
42
76
 
43
- assert.deepEqual(deferred, ['alive-orphan']);
44
- assert.deepEqual(immediate.sort(), ['dead-orphan', 'no-pid-orphan']);
77
+ test('a row whose supervisor record says exited is finalized from its meta, not adopted', () => {
78
+ withRunsDir((runsDir) => {
79
+ const runDir = path.join(runsDir, 'run-1');
80
+ fs.mkdirSync(runDir, { recursive: true });
81
+ writeSupervisorRecord({ runDir, slug: 'exited-row', pid: 4242, runId: 'run-1', startedAt: Date.now() });
82
+ fs.writeFileSync(path.join(runDir, 'exited-row.meta.json'), JSON.stringify({ slug: 'exited-row', exitCode: 0 }));
83
+ const job = { slug: 'exited-row', status: 'running', runId: 'run-1', runtime: { pid: 4242 } };
84
+ // Even with the pid reported alive (recycled/lingering), the exit marker wins.
85
+ const { immediate, adopted } = partitionBootOrphans([job], { pidAlive: () => true, runsDir });
86
+ assert.deepEqual(immediate, ['exited-row']);
87
+ assert.deepEqual(adopted, []);
88
+ applyOrphanOutcome(job, 'success');
89
+ assert.equal(job.status, 'completed');
90
+ });
91
+ });
92
+
93
+ test('a record with a live, same-identity pid is adopted; a dead record without an exit marker is finalized', () => {
94
+ withRunsDir((runsDir) => {
95
+ const runDir = path.join(runsDir, 'run-2');
96
+ fs.mkdirSync(runDir, { recursive: true });
97
+ writeSupervisorRecord({ runDir, slug: 'live-rec', pid: 31337, runId: 'run-2', startedAt: Date.now() });
98
+ writeSupervisorRecord({ runDir, slug: 'dead-rec', pid: 31338, runId: 'run-2', startedAt: Date.now() });
99
+ const jobs = [
100
+ { slug: 'live-rec', status: 'running', runId: 'run-2', runtime: { pid: 31337 } },
101
+ { slug: 'dead-rec', status: 'running', runId: 'run-2', runtime: { pid: 31338 } },
102
+ ];
103
+ const { immediate, adopted } = partitionBootOrphans(jobs, { pidAlive: (pid) => pid === 31337, identityOf: () => null, runsDir });
104
+ assert.deepEqual(adopted, ['live-rec']);
105
+ assert.deepEqual(immediate, ['dead-rec']);
106
+ });
45
107
  });
46
108
 
47
109
  test('partitionBootOrphans ignores non-running jobs entirely', () => {
@@ -49,9 +111,9 @@ test('partitionBootOrphans ignores non-running jobs entirely', () => {
49
111
  { slug: 'a', status: 'completed', runtime: { pid: 1 } },
50
112
  { slug: 'b', status: 'failed', runtime: { pid: 2 } },
51
113
  ];
52
- const { immediate, deferred } = partitionBootOrphans(jobs, () => true);
114
+ const { immediate, adopted } = partitionBootOrphans(jobs, { pidAlive: () => true, runsDir: os.tmpdir() });
53
115
  assert.deepEqual(immediate, []);
54
- assert.deepEqual(deferred, []);
116
+ assert.deepEqual(adopted, []);
55
117
  });
56
118
 
57
119
  test('applyOrphanOutcome: success finalizes to completed, clears runtime', () => {
@@ -89,84 +151,3 @@ test('applyOrphanOutcome: no_result at the cap fails terminally instead of re-qu
89
151
  assert.match(job.error, new RegExp(`exhausted ${ORPHAN_REQUEUE_CAP} re-queue attempts`));
90
152
  assert.equal(job.runtime, undefined);
91
153
  });
92
-
93
- // ── feedback sweep moved into the 60s poll tick (PRD 686) ───────────────────
94
-
95
- test('feedbackSweepDue gates on the tick interval, not every tick', () => {
96
- for (let i = 1; i < FEEDBACK_SWEEP_TICK_INTERVAL; i++) {
97
- assert.equal(feedbackSweepDue(i), false, `tick ${i} should not be due yet`);
98
- }
99
- assert.equal(feedbackSweepDue(FEEDBACK_SWEEP_TICK_INTERVAL), true);
100
- assert.equal(feedbackSweepDue(FEEDBACK_SWEEP_TICK_INTERVAL + 1), true);
101
- });
102
-
103
- test('scheduler.cjs reuses watchdogHelpers.sweep verbatim (no forked copy)', () => {
104
- const { sweep } = require('../../../scripts/lib/watchdogHelpers.cjs');
105
- assert.equal(sweepFeedback, sweep, 'scheduler.cjs must import the same sweep function, not reimplement it');
106
- });
107
-
108
- function makeTmpDir() {
109
- return fs.mkdtempSync(path.join(os.tmpdir(), 'sched-boot-sweep-'));
110
- }
111
-
112
- function makeQueue(base) {
113
- const queuePath = path.join(base, 'queue.json');
114
- fs.writeFileSync(queuePath, JSON.stringify({ version: 1, jobs: [], paused: null }, null, 2));
115
- return queuePath;
116
- }
117
-
118
- const FAKE_SKILL = '---\nname: process-feedback\ndescription: x\n---\n\n# process-feedback\n\nbody\n';
119
- const FAKE_STANDARDS = '# Engineering standards\n\nbody\n';
120
-
121
- test('the poll-tick sweep call emits a PRD for an active project with open feedback, and skips one outside the 90-min window', () => {
122
- const base = makeTmpDir();
123
- try {
124
- // Active project (10 min ago) with open feedback → should emit.
125
- const activeProj = path.join(base, 'active-proj');
126
- fs.mkdirSync(path.join(activeProj, 'session-manager-operations', 'feedback'), { recursive: true });
127
- fs.writeFileSync(path.join(activeProj, 'session-manager-operations', 'feedback', '2026-01-01-item.md'), '# Item\n');
128
-
129
- // Stale project (120 min ago, outside the 90-min window) with open feedback → should NOT be scanned at all.
130
- const staleProj = path.join(base, 'stale-proj');
131
- fs.mkdirSync(path.join(staleProj, 'session-manager-operations', 'feedback'), { recursive: true });
132
- fs.writeFileSync(path.join(staleProj, 'session-manager-operations', 'feedback', '2026-01-01-item.md'), '# Item\n');
133
-
134
- // Active project with no open feedback → scanned but not emitted.
135
- const noFeedbackProj = path.join(base, 'no-feedback-proj');
136
- fs.mkdirSync(noFeedbackProj, { recursive: true });
137
-
138
- const projectsDir = path.join(base, 'projects');
139
- const recent = new Date(Date.now() - 10 * 60 * 1000);
140
- const stale = new Date(Date.now() - 120 * 60 * 1000);
141
- for (const [slug, cwd, mtime] of [
142
- ['slug-active', activeProj, recent],
143
- ['slug-stale', staleProj, stale],
144
- ['slug-no-feedback', noFeedbackProj, recent],
145
- ]) {
146
- const projDir = path.join(projectsDir, slug);
147
- fs.mkdirSync(projDir, { recursive: true });
148
- const fp = path.join(projDir, 'a.jsonl');
149
- fs.writeFileSync(fp, JSON.stringify({ cwd }) + '\n');
150
- fs.utimesSync(fp, mtime, mtime);
151
- }
152
-
153
- const prdsDir = path.join(base, 'prds');
154
- fs.mkdirSync(prdsDir);
155
- const queuePath = makeQueue(base);
156
- const skillPath = path.join(base, 'SKILL.md');
157
- const standardsPath = path.join(base, 'standards.md');
158
- fs.writeFileSync(skillPath, FAKE_SKILL);
159
- fs.writeFileSync(standardsPath, FAKE_STANDARDS);
160
-
161
- const result = sweepFeedback({ projectsDir, prdsDir, queuePath, skillPath, standardsPath });
162
-
163
- assert.equal(result.scanned, 2, 'only the two in-window projects should be scanned');
164
- assert.equal(result.emitted, 1, 'only the active project with open feedback should get a PRD');
165
-
166
- const emittedFiles = fs.readdirSync(prdsDir).filter((f) => f.endsWith('.md'));
167
- assert.equal(emittedFiles.length, 1);
168
- assert.match(emittedFiles[0], /feedback-active-proj\.md$/);
169
- } finally {
170
- fs.rmSync(base, { recursive: true, force: true });
171
- }
172
- });