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
@@ -11,10 +11,7 @@
11
11
  * feedback_list_projects() -> GET /admin/feedback/targets
12
12
  * feedback_open_session({ ... }) -> POST /admin/feedback/open-session
13
13
  *
14
- * project_home_get_contract({ cwd? }) -> GET /admin/project-home/contract
15
- * project_home_validate_summary({ cwd?, summary }) -> POST /admin/project-home/validate-summary
16
- * project_home_render({ cwd?, summary, picks }) -> POST /admin/project-home/render
17
- * project_home_status({ cwd? }) -> GET /admin/project-home/status
14
+ * project_home_write({ cwd?, html }) -> POST /admin/project-home/write
18
15
  *
19
16
  * This is a separate process from the Electron app — it only ever reaches
20
17
  * it over the token-authed loopback HTTP API in admin-api.json, never by
@@ -24,7 +21,9 @@
24
21
 
25
22
  'use strict';
26
23
 
27
- const os = require('node:os');
24
+ // Name this process first (pn-03): otherwise it shows as a bare `node`.
25
+ const { PROC_NAMES, setProcessTitle } = require('../src/main/lib/smProcNames.cjs');
26
+ setProcessTitle(PROC_NAMES.mcpServer);
28
27
  const path = require('node:path');
29
28
  const fsp = require('node:fs/promises');
30
29
  const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
@@ -44,7 +43,7 @@ function descriptionFor(toolName) {
44
43
  return composeDescription(entry);
45
44
  }
46
45
 
47
- const TOKEN_PATH = path.join(os.homedir(), '.claude', 'session-manager', 'admin-api.json');
46
+ const { adminTokenPath } = require('../src/main/lib/schedulerPaths.cjs');
48
47
 
49
48
  // Defined once so every failure path points to the same next call, verbatim
50
49
  // — never paste this sentence at each return site (PRD: session_manager_help).
@@ -71,7 +70,7 @@ function resolveCwdArg(args) {
71
70
  }
72
71
 
