claude-code-session-manager 0.86.0 → 0.88.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/README.md +26 -53
  2. package/dist/assets/AgentLibrary-BZ6IY2g1.js +3 -0
  3. package/dist/assets/{DataModel-Q4jhl24R.js → DataModel-dAoGAODX.js} +1 -1
  4. package/dist/assets/{History-Cj2FejEo.js → History-CF157HyX.js} +2 -2
  5. package/dist/assets/{Hooks-CaelQI6t.js → Hooks-CCPPipBk.js} +3 -3
  6. package/dist/assets/{HostBilko--v7cMR8I.js → HostBilko-Dlmz2m06.js} +1 -1
  7. package/dist/assets/{Library-DgI9oCCZ.js → Library-BaXcboWX.js} +1 -1
  8. package/dist/assets/{ListDetail-DYUZN-x-.js → ListDetail-CoUCzG5S.js} +1 -1
  9. package/dist/assets/MarkdownEditor-DTg3JlOT.js +1 -0
  10. package/dist/assets/{McpServers-ypCYURh3.js → McpServers-DdjY1sA3.js} +2 -2
  11. package/dist/assets/{Memory-C2qYp-3M.js → Memory-BJ3Jlw0C.js} +4 -4
  12. package/dist/assets/{Panel-Cj2kw-Zv.js → Panel-ygRSgc41.js} +1 -1
  13. package/dist/assets/Permissions-m0KAnccF.js +3 -0
  14. package/dist/assets/{Plugins-C1Vj8_dU.js → Plugins-CJRl6B3Q.js} +2 -2
  15. package/dist/assets/{ProvenanceBadge-DczPNM5U.js → ProvenanceBadge-w8cnlVvW.js} +1 -1
  16. package/dist/assets/{SaveBar-Cd_7U6Gb.js → SaveBar-BpyPcUIk.js} +1 -1
  17. package/dist/assets/Scheduler-D2ppxSfy.js +14 -0
  18. package/dist/assets/{ScopeSwitcher-DVSyI44-.js → ScopeSwitcher-EyuIwt-j.js} +1 -1
  19. package/dist/assets/Settings-BHSGGFEc.js +3 -0
  20. package/dist/assets/{SkillReferenceGraph-CUv1_Q2c.js → SkillReferenceGraph-BNaZ2JCF.js} +1 -1
  21. package/dist/assets/{Skills-C_YHkAy-.js → Skills-Dl3q75QV.js} +2 -2
  22. package/dist/assets/{SystemPrompt-B8R7T9xn.js → SystemPrompt-Bg6F-Cyp.js} +1 -1
  23. package/dist/assets/{TagLibrary-dj9YHWyy.js → TagLibrary-BSutflCy.js} +1 -1
  24. package/dist/assets/{TiptapBody-DnSBUjHE.js → TiptapBody-CXih4xrc.js} +1 -1
  25. package/dist/assets/{Toggle-CjV_BJn6.js → Toggle-2qtUHhA9.js} +1 -1
  26. package/dist/assets/{index-CXFQIPhO.js → index-B3emy2oI.js} +583 -583
  27. package/dist/assets/{index-CDo9xBR9.css → index-BHTX4OTc.css} +1 -1
  28. package/dist/assets/settingsSchema-CdzxaqwM.js +3 -0
  29. package/dist/assets/{whisperWorker-Dbia1OpC.js → whisperWorker-C7ZGQwKg.js} +7 -7
  30. package/dist/index.html +2 -2
  31. package/dist/vad/ort-wasm-simd-threaded.asyncify.mjs +106 -110
  32. package/dist/vad/ort-wasm-simd-threaded.asyncify.wasm +0 -0
  33. package/dist/vad/ort-wasm-simd-threaded.jsep.mjs +98 -98
  34. package/dist/vad/ort-wasm-simd-threaded.jsep.wasm +0 -0
  35. package/dist/vad/ort-wasm-simd-threaded.jspi.mjs +99 -102
  36. package/dist/vad/ort-wasm-simd-threaded.jspi.wasm +0 -0
  37. package/dist/vad/ort-wasm-simd-threaded.mjs +46 -46
  38. package/dist/vad/ort-wasm-simd-threaded.wasm +0 -0
  39. package/package.json +14 -17
  40. package/plugins/CLAUDE.md +61 -0
  41. package/plugins/session-manager-dev/.claude-plugin/plugin.json +1 -1
  42. package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +1 -1
  43. package/plugins/session-manager-dev/skills/ops-sweep/SKILL.md +1 -1
  44. package/scripts/README.md +59 -0
  45. package/scripts/audit-ops-hygiene.cjs +350 -0
  46. package/scripts/hooks/guard-destructive-git.cjs +10 -34
  47. package/scripts/hooks/guard-inline-implementation.cjs +14 -8
  48. package/scripts/hooks/guard-prd-writes.cjs +7 -36
  49. package/scripts/hooks/guard-self-schedule.cjs +175 -0
  50. package/scripts/ops-sweep.cjs +355 -0
  51. package/scripts/scheduler-mcp-server.cjs +35 -71
  52. package/src/main/__tests__/agentModelResolve.test.cjs +100 -9
  53. package/src/main/__tests__/bilkoHost-integration.test.cjs +3 -3
  54. package/src/main/__tests__/broadcastCoalescer.test.cjs +18 -0
  55. package/src/main/__tests__/chat-cancel-terminal.test.cjs +6 -9
  56. package/src/main/__tests__/chat-exit-close-race.test.cjs +3 -3
  57. package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +4 -5
  58. package/src/main/__tests__/chat-queue.test.cjs +2 -2
  59. package/src/main/__tests__/chat-stop-signal.test.cjs +2 -2
  60. package/src/main/__tests__/dep-orphan-archive-health.test.cjs +77 -0
  61. package/src/main/__tests__/dod-batchkey.test.cjs +2 -2
  62. package/src/main/__tests__/dod-drain-hook.test.cjs +2 -2
  63. package/src/main/__tests__/dod-report.test.cjs +2 -2
  64. package/src/main/__tests__/dod-reverify.test.cjs +2 -2
  65. package/src/main/__tests__/epicMint.test.cjs +4 -4
  66. package/src/main/__tests__/exchanges.test.cjs +2 -2
  67. package/src/main/__tests__/extractJson.test.cjs +2 -2
  68. package/src/main/__tests__/files-reject-credentials.test.cjs +1 -1
  69. package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +556 -0
  70. package/src/main/__tests__/health-build-freshness.test.cjs +39 -0
  71. package/src/main/__tests__/health-delegation-chain.test.cjs +17 -2
  72. package/src/main/__tests__/health-queue-dispatch.test.cjs +58 -7
  73. package/src/main/__tests__/health-tick-liveness.test.cjs +11 -3
  74. package/src/main/__tests__/health-usage-poller.test.cjs +70 -23
  75. package/src/main/__tests__/historyRollup.test.cjs +2 -2
  76. package/src/main/__tests__/kg-augment.test.cjs +2 -2
  77. package/src/main/__tests__/mcpStatus.test.cjs +2 -2
  78. package/src/main/__tests__/memoryAggregate.test.cjs +1 -1
  79. package/src/main/__tests__/memoryStale.test.cjs +1 -1
  80. package/src/main/__tests__/needsReviewLedger.test.cjs +162 -0
  81. package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +28 -4
  82. package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +48 -4
  83. package/src/main/__tests__/prd-group-allocator.test.cjs +2 -2
  84. package/src/main/__tests__/prdAdminRouteParity.test.cjs +2 -0
  85. package/src/main/__tests__/prdAdminRoutes.test.cjs +14 -2
  86. package/src/main/__tests__/prdAuthoringSeed.test.cjs +39 -0
  87. package/src/main/__tests__/prdCreateDisposition.test.cjs +201 -0
  88. package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +125 -0
  89. package/src/main/__tests__/prdLocations.test.cjs +100 -2
  90. package/src/main/__tests__/prdLocationsArchived.test.cjs +50 -1
  91. package/src/main/__tests__/prdSetDisposition.test.cjs +222 -0
  92. package/src/main/__tests__/proc-role-env.test.cjs +125 -0
  93. package/src/main/__tests__/procname-claude-spawn-sites.test.cjs +304 -0
  94. package/src/main/__tests__/procname-sm-processes.test.cjs +127 -0
  95. package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +81 -401
  96. package/src/main/__tests__/projectPages.test.cjs +63 -149
  97. package/src/main/__tests__/queue-health-verdict.test.cjs +170 -0
  98. package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +117 -20
  99. package/src/main/__tests__/queue-starvation-per-project.test.cjs +14 -2
  100. package/src/main/__tests__/queueHistory.test.cjs +65 -2
  101. package/src/main/__tests__/rateLimitPollerStreak.test.cjs +38 -3
  102. package/src/main/__tests__/reconcileTiming.test.cjs +135 -0
  103. package/src/main/__tests__/runVerify-landed-commit-outranks.test.cjs +181 -0
  104. package/src/main/__tests__/runVerify.test.cjs +5 -5
  105. package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +3 -3
  106. package/src/main/__tests__/scheduleJobTransitions.test.cjs +103 -3
  107. package/src/main/__tests__/scheduler-adopted-run-supervision.test.cjs +143 -0
  108. package/src/main/__tests__/scheduler-autofix-select.test.cjs +2 -2
  109. package/src/main/__tests__/scheduler-autopromote.test.cjs +2 -2
  110. package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +3 -5
  111. package/src/main/__tests__/scheduler-boot-orphans.test.cjs +79 -98
  112. package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +121 -0
  113. package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +43 -0
  114. package/src/main/__tests__/scheduler-default-eligible-heal.test.cjs +161 -0
  115. package/src/main/__tests__/scheduler-dispatch-loop.test.cjs +58 -0
  116. package/src/main/__tests__/scheduler-epic-digest.test.cjs +3 -5
  117. package/src/main/__tests__/scheduler-force-tick-outcome.test.cjs +2 -2
  118. package/src/main/__tests__/scheduler-gate-shadow.test.cjs +119 -0
  119. package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +390 -0
  120. package/src/main/__tests__/scheduler-heartbeat-payload.test.cjs +80 -0
  121. package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +25 -18
  122. package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +4 -4
  123. package/src/main/__tests__/scheduler-job-budget.test.cjs +172 -0
  124. package/src/main/__tests__/scheduler-launch-failure.test.cjs +3 -5
  125. package/src/main/__tests__/scheduler-looks-done.test.cjs +119 -10
  126. package/src/main/__tests__/scheduler-manual-pause.test.cjs +118 -0
  127. package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +26 -3
  128. package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +164 -0
  129. package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +17 -2
  130. package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +3 -5
  131. package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +41 -6
  132. package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +62 -6
  133. package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +3 -5
  134. package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +182 -3
  135. package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +87 -0
  136. package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +100 -0
  137. package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +3 -3
  138. package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +4 -4
  139. package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +110 -0
  140. package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +168 -0
  141. package/src/main/__tests__/scheduler-starve-escalation.test.cjs +18 -8
  142. package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +245 -0
  143. package/src/main/__tests__/scheduler-supervisor-record.test.cjs +81 -0
  144. package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +2 -2
  145. package/src/main/__tests__/scheduler-tick-wedge.test.cjs +172 -0
  146. package/src/main/__tests__/scheduler-transient-failure.test.cjs +2 -2
  147. package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +51 -11
  148. package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +3 -5
  149. package/src/main/__tests__/usageSingleFlight.test.cjs +157 -0
  150. package/src/main/__tests__/workTypeLibrary.test.cjs +1 -1
  151. package/src/main/bilkoHost.cjs +16 -7
  152. package/src/main/build-info.json +8 -0
  153. package/src/main/chatRunner.cjs +12 -7
  154. package/src/main/config.cjs +2 -3
  155. package/src/main/docEdit.cjs +4 -2
  156. package/src/main/health.cjs +268 -52
  157. package/src/main/heapSnapshot.cjs +2 -2
  158. package/src/main/historyAggregator.cjs +6 -1
  159. package/src/main/index.cjs +152 -50
  160. package/src/main/ipcSchemas.cjs +53 -17
  161. package/src/main/lib/__tests__/active-sessions.test.cjs +251 -0
  162. package/src/main/lib/__tests__/auditLog.test.cjs +38 -0
  163. package/src/main/lib/__tests__/bootSelfHeal.test.cjs +107 -0
  164. package/src/main/lib/__tests__/buildIdentity.test.cjs +121 -0
  165. package/src/main/lib/__tests__/cwdClassify.test.cjs +111 -0
  166. package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +95 -0
  167. package/src/main/lib/__tests__/delegationReadiness.test.cjs +323 -44
  168. package/src/main/lib/__tests__/dispatchLoop.test.cjs +63 -0
  169. package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +239 -0
  170. package/src/main/lib/__tests__/gateFixtures.json +20 -0
  171. package/src/main/lib/__tests__/gitWorktree.test.cjs +97 -1
  172. package/src/main/lib/__tests__/guardShims.test.cjs +151 -0
  173. package/src/main/lib/__tests__/instanceLock.test.cjs +93 -7
  174. package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +78 -0
  175. package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +11 -0
  176. package/src/main/lib/__tests__/localAdminHttp.test.cjs +1 -1
  177. package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +6 -1
  178. package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +5 -5
  179. package/src/main/lib/__tests__/prdDisposition.test.cjs +224 -0
  180. package/src/main/lib/__tests__/procIdentity.test.cjs +119 -0
  181. package/src/main/lib/__tests__/procName.test.cjs +92 -0
  182. package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +67 -0
  183. package/src/main/lib/__tests__/reaperHelpers.test.cjs +179 -1
  184. package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +6 -7
  185. package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +37 -204
  186. package/src/main/lib/__tests__/schedulerPaths.test.cjs +226 -0
  187. package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +94 -0
  188. package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +56 -0
  189. package/src/main/lib/__tests__/sessionSlots.test.cjs +45 -0
  190. package/src/main/lib/__tests__/telemetryBacklog.test.cjs +1 -1
  191. package/src/main/lib/__tests__/upgradeDrain.test.cjs +130 -0
  192. package/src/main/lib/__tests__/usageCircuit.test.cjs +285 -0
  193. package/src/main/lib/__tests__/watchdog-helpers.test.cjs +375 -0
  194. package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +266 -0
  195. package/src/main/lib/activeIndexRebuild.cjs +1 -3
  196. package/{scripts → src/main}/lib/activeSessions.cjs +67 -89
  197. package/src/main/lib/adoptedRunSupervisor.cjs +136 -0
  198. package/src/main/lib/agentModelResolve.cjs +94 -24
  199. package/src/main/lib/agentPersonaSchema.cjs +2 -2
  200. package/src/main/lib/auditLog.cjs +30 -5
  201. package/src/main/lib/bootSelfHeal.cjs +88 -0
  202. package/src/main/lib/buildIdentity.cjs +113 -0
  203. package/src/main/lib/classifyPromptTicket.cjs +3 -2
  204. package/src/main/lib/claudeBin.cjs +37 -2
  205. package/src/main/lib/cleanEnv.cjs +27 -1
  206. package/src/main/lib/credentials.cjs +4 -2
  207. package/src/main/lib/cwdClassify.cjs +185 -0
  208. package/src/main/lib/definitionOfDone.cjs +296 -52
  209. package/src/main/lib/delegationReadiness.cjs +290 -225
  210. package/src/main/lib/dispatchLoop.cjs +40 -0
  211. package/src/main/lib/effectiveModelInfo.cjs +332 -0
  212. package/src/main/lib/ephemeralCwd.cjs +7 -28
  213. package/src/main/lib/epicMint.cjs +3 -3
  214. package/src/main/lib/gitWorktree.cjs +136 -19
  215. package/src/main/lib/guardShims.cjs +156 -0
  216. package/src/main/lib/historyRollup.cjs +6 -7
  217. package/src/main/lib/instanceLock.cjs +32 -5
  218. package/src/main/lib/jobDirtFilter.cjs +7 -2
  219. package/src/main/lib/jobSupervisorRecord.cjs +147 -0
  220. package/src/main/lib/jobWorktreeBootLive.cjs +9 -5
  221. package/src/main/lib/launchFailure.cjs +2 -1
  222. package/src/main/lib/localAdminHttp.cjs +9 -27
  223. package/src/main/lib/mcpToolCatalog.cjs +33 -78
  224. package/src/main/lib/needsReviewLedger.cjs +205 -0
  225. package/src/main/lib/opsErrorLog.cjs +1 -1
  226. package/src/main/lib/opsOwnership.cjs +27 -19
  227. package/src/main/lib/prdAuthoringSeed.cjs +36 -0
  228. package/src/main/lib/prdCreate.cjs +56 -1
  229. package/src/main/lib/prdDisposition.cjs +199 -0
  230. package/src/main/lib/prdFrontmatter.cjs +8 -2
  231. package/src/main/lib/prdLocations.cjs +214 -45
  232. package/src/main/lib/procIdentity.cjs +126 -0
  233. package/src/main/lib/procName.cjs +98 -0
  234. package/src/main/lib/projectHomeAdminRoutes.cjs +56 -327
  235. package/src/main/lib/projectRootResolve.cjs +1 -1
  236. package/src/main/lib/queueHistory.cjs +27 -8
  237. package/src/main/lib/queueStore.cjs +63 -38
  238. package/src/main/lib/quietMachineLease.cjs +19 -2
  239. package/src/main/lib/reaperHelpers.cjs +181 -15
  240. package/src/main/lib/reservationExpiry.cjs +30 -0
  241. package/src/main/lib/runClaudeP.cjs +4 -2
  242. package/src/main/lib/runLogRetention.cjs +3 -2
  243. package/src/main/lib/scheduleJobSchema.cjs +10 -2
  244. package/src/main/lib/scheduleJobTransitions.cjs +60 -0
  245. package/src/main/lib/schedulerBatch.cjs +27 -2
  246. package/src/main/lib/schedulerConfig.cjs +24 -0
  247. package/src/main/lib/schedulerPaths.cjs +175 -0
  248. package/src/main/lib/schedulerRuntimeState.cjs +59 -0
  249. package/src/main/lib/sessionSlots.cjs +47 -9
  250. package/src/main/lib/smProcNames.cjs +51 -0
  251. package/src/main/lib/upgradeDrain.cjs +188 -0
  252. package/src/main/lib/usageCircuit.cjs +206 -0
  253. package/{scripts → src/main}/lib/watchdogHelpers.cjs +71 -38
  254. package/src/main/lib/withTimeout.cjs +33 -0
  255. package/src/main/mcpStatus.cjs +4 -2
  256. package/src/main/pluginInstall.cjs +6 -2
  257. package/src/main/projectPages.cjs +41 -145
  258. package/src/main/pty.cjs +8 -1
  259. package/src/main/queueOps.cjs +10 -10
  260. package/src/main/runVerify.cjs +69 -3
  261. package/src/main/scheduler/prdParser.cjs +13 -0
  262. package/src/main/scheduler.cjs +2841 -482
  263. package/src/main/seedAgentPersonas.cjs +1 -1
  264. package/src/main/seedDevPlugin.cjs +1 -1
  265. package/src/main/seedSchedulerMcp.cjs +7 -6
  266. package/src/main/seedStatus.cjs +1 -1
  267. package/src/main/supervisor.cjs +5 -3
  268. package/src/main/templates/PRD_AUTHORING.md +50 -0
  269. package/src/main/usage.cjs +140 -58
  270. package/src/preload/api.d.ts +110 -65
  271. package/src/preload/index.cjs +12 -4
  272. package/src/seed/agents/project-home-builder.md +31 -48
  273. package/dist/assets/AgentLibrary-DTFL7y8G.js +0 -3
  274. package/dist/assets/MarkdownEditor-DCIubYWf.js +0 -1
  275. package/dist/assets/Permissions-BiYZNGYW.js +0 -3
  276. package/dist/assets/Scheduler-DcLBiJBq.js +0 -14
  277. package/dist/assets/Settings-Cv-pRyms.js +0 -3
  278. package/dist/assets/settingsSchema-BJVciriw.js +0 -3
  279. package/screenshots/.gitkeep +0 -0
  280. package/screenshots/README-screenshots.md +0 -13
  281. package/scripts/project-pages-logic/dist/logic.cjs +0 -4709
  282. package/scripts/render-project-pages/dist/renderer.cjs +0 -18900
  283. package/scripts/render-project-pages.cjs +0 -70
  284. package/scripts/validate-project-pages-summary.cjs +0 -62
  285. package/src/main/lib/projectPageSummarySchema.cjs +0 -181
  286. package/src/main/teams.cjs +0 -95
  287. package/src/main/templates/project-pages-catalog.json +0 -741
  288. package/src/main/templates/project-pages-default-home.html +0 -123
  289. package/src/main/templates/project-pages-pipeline.md +0 -417
  290. package/src/seed/prompts/code-review/ac-coverage-check.md +0 -8
  291. package/src/seed/prompts/code-review/correctness-only.md +0 -8
  292. package/src/seed/prompts/code-review/full-spectrum-high.md +0 -8
  293. package/src/seed/prompts/code-review/hallucination-check.md +0 -8
  294. package/src/seed/prompts/code-review/public-api-compat.md +0 -8
  295. package/src/seed/prompts/code-review/readability-naming.md +0 -8
  296. package/src/seed/prompts/debugging/bug-as-failing-test.md +0 -8
  297. package/src/seed/prompts/debugging/git-bisect-regression.md +0 -8
  298. package/src/seed/prompts/debugging/instrument-intermittent-bug.md +0 -8
  299. package/src/seed/prompts/debugging/localize-pipeline-failure.md +0 -8
  300. package/src/seed/prompts/debugging/reproduce-then-diagnose.md +0 -8
  301. package/src/seed/prompts/documentation/adr-from-change.md +0 -8
  302. package/src/seed/prompts/documentation/module-readme.md +0 -8
  303. package/src/seed/prompts/documentation/onboarding-plan.md +0 -8
  304. package/src/seed/prompts/documentation/refresh-claude-md.md +0 -8
  305. package/src/seed/prompts/documentation/tsdoc-public-exports.md +0 -8
  306. package/src/seed/prompts/git-pr/conventional-commit.md +0 -8
  307. package/src/seed/prompts/git-pr/draft-pr-title-body.md +0 -8
  308. package/src/seed/prompts/git-pr/pre-commit-safety-sweep.md +0 -8
  309. package/src/seed/prompts/git-pr/release-notes-block.md +0 -8
  310. package/src/seed/prompts/git-pr/split-large-pr.md +0 -8
  311. package/src/seed/prompts/performance/bundle-startup-audit.md +0 -8
  312. package/src/seed/prompts/performance/complexity-audit.md +0 -8
  313. package/src/seed/prompts/performance/cpu-profile-hot-path.md +0 -8
  314. package/src/seed/prompts/performance/db-query-plan-review.md +0 -8
  315. package/src/seed/prompts/performance/memory-leak-hunt.md +0 -8
  316. package/src/seed/prompts/qa/api-contract-tests.md +0 -8
  317. package/src/seed/prompts/qa/e2e-critical-path.md +0 -8
  318. package/src/seed/prompts/qa/failing-test-for-bug.md +0 -8
  319. package/src/seed/prompts/qa/find-missing-test-coverage.md +0 -8
  320. package/src/seed/prompts/qa/stabilize-flaky-test.md +0 -8
  321. package/src/seed/prompts/qa/tdd-red-first.md +0 -8
  322. package/src/seed/prompts/qa/visual-regression-review.md +0 -8
  323. package/src/seed/prompts/qa/wcag-axe-scan.md +0 -8
  324. package/src/seed/prompts/refactoring/dead-code-sweep.md +0 -8
  325. package/src/seed/prompts/refactoring/extract-duplicated-pattern.md +0 -8
  326. package/src/seed/prompts/refactoring/modernize-legacy-file.md +0 -8
  327. package/src/seed/prompts/refactoring/reduce-cyclomatic-complexity.md +0 -8
  328. package/src/seed/prompts/refactoring/tighten-module-boundaries.md +0 -8
  329. package/src/seed/prompts/security/authz-audit.md +0 -8
  330. package/src/seed/prompts/security/crypto-correctness.md +0 -8
  331. package/src/seed/prompts/security/cwe-top-25-hunt.md +0 -8
  332. package/src/seed/prompts/security/dependency-audit.md +0 -8
  333. package/src/seed/prompts/security/ipc-boundary-hardening.md +0 -8
  334. package/src/seed/prompts/security/owasp-top-10-staged-diff.md +0 -8
  335. package/src/seed/prompts/security/secret-credential-scan.md +0 -8
@@ -0,0 +1,147 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * jobSupervisorRecord.cjs — the one authoritative, SYNCHRONOUS record of a
5
+ * dispatch, written at the same point scheduler.cjs logs `[scheduler] spawned
6
+ * pid=`.
7
+ *
8
+ * Before this, the only durable trace of a dispatch was runtime={pid,...},
9
+ * written asynchronously by a mutate() inside the onPid callback, and the run
10
+ * dir held only the log until an exit path wrote meta.json. A booting process
11
+ * therefore had nothing to tell "my run, still alive" from "dead" and fell
12
+ * back to a ladder of inferences. `<runDir>/<slug>.supervisor.json` is that
13
+ * missing fact.
14
+ *
15
+ * Plain Node, no electron. The run dir is machine-wide scheduler state (not
16
+ * an ops namespace), so queueStore's writeJsonAtomicSync is the right writer.
17
+ * This module never signals a process and adds no cap accounting.
18
+ */
19
+
20
+ const fs = require('node:fs');
21
+ const path = require('node:path');
22
+ const { writeJsonAtomicSync } = require('./queueStore.cjs');
23
+ const { isDifferentProcess } = require('./procIdentity.cjs');
24
+
25
+ const SUFFIX = '.supervisor.json';
26
+ const META_SUFFIX = '.meta.json';
27
+ const INVESTIGATION_EXIT_RE = /\[scheduler\] investigation exit code=/;
28
+ const EXIT_TAIL_BYTES = 8 * 1024;
29
+
30
+ function recordPath(runDir, slug) {
31
+ return path.join(runDir, `${slug}${SUFFIX}`);
32
+ }
33
+
34
+ /**
35
+ * Writes `<runDir>/<slug>.supervisor.json` atomically. Throws on failure —
36
+ * the caller owns the "never fail the dispatch" try/catch. `kind` defaults to
37
+ * "job"; an investigation probe passes kind: "investigation" (and a slug of
38
+ * `<failedSlug>.investigation`, so it never overwrites the failed job's own
39
+ * record in the shared run dir).
40
+ */
41
+ function writeSupervisorRecord({
42
+ runDir, slug, cwd, runId, pid, pgid, identity, execCwd, worktreeDir, worktreeBranch,
43
+ sessionId, startedAt, budgetMs, maxDurationMs, idleKillMs, schedulerPid, codeSha, kind,
44
+ }) {
45
+ if (!runDir || !slug) throw new Error('writeSupervisorRecord: runDir and slug are required');
46
+ const record = {
47
+ kind: kind || 'job',
48
+ slug, cwd: cwd ?? null, runId: runId ?? null,
49
+ pid: pid ?? null, pgid: pgid ?? null, identity: identity ?? null,
50
+ execCwd: execCwd ?? null, worktreeDir: worktreeDir ?? null, worktreeBranch: worktreeBranch ?? null,
51
+ sessionId: sessionId ?? null, startedAt: startedAt ?? null,
52
+ budgetMs: budgetMs ?? null, maxDurationMs: maxDurationMs ?? null, idleKillMs: idleKillMs ?? null,
53
+ schedulerPid: schedulerPid ?? null, codeSha: codeSha ?? null,
54
+ };
55
+ writeJsonAtomicSync(recordPath(runDir, slug), record);
56
+ return record;
57
+ }
58
+
59
+ /** Returns the parsed record, or null when absent/unparseable. Never throws. */
60
+ function readSupervisorRecord(runDir, slug) {
61
+ try {
62
+ const rec = JSON.parse(fs.readFileSync(recordPath(runDir, slug), 'utf8'));
63
+ return rec && typeof rec === 'object' ? rec : null;
64
+ } catch {
65
+ return null;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * True when the run has a durable exit marker: `<slug>.meta.json` for a job,
71
+ * the `investigation exit code=` log line for a probe. This is what separates
72
+ * "exited" from "dead" — a dead pid with no marker never got to write one.
73
+ */
74
+ function hasExitMarker(runDir, record) {
75
+ try {
76
+ if (record.kind === 'investigation') {
77
+ const failedSlug = String(record.slug).replace(/\.investigation$/, '');
78
+ const logPath = path.join(runDir, `${failedSlug}.investigation.log`);
79
+ const { size } = fs.statSync(logPath);
80
+ const fd = fs.openSync(logPath, 'r');
81
+ try {
82
+ const len = Math.min(size, EXIT_TAIL_BYTES);
83
+ const buf = Buffer.alloc(len);
84
+ fs.readSync(fd, buf, 0, len, size - len);
85
+ return INVESTIGATION_EXIT_RE.test(buf.toString('utf8'));
86
+ } finally {
87
+ fs.closeSync(fd);
88
+ }
89
+ }
90
+ return fs.existsSync(path.join(runDir, `${record.slug}${META_SUFFIX}`));
91
+ } catch {
92
+ return false;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Every record under `runsDir/<runId>/` whose file was written within
98
+ * maxAgeMs and which has no exit marker yet. Each entry is the record plus
99
+ * `runDir`. Never throws; an unreadable dir yields [].
100
+ * Complexity: O(run dirs + files in recent dirs).
101
+ */
102
+ function listLiveSupervisorRecords(runsDir, { maxAgeMs, now = Date.now() } = {}) {
103
+ const out = [];
104
+ let dirs;
105
+ try { dirs = fs.readdirSync(runsDir, { withFileTypes: true }); } catch { return out; }
106
+ for (const d of dirs) {
107
+ if (!d.isDirectory()) continue;
108
+ const runDir = path.join(runsDir, d.name);
109
+ let files;
110
+ try { files = fs.readdirSync(runDir); } catch { continue; }
111
+ for (const f of files) {
112
+ if (!f.endsWith(SUFFIX)) continue;
113
+ try {
114
+ if (Number.isFinite(maxAgeMs) && now - fs.statSync(path.join(runDir, f)).mtimeMs > maxAgeMs) continue;
115
+ const record = readSupervisorRecord(runDir, f.slice(0, -SUFFIX.length));
116
+ if (!record || hasExitMarker(runDir, record)) continue;
117
+ out.push({ ...record, runDir });
118
+ } catch { /* raced with a delete — skip */ }
119
+ }
120
+ }
121
+ return out;
122
+ }
123
+
124
+ /**
125
+ * Pure. What should a booting process do with this record?
126
+ * - not alive + exit marker → 'exited' (finished; outcome is on disk)
127
+ * - not alive, no marker → 'dead' (died without writing an outcome)
128
+ * - alive, different process → 'foreign-pid' (pid recycled; never ours)
129
+ * - alive, ours, past budget or log stalled past idleKillMs → 'over-budget'
130
+ * - alive, ours → 'adopt'
131
+ * `identity` is the live probe of record.pid; isDifferentProcess is
132
+ * fail-closed, so an incomplete identity on either side reads as "same".
133
+ */
134
+ function classifyAdoption(record, { identity, pidAlive, logMtimeMs, exitMarker, now = Date.now() } = {}) {
135
+ if (!pidAlive) return exitMarker ? 'exited' : 'dead';
136
+ if (exitMarker) return 'exited';
137
+ if (isDifferentProcess(record.identity, identity)) return 'foreign-pid';
138
+ const ceiling = record.budgetMs > 0 ? record.budgetMs : (record.maxDurationMs > 0 ? record.maxDurationMs : 0);
139
+ if (ceiling > 0 && Number.isFinite(record.startedAt) && now - record.startedAt > ceiling) return 'over-budget';
140
+ if (record.idleKillMs > 0 && Number.isFinite(logMtimeMs) && now - logMtimeMs > record.idleKillMs) return 'over-budget';
141
+ return 'adopt';
142
+ }
143
+
144
+ module.exports = {
145
+ writeSupervisorRecord, readSupervisorRecord, listLiveSupervisorRecords, classifyAdoption, hasExitMarker,
146
+ SUFFIX,
147
+ };
@@ -22,15 +22,19 @@
22
22
  */
23
23
 
24
24
  /**
25
- * @param {{ bootJobs: Array<object>, claudePidAlive: (pid: number) => boolean, hasLiveHolder: (dir: string, holders?: Set<string>) => boolean, cwdHolders?: Set<string> }} deps
25
+ * `rowPid(job)` resolves a running row's pid; scheduler.cjs passes the same
26
+ * supervisor-record → runtime.pid → run-log ladder partitionBootOrphans uses,
27
+ * so the two boot passes agree. Default: runtime.pid alone.
28
+ *
29
+ * @param {{ bootJobs: Array<object>, claudePidAlive: (pid: number) => boolean, hasLiveHolder: (dir: string, holders?: Set<string>) => boolean, cwdHolders?: Set<string>, rowPid?: (job: object) => number|null }} deps
26
30
  * @returns {(slug: string, entry: { worktree: string, branch: string|null }) => boolean}
27
31
  */
28
- function buildJobWorktreeIsLive({ bootJobs, claudePidAlive, hasLiveHolder, cwdHolders }) {
32
+ function buildJobWorktreeIsLive({ bootJobs, claudePidAlive, hasLiveHolder, cwdHolders, rowPid = (j) => j.runtime && j.runtime.pid }) {
29
33
  const runningPidBySlug = new Map();
30
34
  for (const j of Array.isArray(bootJobs) ? bootJobs : []) {
31
- if (j && j.status === 'running' && j.runtime && j.runtime.pid) {
32
- runningPidBySlug.set(j.slug, j.runtime.pid);
33
- }
35
+ if (!j || j.status !== 'running') continue;
36
+ const pid = rowPid(j);
37
+ if (pid) runningPidBySlug.set(j.slug, pid);
34
38
  }
35
39
 
36
40
  return function isLive(slug, entry) {
@@ -309,9 +309,10 @@ function evaluateLaunchGate(block, { now, claudeVersion } = {}) {
309
309
  * closed set so operators never have to open a transcript to tell a
310
310
  * non-start from an implementation failure from a verifier downgrade.
311
311
  */
312
- function deriveTerminalReason({ effectiveStatus, exitCode, verifyResult, sigtermOverride, worktreeIntegrationFailure }) {
312
+ function deriveTerminalReason({ effectiveStatus, exitCode, verifyResult, sigtermOverride, worktreeIntegrationFailure, budgetKill }) {
313
313
  if (worktreeIntegrationFailure) return 'worktree_integration_failed';
314
314
  if (effectiveStatus === 'completed') return 'completed';
315
+ if (budgetKill) return 'budget_exceeded';
315
316
  if (sigtermOverride) return 'signal_kill_with_commit';
316
317
  if (exitCode === 143 || exitCode === 137) return 'signal_kill';
317
318
  if (typeof exitCode === 'number' && exitCode !== 0) return `impl_failed:exit_${exitCode}`;
@@ -32,40 +32,20 @@
32
32
  'use strict';
33
33
 
34
34
  const http = require('node:http');
35
- const os = require('node:os');
36
35
  const path = require('node:path');
37
36
  const crypto = require('node:crypto');
38
37
  const fsp = require('node:fs/promises');
39
38
  const config = require('../config.cjs');
40
-
41
- const TOKEN_PATH = path.join(os.homedir(), '.claude', 'session-manager', 'admin-api.json');
39
+ const schedulerPaths = require('./schedulerPaths.cjs');
42
40
 
43
41
  /**
44
- * Mode-aware token file path, resolved fresh on every call (not cached at
42
+ * Mode-aware token file path, resolved fresh on every call (never cached at
45
43
  * module load) so a dev/e2e launch never collides with a production
46
- * instance's admin-api.json. index.cjs treats SM_DEV and SM_E2E as one OR'd
47
- * `isDev` boolean with no ordering between them; if both happen to be set,
48
- * this resolves to the SM_DEV path (checked first below) — an arbitrary but
49
- * harmless tie-break, since real launches only ever set one or the other.
44
+ * instance's admin-api.json. The SM_ADMIN_TOKEN_PATH / SM_DEV / SM_E2E ladder
45
+ * lives in schedulerPaths.adminTokenPath() (the SM_ADMIN_TOKEN_PATH override
46
+ * exists because unit tests once overwrote the live app's port+token).
50
47
  */
51
- function resolveTokenPath() {
52
- // Explicit override, highest priority. Exists because the unit tests call
53
- // createAdminHttp().start() directly with neither SM_DEV nor SM_E2E set,
54
- // which resolved to the PRODUCTION admin-api.json and overwrote the
55
- // running app's port+token with a throwaway test server's — silently
56
- // orphaning scheduler-mcp-server.cjs's access to the live app until the
57
- // next app restart (confirmed live 2026-08-01: `npm run test:unit` made
58
- // every scheduler_* MCP tool report "app is not running"). Tests point
59
- // this at a tmp file; production never sets it.
60
- if (process.env.SM_ADMIN_TOKEN_PATH) return process.env.SM_ADMIN_TOKEN_PATH;
61
- if (process.env.SM_DEV === '1') {
62
- return path.join(os.homedir(), '.claude', 'session-manager', 'admin-api.dev.json');
63
- }
64
- if (process.env.SM_E2E === '1') {
65
- return path.join(os.homedir(), '.claude', 'session-manager', 'admin-api.e2e.json');
66
- }
67
- return TOKEN_PATH;
68
- }
48
+ const resolveTokenPath = schedulerPaths.adminTokenPath;
69
49
 
70
50
  // Reserved health-check path, handled directly (not via registerRoute) so
71
51
  // start()'s post-boot self-check never depends on route-registration order
@@ -238,9 +218,11 @@ function createAdminHttp() {
238
218
 
239
219
  module.exports = {
240
220
  createAdminHttp,
241
- TOKEN_PATH,
242
221
  resolveTokenPath,
243
222
  timingSafeEqualStrings,
244
223
  readBody,
245
224
  sendJson,
246
225
  };
226
+
227
+ // Lazy getter (the token path is mode-aware and env-driven; never frozen at require).
228
+ Object.defineProperty(module.exports, 'TOKEN_PATH', { get: () => schedulerPaths.adminTokenPath(), enumerable: true });
@@ -47,6 +47,24 @@ const MCP_TOOL_CATALOG = [
47
47
  exampleArgs: { slug: 'add-mcp-tool-catalog', force: false },
48
48
  notes: 'Refuses a job whose status is already "completed" unless force:true is passed — resetting a completed job re-executes already-shipped work.',
49
49
  },
50
+ {
51
+ name: 'scheduler_pause',
52
+ group: 'scheduler',
53
+ purpose: "Pause the scheduler (user-initiated) via the session-manager app's admin API.",
54
+ whenToUse: 'Use to stop NEW job dispatch on request (e.g. before a risky change). Running jobs are never killed.',
55
+ whenNotToUse: 'Do not use to cancel a running job (use scheduler_cancel_job) or as a workaround for a stuck queue.',
56
+ exampleArgs: {},
57
+ notes: 'A manual pause survives app restart and is never auto-cleared by the rate-limit timer or network recovery — only scheduler_resume clears it.',
58
+ },
59
+ {
60
+ name: 'scheduler_resume',
61
+ group: 'scheduler',
62
+ purpose: "Clear any scheduler pause (manual or automatic) via the session-manager app's admin API.",
63
+ whenToUse: 'Use to restart dispatch after scheduler_pause, or to lift an auto-pause you have judged stale.',
64
+ whenNotToUse: 'Do not use to retry a parked needs_review job — that is scheduler_reset_job.',
65
+ exampleArgs: {},
66
+ notes: null,
67
+ },
50
68
  {
51
69
  name: 'scheduler_list_jobs',
52
70
  group: 'scheduler',
@@ -75,7 +93,10 @@ const MCP_TOOL_CATALOG = [
75
93
  + '`tag` and `agentType` are two DIFFERENT, independent fields — `tag` is WHAT KIND '
76
94
  + 'of work this PRD is (feature/bug/build/...); `agentType` is WHO executes it (a '
77
95
  + 'persona name, e.g. "dev-lead"). `agentType` defaults to "dev-lead" when omitted, '
78
- + 'and is rejected at write time if it does not name a real persona file.',
96
+ + 'and is rejected at write time if it does not name a real persona file. '
97
+ + 'If this PRD has no dependsOn of its own AND the target Epic already has incomplete PRDs, '
98
+ + '`disposition` ("append" or "new-head") is REQUIRED — the write is refused without it (a '
99
+ + 'headless job caller instead gets a logged "append" default, since no human is present to ask).',
79
100
  whenToUse: 'Use whenever new work should be queued into an already-approved Epic — this is the /develop path.',
80
101
  whenNotToUse: 'TWO DISTINCT FAILURE MODES if this tool is not usable — do not conflate them: '
81
102
  + '(a) this tool call is PRESENT in your tool list but ERRORS as app-not-running / admin '
@@ -221,65 +242,18 @@ const MCP_TOOL_CATALOG = [
221
242
  + 'delivered.',
222
243
  },
223
244
  {
224
- name: 'project_home_get_contract',
225
- group: 'project-home',
226
- purpose: "Fetch the self-sufficient contract for generating this project's Project Home pages via the "
227
- + "session-manager app's admin API — the protocol steps, the ProjectPageSummary/ProjectPagePicks JSON "
228
- + 'schemas, the component catalog (all 5 lenses: home, marketing, feature, architecture, brief), the '
229
- + 'absolute output paths, and the full pipeline spec text. Nothing in the response requires reading '
230
- + "session-manager's own repo — this is what makes generation work on a machine with only the npm "
231
- + 'package installed.',
232
- whenToUse: 'Call this FIRST, before composing any ProjectPageSummary content or picking variants, whenever '
233
- + 'a project-home-builder session starts generating Project Home pages for a project.',
234
- whenNotToUse: 'Do not begin composing a ProjectPageSummary or choosing variants before calling this — it '
235
- + 'must be the FIRST call a project-home-builder session makes; every other project_home_* tool assumes '
236
- + "you already have this response's schema/catalog/paths in hand.",
237
- exampleArgs: { cwd: '/home/bilko/Projects/session-manager' },
238
- notes: "cwd is optional — defaults to the calling session's own project root (SM_PROJECT_ROOT or process.cwd()) when omitted.",
239
- },
240
- {
241
- name: 'project_home_validate_summary',
242
- group: 'project-home',
243
- purpose: 'Validate a composed ProjectPageSummary against the schema from project_home_get_contract via the '
244
- + "session-manager app's admin API, without writing anything to disk.",
245
- whenToUse: 'Use after composing (or editing) a ProjectPageSummary and before calling project_home_render — '
246
- + 'fix every returned {field, message} error and re-validate until valid:true.',
247
- whenNotToUse: 'Do not skip straight to project_home_render on an unvalidated summary — render does '
248
- + 're-validate server-side and rejects with no writes on failure, but catching errors here first saves a '
249
- + 'round trip and gives per-field detail sooner.',
250
- exampleArgs: { cwd: '/home/bilko/Projects/session-manager', summary: { title: 'Example Project' } },
251
- notes: "cwd is optional — defaults to the calling session's own project root (SM_PROJECT_ROOT or process.cwd()) when omitted.",
252
- },
253
- {
254
- name: 'project_home_render',
255
- group: 'project-home',
256
- purpose: 'Render and write all 5 Project Home lenses (home, marketing, feature, architecture, brief) plus '
257
- + "summary.json/picks.json/manifest.json via the session-manager app's admin API — the only write path "
258
- + 'for Project Home generation.',
259
- whenToUse: 'Use once project_home_validate_summary reports valid:true and a variant has been picked for '
260
- + "every lens/slot in the contract's catalog.",
261
- whenNotToUse: 'Must NOT be used to fabricate summary content — every field of the summary passed here must '
262
- + 'trace to something concrete about the real project (an Epic goal, a source file/dir, a convention, a '
263
- + 'git log entry). This tool re-validates the summary server-side and rejects with no writes on schema '
264
- + 'failure, but it only checks shape, never truthfulness.',
265
- exampleArgs: {
266
- cwd: '/home/bilko/Projects/session-manager',
267
- summary: { title: 'Example Project' },
268
- picks: { home: { hero: 'variant-a' } },
269
- },
270
- notes: "cwd is optional — defaults to the calling session's own project root (SM_PROJECT_ROOT or process.cwd()) when omitted.",
271
- },
272
- {
273
- name: 'project_home_status',
245
+ name: 'project_home_write',
274
246
  group: 'project-home',
275
- purpose: "Report what already exists for this project's Project Home generation via the session-manager "
276
- + "app's admin API — summary.json/picks.json/each lens file's existence + mtime, and manifest.json's "
277
- + 'generatedAt.',
278
- whenToUse: 'Use before starting generation to see prior state, and again after project_home_render to '
279
- + 'confirm the new files landed.',
280
- whenNotToUse: 'Do not treat an all-absent result as an error — a project that has never generated a '
281
- + 'Project Home page yet still returns a well-formed status with every exists:false rather than failing.',
282
- exampleArgs: { cwd: '/home/bilko/Projects/session-manager' },
247
+ purpose: "Write this project's Project Home page — ONE self-contained HTML document — to "
248
+ + "session-manager-operations/project-pages/home.html via the session-manager app's admin API. "
249
+ + 'The only write path for Project Home.',
250
+ whenToUse: 'Use when asked to generate or refresh the project home page. Compose the whole page yourself '
251
+ + '(inline <style>/<script>, data: URIs for assets) from what is really true about the project, then '
252
+ + 'pass the full document as `html`.',
253
+ whenNotToUse: 'Never fabricate content — every claim must trace to something concrete in the project. The '
254
+ + 'document must be self-contained: the call is rejected for empty input, more than 1MB, any '
255
+ + '<script src>, <link href="http…">, @import, or url(http…) — no network egress.',
256
+ exampleArgs: { cwd: '/home/bilko/Projects/session-manager', html: '<!DOCTYPE html><html><body><h1>Example</h1></body></html>' },
283
257
  notes: "cwd is optional — defaults to the calling session's own project root (SM_PROJECT_ROOT or process.cwd()) when omitted.",
284
258
  },
285
259
  {
@@ -332,25 +306,6 @@ const MCP_RECIPES = [
332
306
  'The call only delivers a PROPOSED session in the receiving project — nothing runs until a human there presses Approve & start; there is no reply channel.',
333
307
  ],
334
308
  },
335
- {
336
- id: 'generate-project-home',
337
- title: "Generate a project's Project Home pages",
338
- steps: [
339
- "Call project_home_get_contract to get the protocol, ProjectPageSummary/ProjectPagePicks schemas, "
340
- + 'component catalog, absolute output paths, and pipeline spec — the FIRST call any '
341
- + 'project-home-builder session makes.',
342
- 'Author a ProjectPageSummary matching the contract\'s summarySchema — every field must trace to '
343
- + 'something concrete about the real project; never fabricate content.',
344
- 'Call project_home_validate_summary with { cwd, summary } and fix every {field, message} error it '
345
- + 'returns, re-validating until valid:true.',
346
- "For each lens and slot in the contract's catalog, choose exactly one variant id by judging the "
347
- + 'candidate variants against the summary you composed, assembling a ProjectPagePicks object '
348
- + '(lensId -> slotId -> variantId).',
349
- 'Call project_home_render with { cwd, summary, picks } to render all 5 lenses and write '
350
- + 'summary.json/picks.json/output files.',
351
- "Call project_home_status to confirm the new files landed and read manifest.json's generatedAt.",
352
- ],
353
- },
354
309
  ];
355
310
 
356
311
  for (const entry of MCP_TOOL_CATALOG) {
@@ -0,0 +1,205 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * needsReviewLedger.cjs — pure helpers that make `needs_review` episodes
5
+ * durable and queryable in history.jsonl.
6
+ *
7
+ * Before this module, `queueHistory.cjs` archived a job to history.jsonl
8
+ * only on a TERMINAL status (completed/failed/skipped) — a needs_review
9
+ * park never left queue.json's hot jobs[] (it isn't terminal; see
10
+ * queueHistory.cjs's partitionJobs), so it never reached durable history
11
+ * either. Profiling history.jsonl for "how much needs_review is really
12
+ * happening" therefore always returned zero, however loud the problem
13
+ * looked in the UI.
14
+ *
15
+ * Two line shapes are appended to the SAME history.jsonl a terminal row
16
+ * lands in, distinguished by `kind` so existing terminal-only readers can
17
+ * filter them out on sight (see scheduleJobTransitions.cjs, the actual
18
+ * write choke point, and its callers' audit in the owning PRD):
19
+ * - kind: 'needs_review_entry' — one per transition INTO needs_review
20
+ * - kind: 'needs_review_resolution' — one per transition OUT of needs_review,
21
+ * referencing the entry's own runId so the two can be paired
22
+ *
23
+ * Kept fs-free and pure (mirrors reaperHelpers.cjs's rationale) so the
24
+ * ladder-rung classification and rollup reduction are unit-testable without
25
+ * touching disk or importing scheduler.cjs (which requires electron).
26
+ */
27
+
28
+ // The 6 named ladder rungs a needs_review episode can resolve through
29
+ // (spawnJob's mechanical/resume/quarantine/auto-fix branches, the periodic
30
+ // reverifyNeedsReview heal pass, or an explicit human/API reset). Any
31
+ // resolution whose transitionJob `source` doesn't match a known automated
32
+ // rung is bucketed as 'manual-reset' — the conservative default for
33
+ // anything that isn't a recognized self-healing path.
34
+ const LADDER_RUNGS = [
35
+ 'mechanical-recovery',
36
+ 'resume-recovery',
37
+ 'quarantine',
38
+ 'auto-fix',
39
+ 'reverify',
40
+ 'manual-reset',
41
+ ];
42
+
43
+ /**
44
+ * classifyLadderRung({ source, reason }) → one of LADDER_RUNGS.
45
+ *
46
+ * Matches the exact `source` strings scheduleJobTransitions.cjs's callers in
47
+ * scheduler.cjs pass for each recovery-ladder rung (see that module's
48
+ * LEGAL_TRANSITIONS doc comment for the full call-site inventory). Pattern
49
+ * matching, not an exhaustive enum lookup, because 'spawnJob:dispatch' is
50
+ * shared between the ordinary pending->running dispatch and the
51
+ * needs_review->running resume-recovery dispatch — the two are only
52
+ * distinguishable by `reason` text.
53
+ */
54
+ function classifyLadderRung({ source, reason } = {}) {
55
+ const s = typeof source === 'string' ? source : '';
56
+ const r = typeof reason === 'string' ? reason : '';
57
+
58
+ if (s === 'scheduler:mechanicalRecovery') return 'mechanical-recovery';
59
+ if (s === 'spawnJob:dispatch' && /resume-recovery/i.test(r)) return 'resume-recovery';
60
+ if (s === 'spawnInvestigation:start' || s === 'spawnJob:auto-promote' || s === 'reverifyNeedsReview:auto-promote') {
61
+ return 'auto-fix';
62
+ }
63
+ if (s === 'reverifyNeedsReview:heal' || s === 'needsReviewAutoResolve') return 'reverify';
64
+ // No transitionJob call site resolves needs_review with a 'quarantine'
65
+ // source today (performLeftoverQuarantine cleans up leftover paths without
66
+ // itself changing job.status — the row stays needs_review until a later
67
+ // reverify/auto-resolve pass actually resolves it), but the rung is kept
68
+ // in LADDER_RUNGS/this classifier so a future direct-resolving quarantine
69
+ // path is classified correctly without another migration.
70
+ return 'manual-reset';
71
+ }
72
+
73
+ /**
74
+ * buildNeedsReviewEntryLine(job, entry) → JSONL-ready object.
75
+ *
76
+ * `entry` is the statusHistory record scheduleJobTransitions.cjs just
77
+ * pushed for this transition ({ from, to, reason, source, at }). The park
78
+ * reason prefers the structured `verifierVerdict` (a short, bucketable
79
+ * code) over the free-text `heldReason`/`error` fields, since those are the
80
+ * only reason evidence available for parks that never went through the
81
+ * verifier (e.g. a pidless-reap needs_review park).
82
+ */
83
+ function buildNeedsReviewEntryLine(job, entry) {
84
+ return {
85
+ kind: 'needs_review_entry',
86
+ slug: job?.slug ?? null,
87
+ cwd: job?.cwd ?? null,
88
+ epicId: job?.epicId ?? null,
89
+ runId: job?.runId ?? null,
90
+ at: entry?.at ?? new Date().toISOString(),
91
+ reason: job?.verifierVerdict ?? job?.heldReason ?? job?.error ?? null,
92
+ source: entry?.source ?? null,
93
+ };
94
+ }
95
+
96
+ /**
97
+ * buildNeedsReviewResolutionLine(job, entry, episode) → JSONL-ready object.
98
+ *
99
+ * `episode` is `{ runId, enteredAt }` captured off the job at entry time
100
+ * (needsReviewEntryRunId/needsReviewEnteredAt) — the resolution line
101
+ * references the ENTRY's runId (which may differ from `job.runId` by
102
+ * resolution time, e.g. after a resume-recovery re-dispatch minted a fresh
103
+ * one), not whatever runId the job carries right now.
104
+ */
105
+ function buildNeedsReviewResolutionLine(job, entry, episode) {
106
+ const enteredAtMs = episode?.enteredAt ? Date.parse(episode.enteredAt) : NaN;
107
+ const resolvedAtMs = entry?.at ? Date.parse(entry.at) : NaN;
108
+ const dwellMs = Number.isFinite(enteredAtMs) && Number.isFinite(resolvedAtMs)
109
+ ? Math.max(0, resolvedAtMs - enteredAtMs)
110
+ : null;
111
+ return {
112
+ kind: 'needs_review_resolution',
113
+ slug: job?.slug ?? null,
114
+ cwd: job?.cwd ?? null,
115
+ epicId: job?.epicId ?? null,
116
+ runId: episode?.runId ?? job?.runId ?? null,
117
+ at: entry?.at ?? new Date().toISOString(),
118
+ resolvedTo: entry?.to ?? null,
119
+ ladderRung: classifyLadderRung({ source: entry?.source, reason: entry?.reason }),
120
+ source: entry?.source ?? null,
121
+ dwellMs,
122
+ };
123
+ }
124
+
125
+ function percentile(sortedValues, p) {
126
+ if (!sortedValues.length) return null;
127
+ const idx = Math.min(sortedValues.length - 1, Math.max(0, Math.ceil((p / 100) * sortedValues.length) - 1));
128
+ return sortedValues[idx];
129
+ }
130
+
131
+ /**
132
+ * reduceNeedsReviewLedger(lines) → rollup.
133
+ *
134
+ * Pure O(n log n) reduction (the log n is the dwell-time sort for the
135
+ * percentiles) over a flat array of already-parsed history.jsonl rows
136
+ * (mixing needs_review_entry/needs_review_resolution/terminal rows is fine —
137
+ * anything that isn't one of the two kinds this module writes is ignored).
138
+ *
139
+ * Pairs an entry with its resolution by (slug, runId) — the same pairing
140
+ * key buildNeedsReviewResolutionLine's `episode.runId` guarantees matches
141
+ * the entry line's `runId`. A slug that parks twice (two different runIds)
142
+ * produces two independent pairs, matching two independent recovery
143
+ * episodes rather than being conflated into one.
144
+ *
145
+ * Returns:
146
+ * - byReason: { [reason]: entryCount } (reason = entry line's `reason`, or 'unknown')
147
+ * - byLadderRung: { [rung]: resolutionCount } (rung = one of LADDER_RUNGS)
148
+ * - unresolvedCount: entries with no matching resolution line yet
149
+ * - dwellMsP50 / dwellMsP90: percentiles over resolved episodes' dwellMs (null if none)
150
+ */
151
+ function reduceNeedsReviewLedger(lines) {
152
+ const list = Array.isArray(lines) ? lines : [];
153
+ const entriesByKey = new Map();
154
+ const resolutionsByKey = new Map();
155
+
156
+ for (const line of list) {
157
+ if (!line || typeof line !== 'object') continue;
158
+ const key = `${line.slug ?? ''}|${line.runId ?? ''}`;
159
+ if (line.kind === 'needs_review_entry') {
160
+ entriesByKey.set(key, line);
161
+ } else if (line.kind === 'needs_review_resolution') {
162
+ resolutionsByKey.set(key, line);
163
+ }
164
+ }
165
+
166
+ const byReason = {};
167
+ let unresolvedCount = 0;
168
+ for (const [key, entry] of entriesByKey) {
169
+ const reason = entry.reason ?? 'unknown';
170
+ byReason[reason] = (byReason[reason] ?? 0) + 1;
171
+ if (!resolutionsByKey.has(key)) unresolvedCount += 1;
172
+ }
173
+
174
+ const byLadderRung = {};
175
+ const dwellMsValues = [];
176
+ for (const [key, resolution] of resolutionsByKey) {
177
+ const rung = LADDER_RUNGS.includes(resolution.ladderRung) ? resolution.ladderRung : 'manual-reset';
178
+ byLadderRung[rung] = (byLadderRung[rung] ?? 0) + 1;
179
+ // Only count dwell for episodes that actually matched an entry — a
180
+ // resolution line with no paired entry (e.g. history truncated by
181
+ // retention before the entry aged out — never happens for needs_review
182
+ // rows today since they're excluded from partitionJobs' archivable set,
183
+ // but this stays defensive) has no truthful dwell to report.
184
+ if (entriesByKey.has(key) && typeof resolution.dwellMs === 'number' && Number.isFinite(resolution.dwellMs)) {
185
+ dwellMsValues.push(resolution.dwellMs);
186
+ }
187
+ }
188
+ dwellMsValues.sort((a, b) => a - b);
189
+
190
+ return {
191
+ byReason,
192
+ byLadderRung,
193
+ unresolvedCount,
194
+ dwellMsP50: percentile(dwellMsValues, 50),
195
+ dwellMsP90: percentile(dwellMsValues, 90),
196
+ };
197
+ }
198
+
199
+ module.exports = {
200
+ LADDER_RUNGS,
201
+ classifyLadderRung,
202
+ buildNeedsReviewEntryLine,
203
+ buildNeedsReviewResolutionLine,
204
+ reduceNeedsReviewLedger,
205
+ };
@@ -107,7 +107,7 @@ function writeLocalLine({ cwd, scope, level, tabId, epicId, tags, message, meta
107
107
  function reportToTelemetry({ cwd, scope, level, tabId, epicId, tags, message }) {
108
108
  try {
109
109
  const telemetryClient = require('./telemetryClient.cjs');
110
- const { projectRootOf } = require('../../../scripts/lib/activeSessions.cjs');
110
+ const { projectRootOf } = require('./activeSessions.cjs');
111
111
  const normalizedCwd = projectRootOf(cwd) || cwd;
112
112
  const autoTags = [
113
113
  `scope:${scope || 'unknown'}`,