73
72
  async function readAdminConfig() {
74
- const raw = await fsp.readFile(TOKEN_PATH, 'utf8');
73
+ const raw = await fsp.readFile(adminTokenPath(), 'utf8');
75
74
  const parsed = JSON.parse(raw);
76
75
  if (!parsed || typeof parsed.port !== 'number' || typeof parsed.token !== 'string') {
77
76
  throw new Error(NOT_RUNNING_ERROR);
@@ -140,6 +139,16 @@ const TOOLS = [
140
139
  required: ['slug'],
141
140
  },
142
141
  },
142
+ {
143
+ name: 'scheduler_pause',
144
+ description: descriptionFor('scheduler_pause'),
145
+ inputSchema: { type: 'object', properties: {} },
146
+ },
147
+ {
148
+ name: 'scheduler_resume',
149
+ description: descriptionFor('scheduler_resume'),
150
+ inputSchema: { type: 'object', properties: {} },
151
+ },
143
152
  {
144
153
  name: 'scheduler_list_jobs',
145
154
  description: descriptionFor('scheduler_list_jobs'),
@@ -164,6 +173,11 @@ const TOOLS = [
164
173
  slug: { type: 'string', description: 'Optional kebab-case slug; derived from title if omitted' },
165
174
  parallelGroup: { type: 'number', description: 'DEPRECATED and ignored (PRD 832): numbers are strictly unique per project; use dependsOn for ordering' },
166
175
  dependsOn: { type: 'array', items: { type: 'string' }, description: 'Optional: PRD slugs that must complete before this one becomes eligible (replaces the retired shared-NN-parallel convention)' },
176
+ disposition: {
177
+ type: 'string',
178
+ enum: ['append', 'new-head'],
179
+ description: 'Required when this PRD would land as a new root (no dependsOn given) in an Epic that already has incomplete PRDs: "append" depends this wave on the existing chain\'s current terminal PRD(s); "new-head" leaves it independent, eligible to run in parallel. Omit when the Epic has no incomplete PRDs yet, or when dependsOn already places this PRD explicitly.',
180
+ },
167
181
  sourcePromptId: { type: 'string', description: "Effectively required: an EXISTING, already-human-approved Epic's promptSessionId (the id shown in the Epics list / active-index.json sessions key) to join — NOT a PromptTicket.id (those are distinct fields). The server never mints a new Epic; if this is omitted and no Epic can be resolved (via the SM_CHAT_SESSION_ID fallback below), the write is refused with an error telling the caller to create/approve an Epic first." },
168
182
  sourceTabId: { type: 'string', description: 'Optional: tab id (claudeSessionId) this PRD was queued from, so the scheduler can route a completion status prompt back to it' },
169
183
  tag: {
@@ -231,6 +245,8 @@ const TOOLS = [
231
245
  tag: { type: 'string', enum: PRD_WORK_TYPES },
232
246
  agentType: { type: 'string', description: 'WHO executes this PRD (persona name) — patchable after creation too, see scheduler_create_prd' },
233
247
  quietMachine: { type: 'boolean', description: 'Optional: the exclusive machine-wide quiet lease flag (see scheduler_create_prd) — patchable after creation too' },
248
+ dependsOn: { type: 'array', items: { type: 'string' }, description: 'Optional: replaces the dependsOn list; an explicit empty array CLEARS it — see scheduler_create_prd' },
249
+ disposition: { type: 'string', enum: ['append', 'new-head'], description: 'Optional: patches the recorded wave-authoring decision — see scheduler_create_prd' },
234
250
  },
235
251
  },
236
252
  body: { type: 'string', description: 'Optional: full replacement body (everything after the frontmatter)' },
@@ -340,48 +356,15 @@ const TOOLS = [
340
356
  },
341
357
  },
342
358
  {
343
- name: 'project_home_get_contract',
344
- description: descriptionFor('project_home_get_contract'),
359
+ name: 'project_home_write',
360
+ description: descriptionFor('project_home_write'),
345
361
  inputSchema: {
346
362
  type: 'object',
347
363
  properties: {
348
364
  cwd: { type: 'string', description: "Optional: absolute path to the target project. Defaults to the calling session's own project root (SM_PROJECT_ROOT or process.cwd()) when omitted." },
365
+ html: { type: 'string', description: 'The complete self-contained HTML document (<= 1MB; no <script src>, <link href=http*>, @import, or url(http*)).' },
349
366
  },
350
- },
351
- },
352
- {
353
- name: 'project_home_validate_summary',
354
- description: descriptionFor('project_home_validate_summary'),
355
- inputSchema: {
356
- type: 'object',
357
- properties: {
358
- cwd: { type: 'string', description: "Optional: absolute path to the target project. Defaults to the calling session's own project root when omitted." },
359
- summary: { type: 'object', description: 'ProjectPageSummary object matching the summarySchema from project_home_get_contract' },
360
- },
361
- required: ['summary'],
362
- },
363
- },
364
- {
365
- name: 'project_home_render',
366
- description: descriptionFor('project_home_render'),
367
- inputSchema: {
368
- type: 'object',
369
- properties: {
370
- cwd: { type: 'string', description: "Optional: absolute path to the target project. Defaults to the calling session's own project root when omitted." },
371
- summary: { type: 'object', description: 'ProjectPageSummary object matching the summarySchema from project_home_get_contract' },
372
- picks: { type: 'object', description: 'ProjectPagePicks object (lensId -> slotId -> variantId) matching the picksSchema from project_home_get_contract' },
373
- },
374
- required: ['summary', 'picks'],
375
- },
376
- },
377
- {
378
- name: 'project_home_status',
379
- description: descriptionFor('project_home_status'),
380
- inputSchema: {
381
- type: 'object',
382
- properties: {
383
- cwd: { type: 'string', description: "Optional: absolute path to the target project. Defaults to the calling session's own project root when omitted." },
384
- },
367
+ required: ['html'],
385
368
  },
386
369
  },
387
370
  {
@@ -421,6 +404,10 @@ async function handleCallTool(request) {
421
404
  const result = await adminRequest('POST', '/admin/scheduler/reset-job', { slug, force, cwd });
422
405
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
423
406
  }
407
+ if (name === 'scheduler_pause' || name === 'scheduler_resume') {
408
+ const result = await adminRequest('POST', `/admin/scheduler/${name === 'scheduler_pause' ? 'pause' : 'resume'}`, {});
409
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
410
+ }
424
411
  if (name === 'scheduler_list_jobs') {
425
412
  const result = await adminRequest('GET', '/admin/scheduler/jobs');
426
413
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
@@ -568,35 +555,12 @@ async function handleCallTool(request) {
568
555
  const result = await adminRequest('POST', '/admin/chat/send-prompt', { tabId, prompt });
569
556
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
570
557
  }
571
- if (name === 'project_home_get_contract') {
572
- const cwd = resolveCwdArg(args);
573
- const qs = new URLSearchParams({ cwd });
574
- const result = await adminRequest('GET', `/admin/project-home/contract?${qs.toString()}`);
575
- return { content: [{ type: 'text', text: JSON.stringify(result) }] };
576
- }
577
- if (name === 'project_home_validate_summary') {
578
- if (!args || typeof args.summary !== 'object' || args.summary === null) {
579
- return errorResult('missing required argument: summary');
580
- }
581
- const cwd = resolveCwdArg(args);
582
- const result = await adminRequest('POST', '/admin/project-home/validate-summary', { cwd, summary: args.summary });
583
- return { content: [{ type: 'text', text: JSON.stringify(result) }] };
584
- }
585
- if (name === 'project_home_render') {
586
- if (!args || typeof args.summary !== 'object' || args.summary === null) {
587
- return errorResult('missing required argument: summary');
588
- }
589
- if (typeof args.picks !== 'object' || args.picks === null) {
590
- return errorResult('missing required argument: picks');
558
+ if (name === 'project_home_write') {
559
+ if (!args || typeof args.html !== 'string' || args.html.length === 0) {
560
+ return errorResult('missing required argument: html');
591
561
  }
592
562
  const cwd = resolveCwdArg(args);
593
- const result = await adminRequest('POST', '/admin/project-home/render', { cwd, summary: args.summary, picks: args.picks });
594
- return { content: [{ type: 'text', text: JSON.stringify(result) }] };
595
- }
596
- if (name === 'project_home_status') {
597
- const cwd = resolveCwdArg(args);
598
- const qs = new URLSearchParams({ cwd });
599
- const result = await adminRequest('GET', `/admin/project-home/status?${qs.toString()}`);
563
+ const result = await adminRequest('POST', '/admin/project-home/write', { cwd, html: args.html });
600
564
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
601
565
  }
602
566
  if (name === 'session_manager_help') {
@@ -17,7 +17,7 @@ const {
17
17
  FALLBACK_MODEL,
18
18
  resolveEpicModel,
19
19
  findAgentTypeByClaudeSessionId,
20
- readPersonaModel,
20
+ readOverlayAwarePersonaModel,
21
21
  resolvePrdPersonaForSpawn,
22
22
  } = require('../lib/agentModelResolve.cjs');
23
23
 
@@ -103,37 +103,39 @@ test('findAgentTypeByClaudeSessionId returns null for no match', async () => {
103
103
  expect(findAgentTypeByClaudeSessionId(cwd, 'missing')).toBeNull();
104
104
  });
105
105
 
106
- test('readPersonaModel returns null for a persona with no model field', async () => {
106
+ test('readOverlayAwarePersonaModel returns null for a persona with no model field', async () => {
107
+ const cwd = await mkTmpDir('sm-agentmodel-cwd-');
107
108
  const globalDir = await mkTmpDir('sm-agentmodel-agents-');
108
109
  writePersona(globalDir, 'no-model-persona', []);
109
- expect(readPersonaModel('no-model-persona', { globalDir, validatePath: noopValidatePath })).toBeNull();
110
+ expect(readOverlayAwarePersonaModel('no-model-persona', { cwd, globalDir, validatePath: noopValidatePath })).toBeNull();
110
111
  });
111
112
 
112
- test('readPersonaModel returns null (never throws) for a path-traversal agentType rejected by validatePath', async () => {
113
+ test('readOverlayAwarePersonaModel returns null (never throws) for a path-traversal agentType rejected by validatePath', async () => {
114
+ const cwd = await mkTmpDir('sm-agentmodel-cwd-');
113
115
  const globalDir = await mkTmpDir('sm-agentmodel-agents-');
114
116
  const realValidatePath = (p) => {
115
117
  const real = path.resolve(p);
116
118
  if (!real.startsWith(path.resolve(globalDir) + path.sep)) throw new Error('outside allowed boundaries');
117
119
  return real;
118
120
  };
119
- expect(readPersonaModel('../../../etc/passwd', { globalDir, validatePath: realValidatePath })).toBeNull();
121
+ expect(readOverlayAwarePersonaModel('../../../etc/passwd', { cwd, globalDir, validatePath: realValidatePath })).toBeNull();
120
122
  });
121
123
 
122
124
  // READ side of the agentType FK: a persona that was valid when the Epic was
123
125
  // created but got deleted afterward (rename/deletion in Agent Library) must
124
126
  // still resolve to null, not throw — the WRITE side (epicMint.cjs's
125
127
  // ensureEpic) is what refuses a bad reference at creation time.
126
- test('readPersonaModel returns null (does not throw) for a persona deleted after the Epic was created, and logs it once via opsErrorLog', async () => {
128
+ test('readOverlayAwarePersonaModel returns null (does not throw) for a persona deleted after the Epic was created, and logs it once via opsErrorLog', async () => {
127
129
  const cwd = await mkTmpDir('sm-agentmodel-cwd-');
128
130
  const globalDir = await mkTmpDir('sm-agentmodel-agents-'); // never written to — simulates a deleted persona
129
131
 
130
- expect(() => readPersonaModel('deleted-persona', { globalDir, validatePath: noopValidatePath, cwd })).not.toThrow();
131
- expect(readPersonaModel('deleted-persona', { globalDir, validatePath: noopValidatePath, cwd })).toBeNull();
132
+ expect(() => readOverlayAwarePersonaModel('deleted-persona', { globalDir, validatePath: noopValidatePath, cwd })).not.toThrow();
133
+ expect(readOverlayAwarePersonaModel('deleted-persona', { globalDir, validatePath: noopValidatePath, cwd })).toBeNull();
132
134
 
133
135
  const { todayFile } = require('../lib/opsErrorLog.cjs');
134
136
  const lines = fs.readFileSync(todayFile(cwd), 'utf8').split('\n').filter(Boolean).map((l) => JSON.parse(l));
135
137
  const matches = lines.filter((l) => l.message.includes('deleted-persona'));
136
- // Logged once despite two readPersonaModel calls above — dedup key is (cwd, agentType).
138
+ // Logged once despite two readOverlayAwarePersonaModel calls above — dedup key is (cwd, agentType).
137
139
  expect(matches).toHaveLength(1);
138
140
  expect(matches[0].level).toBe('warn');
139
141
  });
@@ -190,6 +192,95 @@ test('resolvePrdPersonaForSpawn returns the fallback (no persona applied) when a
190
192
  expect(result).toEqual({ model: FALLBACK_MODEL, systemPrompt: null, personaPath: null });
191
193
  });
192
194
 
195
+ // Overlay-aware resolution (unify-epic-model-resolution): resolveEpicModel
196
+ // must use the SAME project-overlay-then-global precedence
197
+ // resolvePrdPersonaForSpawn already uses — so a project's
198
+ // `.claude/agents/<name>.md` wins for a launched Chat/Terminal session
199
+ // exactly like it already does for a scheduled PRD.
200
+
201
+ test('resolveEpicModel prefers a project-overlay persona over the global one with the same name', async () => {
202
+ const cwd = await mkTmpDir('sm-agentmodel-cwd-');
203
+ const globalDir = await mkTmpDir('sm-agentmodel-agents-');
204
+ writeIndex(cwd, { 'epic-1': { id: 'epic-1', claudeSessionId: 'sess-overlay', agentType: 'shared-persona' } });
205
+ writePersona(globalDir, 'shared-persona', ['model: opus']);
206
+ const projectAgentsDir = path.join(cwd, '.claude', 'agents');
207
+ writePersona(projectAgentsDir, 'shared-persona', ['model: haiku']);
208
+
209
+ const model = resolveEpicModel({
210
+ cwd,
211
+ claudeSessionId: 'sess-overlay',
212
+ deps: { globalDir, validatePath: noopValidatePath },
213
+ });
214
+
215
+ expect(model).toBe('haiku');
216
+ });
217
+
218
+ test('resolveEpicModel falls through to the global persona when no project overlay exists', async () => {
219
+ const cwd = await mkTmpDir('sm-agentmodel-cwd-');
220
+ const globalDir = await mkTmpDir('sm-agentmodel-agents-');
221
+ writeIndex(cwd, { 'epic-1': { id: 'epic-1', claudeSessionId: 'sess-global-only', agentType: 'global-only-persona' } });
222
+ writePersona(globalDir, 'global-only-persona', ['model: opus']);
223
+
224
+ const model = resolveEpicModel({
225
+ cwd,
226
+ claudeSessionId: 'sess-global-only',
227
+ deps: { globalDir, validatePath: noopValidatePath },
228
+ });
229
+
230
+ expect(model).toBe('opus');
231
+ });
232
+
233
+ test('resolveEpicModel falls back to FALLBACK_MODEL (never throws) for a dangling agentType with no persona file anywhere, and logs it once', async () => {
234
+ const cwd = await mkTmpDir('sm-agentmodel-cwd-');
235
+ const globalDir = await mkTmpDir('sm-agentmodel-agents-'); // never written to
236
+ writeIndex(cwd, { 'epic-1': { id: 'epic-1', claudeSessionId: 'sess-dangling', agentType: 'ghost-persona' } });
237
+
238
+ const call = () => resolveEpicModel({
239
+ cwd,
240
+ claudeSessionId: 'sess-dangling',
241
+ deps: { globalDir, validatePath: noopValidatePath },
242
+ });
243
+
244
+ expect(call).not.toThrow();
245
+ expect(call()).toBe(FALLBACK_MODEL);
246
+ call(); // second call must not log a second line (dedup per (cwd, agentType))
247
+
248
+ const { todayFile } = require('../lib/opsErrorLog.cjs');
249
+ const lines = fs.readFileSync(todayFile(cwd), 'utf8').split('\n').filter(Boolean).map((l) => JSON.parse(l));
250
+ const matches = lines.filter((l) => l.message.includes('ghost-persona'));
251
+ expect(matches).toHaveLength(1);
252
+ });
253
+
254
+ // Chat (chatRunner.cjs, in-process) and Terminal (EpicTerminalPane.tsx, via
255
+ // the agents:resolve-epic-model IPC added in this change) both call this
256
+ // exact function with the same { cwd, claudeSessionId } shape — so "the same
257
+ // Epic resolves identically in both views" is structural (one function, one
258
+ // call signature), not a coincidence of two implementations agreeing. This
259
+ // asserts the function is a pure, deterministic read of that Epic's overlay-
260
+ // resolved persona model, which is what makes that guarantee hold.
261
+ test('resolveEpicModel resolves identically for the same Epic across repeated calls (the guarantee Chat and Terminal both rely on)', async () => {
262
+ const cwd = await mkTmpDir('sm-agentmodel-cwd-');
263
+ const globalDir = await mkTmpDir('sm-agentmodel-agents-');
264
+ writeIndex(cwd, { 'epic-1': { id: 'epic-1', claudeSessionId: 'sess-shared', agentType: 'shared-epic-persona' } });
265
+ writePersona(globalDir, 'shared-epic-persona', ['model: haiku']);
266
+
267
+ const callArgs = { cwd, claudeSessionId: 'sess-shared', deps: { globalDir, validatePath: noopValidatePath } };
268
+ const chatResult = resolveEpicModel(callArgs);
269
+ const terminalResult = resolveEpicModel(callArgs); // simulates the IPC-forwarded call Terminal makes
270
+
271
+ expect(chatResult).toBe('haiku');
272
+ expect(terminalResult).toBe(chatResult);
273
+ });
274
+
275
+ // --model must never be left unpinned (CLAUDE.md model-pinning rule) on any
276
+ // of the three launch paths. resolvePrdPersonaForSpawn's own miss-path tests
277
+ // above already cover the scheduler call site; this covers the shared
278
+ // Chat/Terminal resolver.
279
+ test('resolveEpicModel never returns an empty/falsy --model value, even on every miss path', async () => {
280
+ expect(resolveEpicModel({})).toBeTruthy();
281
+ expect(resolveEpicModel({ cwd: '/nonexistent-cwd-xyz', claudeSessionId: 'no-such-session' })).toBeTruthy();
282
+ });
283
+
193
284
  test('resolvePrdPersonaForSpawn caps the persona body at 6000 characters with a truncation notice naming the persona path', async () => {
194
285
  const longBody = 'x'.repeat(6500);
195
286
  const result = await resolvePrdPersonaForSpawn({
@@ -14,8 +14,8 @@ function makeProject() {
14
14
  config.addAllowedRoot(root);
15
15
  fs.mkdirSync(path.join(root, 'session-manager-operations', 'project-pages', 'output'), { recursive: true });
16
16
  fs.writeFileSync(
17
- path.join(root, 'session-manager-operations', 'project-pages', 'output', 'marketing.html'),
18
- '<html><title>Demo</title>Marketing</html>',
17
+ path.join(root, 'session-manager-operations', 'project-pages', 'home.html'),
18
+ '<html><title>Demo</title>Home</html>',
19
19
  );
20
20
  fs.writeFileSync(
21
21
  path.join(root, 'session-manager-operations', 'project-pages', 'output', 'feature.html'),
@@ -29,7 +29,7 @@ test('prepareBundle seeds a root document and writes dist/index.html + manifest.
29
29
  const root = makeProject();
30
30
  const result = await bilkoHost.prepareBundle({ cwd: root, slug: 'demo-app' });
31
31
  assert.equal(fs.existsSync(path.join(result.distPath, 'index.html')), true);
32
- assert.equal(fs.readFileSync(path.join(result.distPath, 'index.html'), 'utf8'), '<html><title>Demo</title>Marketing</html>');
32
+ assert.equal(fs.readFileSync(path.join(result.distPath, 'index.html'), 'utf8'), '<html><title>Demo</title>Home</html>');
33
33
  assert.equal(result.manifest.slug, 'demo-app');
34
34
  assert.equal(result.manifest.bundle.fileCount, 1);
35
35
  fs.rmSync(root, { recursive: true, force: true });
@@ -101,4 +101,22 @@ test('flush() with no pending timer still sends (no-arm case)', async () => {
101
101
  expect(sent).toEqual(['state-1']);
102
102
  });
103
103
 
104
+ test('a getPayload() that throws does not wedge the coalescer — the next schedule() still sends', async () => {
105
+ const sent = [];
106
+ const send = vi.fn((payload) => sent.push(payload));
107
+ const getPayload = vi.fn()
108
+ .mockRejectedValueOnce(new Error('boom'))
109
+ .mockResolvedValue('state-recovered');
110
+ const coalescer = createBroadcastCoalescer({ delayMs: 200, send, getPayload });
111
+
112
+ coalescer.schedule();
113
+ await vi.advanceTimersByTimeAsync(200);
114
+ expect(sent).toEqual([]);
115
+
116
+ coalescer.schedule();
117
+ await vi.advanceTimersByTimeAsync(200);
118
+ expect(sent).toEqual(['state-recovered']);
119
+ expect(getPayload).toHaveBeenCalledTimes(2);
120
+ });
121
+
104
122
  console.log('broadcastCoalescer tests: PASS');
@@ -13,14 +13,14 @@
13
13
  * path (via SM_CLAUDE_BIN pointing at a stub process) and asserts a terminal
14
14
  * event is broadcast on cancel.
15
15
  *
16
- * Run: timeout 120 node --test src/main/__tests__/chat-cancel-terminal.test.cjs
16
+ * Run: timeout 300 npx vitest run src/main/__tests__/chat-cancel-terminal.test.cjs
17
17
  */
18
18
 
19
19
  'use strict';
20
20
 
21
21
  delete process.env.SM_CHAT_CONCURRENCY;
22
22
 
23
- const { test } = require('node:test');
23
+ import { test, afterAll } from 'vitest';
24
24
  const assert = require('node:assert/strict');
25
25
  const fs = require('node:fs');
26
26
  const os = require('node:os');
@@ -30,12 +30,9 @@ const path = require('node:path');
30
30
  // mimics a long-running headless run so cancel() has something to SIGTERM.
31
31
  // Shebang + chmod makes it directly spawnable as a single executable (spawn
32
32
  // does not split a path on spaces, so SM_CLAUDE_BIN must be one binary).
33
- const stubPath = path.join(os.tmpdir(), `sm-claude-stub-${process.pid}.cjs`);
34
- fs.writeFileSync(
35
- stubPath,
36
- `#!${process.execPath}\nsetInterval(() => {}, 1000);\n`,
37
- { mode: 0o755 },
38
- );
33
+ const stubPath = require('../../../tests/helpers/claudeStub.cjs').writeClaudeStub({
34
+ body: 'setInterval(() => {}, 1000);',
35
+ });
39
36
  process.env.SM_CLAUDE_BIN = stubPath;
40
37
 
41
38
  const cr = require('../chatRunner.cjs');
@@ -114,7 +111,7 @@ function isTerminal(channel) {
114
111
  );
115
112
  }
116
113
 
117
- test.after(() => {
114
+ afterAll(() => {
118
115
  try { fs.unlinkSync(stubPath); } catch { /* already gone */ }
119
116
  delete process.env.SM_CLAUDE_BIN;
120
117
  });
@@ -21,14 +21,14 @@
21
21
  * arrives, THEN close fires) rather than relying on real OS/pipe timing,
22
22
  * which is not reliably reproducible from a real child process.
23
23
  *
24
- * Run: timeout 120 node --test src/main/__tests__/chat-exit-close-race.test.cjs
24
+ * Run: timeout 300 npx vitest run src/main/__tests__/chat-exit-close-race.test.cjs
25
25
  */
26
26
 
27
27
  'use strict';
28
28
 
29
29
  delete process.env.SM_CHAT_CONCURRENCY;
30
30
 
31
- const { test } = require('node:test');
31
+ import { test, afterAll } from 'vitest';
32
32
  const assert = require('node:assert/strict');
33
33
  const EventEmitter = require('node:events');
34
34
  const cp = require('node:child_process');
@@ -134,7 +134,7 @@ function isTerminal(channel) {
134
134
  );
135
135
  }
136
136
 
137
- test.after(() => {
137
+ afterAll(() => {
138
138
  cp.spawn = originalSpawn;
139
139
  delete process.env.SM_CHAT_CONCURRENCY;
140
140
  });
@@ -6,24 +6,23 @@
6
6
  * asserts a `chat:run:notice` event is broadcast — informational, not a
7
7
  * terminal event.
8
8
  *
9
- * Run: timeout 120 node --test src/main/__tests__/chat-mcp-consent-notice.test.cjs
9
+ * Run: timeout 300 npx vitest run src/main/__tests__/chat-mcp-consent-notice.test.cjs
10
10
  */
11
11
 
12
12
  'use strict';
13
13
 
14
14
  delete process.env.SM_CHAT_CONCURRENCY;
15
15
 
16
- const { test } = require('node:test');
16
+ import { test } from 'vitest';
17
17
  const assert = require('node:assert/strict');
18
18
  const fs = require('node:fs');
19
19
  const os = require('node:os');
20
20
  const path = require('node:path');
21
+ const claudeStub = require('../../../tests/helpers/claudeStub.cjs');
21
22
 
22
23
  function writeStub(lines) {
23
- const stubPath = path.join(os.tmpdir(), `sm-claude-stub-${process.pid}-${Math.floor(Math.random() * 1e9)}.cjs`);
24
24
  const body = lines.map((l) => `process.stdout.write(${JSON.stringify(JSON.stringify(l))} + "\\n");`).join('\n');
25
- fs.writeFileSync(stubPath, `#!${process.execPath}\n${body}\nprocess.exit(0);\n`, { mode: 0o755 });
26
- return stubPath;
25
+ return claudeStub.writeClaudeStub({ body: `${body}\nprocess.exit(0);` });
27
26
  }
28
27
 
29
28
  function isTerminal(channel) {
@@ -6,7 +6,7 @@
6
6
  * this cap/FIFO governs silent runs only — manual runs bypass it entirely
7
7
  * (see chatRunner.spec.ts's "manual runs are uncapped" tests).
8
8
  *
9
- * Run: timeout 120 node --test src/main/__tests__/chat-queue.test.cjs
9
+ * Run: timeout 300 npx vitest run src/main/__tests__/chat-queue.test.cjs
10
10
  */
11
11
 
12
12
  'use strict';
@@ -14,7 +14,7 @@
14
14
  // Force the default cap (2) regardless of the developer's shell env.
15
15
  delete process.env.SM_CHAT_CONCURRENCY;
16
16
 
17
- const { test } = require('node:test');
17
+ import { test } from 'vitest';
18
18
  const assert = require('node:assert/strict');
19
19
  const cr = require('../chatRunner.cjs');
20
20
 
@@ -2,12 +2,12 @@
2
2
  * chat-stop-signal.test.cjs — unit tests for chatRunner.parseStopSignal, the
3
3
  * single source of truth for the terminal-chat stop-signal protocol (PRD 318).
4
4
  *
5
- * Run: timeout 120 node --test src/main/__tests__/chat-stop-signal.test.cjs
5
+ * Run: timeout 300 npx vitest run src/main/__tests__/chat-stop-signal.test.cjs
6
6
  */
7
7
 
8
8
  'use strict';
9
9
 
10
- const { test } = require('node:test');
10
+ import { test } from 'vitest';
11
11
  const assert = require('node:assert/strict');
12
12
  const { parseStopSignal, STOP_SENTINEL } = require('../chatRunner.cjs');
13
13
 
@@ -0,0 +1,77 @@
1
+ /**
2
+ * PRD 1286 — archiving a row must never orphan its dependents, and a fully
3
+ * blocked queue must never read healthy.
4
+ *
5
+ * Option (b): the depends-gate (computeDepHistorySatisfaction) treats a dep
6
+ * whose PRD file was archived — including the manual-archive layout
7
+ * prds-archived/<ISO-ts>/<slug>.md — as satisfied. A dep with no row, no
8
+ * history and no archived file still HOLDS.
9
+ *
10
+ * Run: timeout 120 npx vitest run src/main/__tests__/dep-orphan-archive-health.test.cjs
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ import { test, expect } from 'vitest';
16
+ import fs from 'node:fs';
17
+ import os from 'node:os';
18
+ import path from 'node:path';
19
+
20
+ const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-1286-home-'));
21
+ process.env.HOME = tmpHome;
22
+
23
+ const scheduler = require('../scheduler.cjs');
24
+ const { pickNextBatch } = require('../lib/schedulerBatch.cjs');
25
+ const { evaluateUnresolvableDepHealth } = require('../health.cjs');
26
+
27
+ function projectWithDependent(dep) {
28
+ const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-1286-proj-'));
29
+ const jobs = [{ slug: '902-next', status: 'pending', cwd, dependsOn: [dep] }];
30
+ return { cwd, jobs };
31
+ }
32
+
33
+ async function eligible(jobs) {
34
+ const satisfiedSlugsByCwd = await scheduler.computeDepHistorySatisfaction({ jobs });
35
+ const { batch: picked } = pickNextBatch(jobs, new Set(), 3, { satisfiedSlugsByCwd });
36
+ return { batch: picked.map((j) => j.slug ?? j.job?.slug ?? j), satisfiedSlugsByCwd };
37
+ }
38
+
39
+ test('a dep archived via the manual-archive layout (prds-archived/<ts>/) leaves its dependent eligible', async () => {
40
+ const { cwd, jobs } = projectWithDependent('fo-01-base'); // bare slug, as authored
41
+ const dir = path.join(cwd, 'session-manager-operations', 'scheduler', 'epics', 'e1', 'prds-archived', '2026-09-18T01-00-00-000Z');
42
+ fs.mkdirSync(dir, { recursive: true });
43
+ fs.writeFileSync(path.join(dir, '900-fo-01-base.md'), '# Goal\n');
44
+ const { batch } = await eligible(jobs);
45
+ expect(batch).toContain('902-next');
46
+ });
47
+
48
+ test('a dep that never ran (no row, history or archived file) keeps its dependent held', async () => {
49
+ const { jobs } = projectWithDependent('fo-01-base');
50
+ const { batch } = await eligible(jobs);
51
+ expect(batch).not.toContain('902-next');
52
+ });
53
+
54
+ test('health: every pending row held behind one unresolvable root is non-GREEN and names the root', async () => {
55
+ const { cwd } = projectWithDependent('fo-01-base');
56
+ const jobs = [
57
+ { slug: '902-a', status: 'pending', cwd, dependsOn: ['fo-01-base'] },
58
+ { slug: '903-b', status: 'pending', cwd, dependsOn: ['a'] }, // transitive, bare
59
+ ];
60
+ const satisfied = await scheduler.computeDepHistorySatisfaction({ jobs });
61
+ const res = evaluateUnresolvableDepHealth(jobs, satisfied);
62
+ expect(res.ok).toBe(false);
63
+ expect(res.projects[0].roots).toEqual(['fo-01-base']);
64
+ expect(res.message).toContain('fo-01-base');
65
+ });
66
+
67
+ test('health: a project with any dispatchable pending row, or a satisfied root, stays GREEN', async () => {
68
+ const { cwd } = projectWithDependent('x');
69
+ const mixed = [
70
+ { slug: '902-a', status: 'pending', cwd, dependsOn: ['fo-01-base'] },
71
+ { slug: '903-free', status: 'pending', cwd, dependsOn: [] },
72
+ ];
73
+ const satisfied = await scheduler.computeDepHistorySatisfaction({ jobs: mixed });
74
+ expect(evaluateUnresolvableDepHealth(mixed, satisfied).ok).toBe(true);
75
+ const satisfiedRoot = new Map([[cwd, new Set(['900-fo-01-base'])]]);
76
+ expect(evaluateUnresolvableDepHealth([mixed[0]], satisfiedRoot).ok).toBe(true);
77
+ });
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * dod-batchkey.test.cjs — unit tests for definitionOfDone.cjs helpers.
3
3
  *
4
- * Run: timeout 120 node --test src/main/__tests__/dod-batchkey.test.cjs
4
+ * Run: timeout 300 npx vitest run src/main/__tests__/dod-batchkey.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 os = require('node:os');
12
12
  const fs = require('node:fs');
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * dod-drain-hook.test.cjs — drives the DoD drain handler.
3
3
  *
4
- * Run: timeout 120 node --test src/main/__tests__/dod-drain-hook.test.cjs
4
+ * Run: timeout 300 npx vitest run src/main/__tests__/dod-drain-hook.test.cjs
5
5
  *
6
6
  * Scenarios:
7
7
  * 1. Drain with completed jobs → writes report once.
@@ -13,7 +13,7 @@
13
13
 
14
14
  'use strict';
15
15
 
16
- const { test, beforeEach, afterEach } = require('node:test');
16
+ import { test, beforeEach, afterEach } from 'vitest';
17
17
  const assert = require('node:assert/strict');
18
18
  const os = require('node:os');
19
19
  const fs = require('node:fs');
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * dod-report.test.cjs — unit tests for flagRiskySurfaces + writeReport.
3
3
  *
4
- * Run: timeout 120 node --test src/main/__tests__/dod-report.test.cjs
4
+ * Run: timeout 300 npx vitest run src/main/__tests__/dod-report.test.cjs
5
5
  *
6
6
  * Fixtures: os.tmpdir() only — never writes into real runs/.
7
7
  */
8
8
 
9
9
  'use strict';
10
10
 
11
- const { test } = require('node:test');
11
+ import { test } from 'vitest';
12
12
  const assert = require('node:assert/strict');
13
13
  const os = require('node:os');
14
14
  const fs = require('node:fs');