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
@@ -1,25 +1,14 @@
1
1
  /**
2
- * projectHomeAdminRoutes.cjs — the app-side half of "the generating session
3
- * needs zero repo knowledge to build a Project Home page" (PRD:
4
- * project-home-admin-routes). Before this file, the project-home-builder
5
- * Epic had to know session-manager's own repo layout (where the catalog
6
- * lives, how to invoke the validator/renderer CLIs, which build step to run
7
- * first) — which is exactly why generation silently hung on a foreign
8
- * machine with no repo checked out. These four routes make the app itself
9
- * the authority on paths/schema/catalog/rendering, reusing the shipped
10
- * assets PRD 1088 put in the npm tarball (scripts/render-project-pages/dist/
11
- * renderer.cjs, scripts/project-pages-logic/dist/logic.cjs,
12
- * src/main/templates/project-pages-catalog.json,
13
- * src/main/templates/project-pages-pipeline.md) rather than reimplementing
14
- * any of them.
2
+ * projectHomeAdminRoutes.cjs — the single admin route behind the
3
+ * `project_home_write` MCP tool: POST /admin/project-home/write {cwd, html}
4
+ * writes one self-contained HTML document to
5
+ * `session-manager-operations/project-pages/home.html` (owner: project-home,
6
+ * see opsOwnership.cjs). Registered against the same injected
7
+ * localAdminHttp.cjs transport as prdAdminRoutes.cjs.
15
8
  *
16
- * Registered against the same injected localAdminHttp.cjs transport as
17
- * prdAdminRoutes.cjs — see that file's header for the transport contract
18
- * (`registerRoute(method, url, handler)`, handlers get
19
- * `(req, res, query: URLSearchParams)`).
20
- *
21
- * The MCP tool surface that calls these routes is the NEXT PRD in this
22
- * chain — this file only exposes the HTTP contract.
9
+ * "Self-contained" is enforced here, not trusted: the document must not
10
+ * reference any remote resource (`<script src>`, `<link href=http*>`,
11
+ * remote `@import` / `url(http*)`), so opening it never causes network egress.
23
12
  */
24
13
  'use strict';
25
14
 
@@ -28,64 +17,31 @@ const path = require('node:path');
28
17
  const { readBody, sendJson } = require('./localAdminHttp.cjs');
29
18
  const { resolveProjectContext } = require('./projectRootResolve.cjs');
30
19
  const { opsPath, OPS_ROOT_DIR } = require('./opsOwnership.cjs');
31
- const { PROJECT_PAGE_SUMMARY_JSON_SCHEMA, PROJECT_PAGE_PICKS_JSON_SCHEMA } = require('./projectPageSummarySchema.cjs');
32
20
  const config = require('../config.cjs');
33
21
  const { schemas } = require('../ipcSchemas.cjs');
34
22
 
35
- const LENSES = ['home', 'marketing', 'feature', 'architecture', 'brief'];
36
-
37
- // Shipped, build-time assets — fixed app-relative paths, read once and
38
- // cached in-process, same pattern as projectPages.cjs's
39
- // cachedDefaultHomeHtml (this file never changes at runtime; a repo edit
40
- // requires an app restart to pick up during dev, same as that module).
41
- const CATALOG_PATH = path.join(__dirname, '..', 'templates', 'project-pages-catalog.json');
42
- const SPEC_PATH = path.join(__dirname, '..', 'templates', 'project-pages-pipeline.md');
43
- const RENDERER_BUNDLE_PATH = path.join(__dirname, '..', '..', '..', 'scripts', 'render-project-pages', 'dist', 'renderer.cjs');
44
- const LOGIC_BUNDLE_PATH = path.join(__dirname, '..', '..', '..', 'scripts', 'project-pages-logic', 'dist', 'logic.cjs');
45
-
46
- let cachedCatalog = null;
47
- function loadCatalog() {
48
- if (!cachedCatalog) {
49
- const raw = fs.readFileSync(CATALOG_PATH, 'utf8');
50
- const parsed = JSON.parse(raw);
51
- // Drop $comment — it names the repo-relative source files the catalog
52
- // was generated from (build-tooling provenance), which would violate
53
- // this route's "never point the caller at a repo-relative path"
54
- // contract if echoed back verbatim.
55
- cachedCatalog = { lenses: parsed.lenses };
56
- }
57
- return cachedCatalog;
58
- }
59
-
60
- let cachedSpecText = null;
61
- function loadSpecText() {
62
- if (cachedSpecText === null) {
63
- cachedSpecText = fs.readFileSync(SPEC_PATH, 'utf8');
64
- }
65
- return cachedSpecText;
66
- }
67
-
68
- let cachedRenderer = null;
69
- function loadRenderer() {
70
- if (!cachedRenderer) {
71
- ({ renderProjectPages: cachedRenderer } = require(RENDERER_BUNDLE_PATH));
72
- }
73
- return cachedRenderer;
74
- }
75
-
76
- let cachedValidator = null;
77
- function loadValidator() {
78
- if (!cachedValidator) {
79
- ({ validateProjectPageSummary: cachedValidator } = require(LOGIC_BUNDLE_PATH));
23
+ const MAX_HTML_BYTES = 1024 * 1024;
24
+
25
+ // Each detector is a linear regex scan; the input is capped at 1MB first.
26
+ const REMOTE_REFERENCE_CHECKS = [
27
+ { re: /<script\b[^>]*\bsrc\s*=/i, message: '<script src> is not allowed — inline all scripts' },
28
+ { re: /<link\b[^>]*\bhref\s*=\s*["']?\s*(?:https?:)?\/\//i, message: '<link href="http(s)://…"> is not allowed — inline all styles' },
29
+ { re: /@import\b/i, message: '@import is not allowed — inline all styles' },
30
+ { re: /url\(\s*["']?\s*(?:https?:)?\/\//i, message: 'url(http(s)://…) is not allowed — embed assets as data: URIs' },
31
+ ];
32
+
33
+ /** -> null when `html` is acceptable, else a human-readable rejection reason. */
34
+ function validateHomeHtml(html) {
35
+ if (typeof html !== 'string' || html.trim().length === 0) return 'html must be a non-empty string';
36
+ if (Buffer.byteLength(html, 'utf8') > MAX_HTML_BYTES) return `html exceeds the ${MAX_HTML_BYTES}-byte limit`;
37
+ for (const { re, message } of REMOTE_REFERENCE_CHECKS) {
38
+ if (re.test(html)) return `html must be self-contained: ${message}`;
80
39
  }
81
- return cachedValidator;
40
+ return null;
82
41
  }
83
42
 
84
43
  /** Same definition crossProjectFeedback.cjs uses for "is this a Session
85
- * Manager project": it already has an operations root. Deliberately does
86
- * NOT require project-pages/ specifically to exist — a project that has
87
- * never generated a Project Home page yet is still a Session Manager
88
- * project (see the "no output dir yet" edge case on /status and /render). */
44
+ * Manager project": it already has an operations root. */
89
45
  function isSessionManagerProject(cwd) {
90
46
  try {
91
47
  return fs.statSync(path.join(cwd, OPS_ROOT_DIR)).isDirectory();
@@ -94,26 +50,8 @@ function isSessionManagerProject(cwd) {
94
50
  }
95
51
  }
96
52
 
97
- function projectPagesPaths(cwd) {
98
- const dir = opsPath(cwd, 'project-pages');
99
- return {
100
- summaryPath: path.join(dir, 'summary.json'),
101
- picksPath: path.join(dir, 'picks.json'),
102
- outputDir: path.join(dir, 'output'),
103
- };
104
- }
105
-
106
- /**
107
- * Resolve+validate a caller-supplied cwd into a real Session Manager
108
- * project root. Never throws — returns a structured verdict so every route
109
- * can turn a bad cwd into a JSON error instead of an unhandled throw (this
110
- * PRD's edge-case requirement).
111
- *
112
- * Deliberately does NOT call config.addAllowedRoot here — that widens
113
- * config.cjs's process-lifetime write boundary and belongs only on an
114
- * actual write path (the render route calls it itself, right before
115
- * writing), not on every GET this function also gates.
116
- */
53
+ /** Resolve a caller-supplied cwd into a real Session Manager project root.
54
+ * Never throws — returns a structured verdict. */
117
55
  function resolveCwd(rawCwd) {
118
56
  if (!rawCwd || typeof rawCwd !== 'string') {
119
57
  return { ok: false, status: 400, error: 'cwd is required' };
@@ -121,9 +59,8 @@ function resolveCwd(rawCwd) {
121
59
  if (!path.isAbsolute(rawCwd)) {
122
60
  return { ok: false, status: 400, error: 'cwd must be an absolute path' };
123
61
  }
124
- // Normalizes a worktree/ops-internal cwd to its real project root (see
125
- // projectRootResolve.cjs's header) so a headless job running inside an
126
- // Epic's worktree still targets the right project.
62
+ // Normalizes a worktree/ops-internal cwd to its real project root so a
63
+ // headless job inside an Epic's worktree still targets the right project.
127
64
  const resolved = resolveProjectContext({ cwd: rawCwd });
128
65
  const candidate = resolved.cwd || rawCwd;
129
66
  let realCwd;
@@ -142,261 +79,53 @@ function resolveCwd(rawCwd) {
142
79
  return { ok: true, cwd: realCwd };
143
80
  }
144
81
 
145
- /** Each validateProjectPageSummary error string is "<field.path> must ...".
146
- * Split it into { field, message } for the route's documented response
147
- * shape — the validator itself only returns flat strings. */
148
- function toFieldErrors(errors) {
149
- return errors.map((message) => {
150
- const m = /^([\w.]+)\s/.exec(message);
151
- return { field: m ? m[1] : 'summary', message };
152
- });
153
- }
154
-
155
- /**
156
- * Structural check for ProjectPagePicks (lensId -> slotId -> variantId) —
157
- * the shipped validator only covers `summary`, so this is the one guard
158
- * against handing the renderer bundle a shape it doesn't expect. Returns
159
- * "<field> must ..." strings in the same format toFieldErrors expects, so
160
- * both routes can share one error-shape.
161
- */
162
- function validatePicksShape(picks) {
163
- if (picks === null || typeof picks !== 'object' || Array.isArray(picks)) {
164
- return ['picks must be an object'];
165
- }
166
- const errors = [];
167
- for (const [lensId, slotMap] of Object.entries(picks)) {
168
- if (slotMap === null || typeof slotMap !== 'object' || Array.isArray(slotMap)) {
169
- errors.push(`picks.${lensId} must be an object`);
170
- continue;
171
- }
172
- for (const [slotId, variantId] of Object.entries(slotMap)) {
173
- if (typeof variantId !== 'string' || variantId.length === 0) {
174
- errors.push(`picks.${lensId}.${slotId} must be a non-empty string`);
175
- }
176
- }
177
- }
178
- return errors;
179
- }
180
-
181
- /** Shared readBody -> JSON.parse -> zod-schema.parse pipeline for the two
182
- * POST routes — collapses the 3-step try/catch chain that was previously
183
- * duplicated in both handlers into one call. Returns either
184
- * { ok: true, input } or { ok: false, status, error, details }. */
185
- async function parseJsonBody(req, schema) {
186
- const raw = await readBody(req);
187
- let body;
188
- try {
189
- body = raw ? JSON.parse(raw) : {};
190
- } catch {
191
- return { ok: false, status: 400, error: 'invalid JSON body' };
192
- }
193
- try {
194
- return { ok: true, input: schema.parse(body) };
195
- } catch (e) {
196
- return { ok: false, status: 400, error: 'invalid payload', details: e?.issues ?? e?.message };
197
- }
198
- }
199
-
200
- function buildProtocol() {
201
- return [
202
- 'Call GET /admin/project-home/status?cwd=<abs> to see what already exists for this project (summary.json, picks.json, and any prior output).',
203
- 'Compose a ProjectPageSummary object matching the schema in this response\'s "summarySchema" field. Never fabricate content — every field must trace to something concrete about this project (an Epic goal, a source file/dir, a convention, a git log entry). Omit "brief" entirely if this project has no generated brief.json yet; omit "quotes" entries rather than inventing a testimonial.',
204
- 'For each lens in this response\'s "catalog.lenses", and for each of that lens\'s slots, pick exactly one variant id by judging the candidate variants\' "note" text against the summary content you composed. Assemble the picks into a ProjectPagePicks object (lensId -> slotId -> variantId) matching "picksSchema".',
205
- 'POST /admin/project-home/validate-summary with {cwd, summary}. If "valid" is false, fix every listed {field, message} error and re-validate before continuing.',
206
- 'POST /admin/project-home/render with {cwd, summary, picks}. This re-validates the summary server-side (rejecting with no writes on failure), renders all 5 lenses, and writes summary.json, picks.json, and output/{home,marketing,feature,architecture,brief}.html + output/manifest.json at the absolute paths in this response\'s "paths" field.',
207
- 'Optionally call GET /admin/project-home/status again to confirm the new files landed and to read manifest.json\'s generatedAt.',
208
- ];
209
- }
210
-
211
82
  function registerAdminRoute(adminHttp) {
212
- // GET /admin/project-home/contract?cwd=<abs> — the self-sufficient
213
- // contract: protocol, schema, catalog, absolute paths, spec. Nothing in
214
- // this response should require the caller to read a repo-relative path.
215
- adminHttp.registerRoute('GET', '/admin/project-home/contract', async (req, res, query) => {
216
- let input;
83
+ // POST /admin/project-home/write {cwd, html}
84
+ adminHttp.registerRoute('POST', '/admin/project-home/write', async (req, res) => {
85
+ // Allow headroom over MAX_HTML_BYTES for JSON escaping; the html byte
86
+ // limit itself is enforced by validateHomeHtml below.
87
+ let raw;
217
88
  try {
218
- input = schemas.projectHomeAdminCwdQuery.parse(Object.fromEntries(query ?? []));
89
+ raw = await readBody(req, MAX_HTML_BYTES * 2 + 4096);
219
90
  } catch (e) {
220
- sendJson(res, 400, { ok: false, error: 'invalid query', details: e?.issues ?? e?.message });
91
+ sendJson(res, 400, { ok: false, error: `request body rejected: ${e?.message ?? 'unreadable'} (html limit is ${MAX_HTML_BYTES} bytes)` });
221
92
  return;
222
93
  }
223
- const resolved = resolveCwd(input.cwd);
224
- if (!resolved.ok) {
225
- sendJson(res, resolved.status, { ok: false, error: resolved.error });
94
+ let body;
95
+ try {
96
+ body = raw ? JSON.parse(raw) : {};
97
+ } catch {
98
+ sendJson(res, 400, { ok: false, error: 'invalid JSON body' });
226
99
  return;
227
100
  }
228
- // projectPagesPaths()->opsPath() can throw for a cwd resolveCwd accepted
229
- // but opsPath's own (separate) ephemeral-root check refuses — keep this
230
- // route's "never an unhandled throw" contract even on that edge.
101
+ let input;
231
102
  try {
232
- const { summaryPath, picksPath, outputDir } = projectPagesPaths(resolved.cwd);
233
- sendJson(res, 200, {
234
- ok: true,
235
- protocol: buildProtocol(),
236
- summarySchema: PROJECT_PAGE_SUMMARY_JSON_SCHEMA,
237
- picksSchema: PROJECT_PAGE_PICKS_JSON_SCHEMA,
238
- catalog: loadCatalog(),
239
- paths: { summaryPath, picksPath, outputDir },
240
- spec: { text: loadSpecText(), path: SPEC_PATH },
241
- });
103
+ input = schemas.projectHomeAdminWriteBody.parse(body);
242
104
  } catch (e) {
243
- sendJson(res, 400, { ok: false, error: e?.message ?? 'failed to resolve project-pages paths' });
244
- }
245
- });
246
-
247
- // POST /admin/project-home/validate-summary {cwd, summary}
248
- adminHttp.registerRoute('POST', '/admin/project-home/validate-summary', async (req, res) => {
249
- const parsed = await parseJsonBody(req, schemas.projectHomeAdminValidateSummaryBody);
250
- if (!parsed.ok) {
251
- sendJson(res, parsed.status, { ok: false, error: parsed.error, details: parsed.details });
252
- return;
253
- }
254
- const { input } = parsed;
255
- const resolved = resolveCwd(input.cwd);
256
- if (!resolved.ok) {
257
- sendJson(res, resolved.status, { ok: false, error: resolved.error });
258
- return;
259
- }
260
- const validateProjectPageSummary = loadValidator();
261
- const result = validateProjectPageSummary(input.summary);
262
- if (!result.ok) {
263
- sendJson(res, 200, { ok: true, valid: false, errors: toFieldErrors(result.errors) });
264
- return;
265
- }
266
- sendJson(res, 200, { ok: true, valid: true, errors: [] });
267
- });
268
-
269
- // POST /admin/project-home/render {cwd, summary, picks}
270
- adminHttp.registerRoute('POST', '/admin/project-home/render', async (req, res) => {
271
- const parsed = await parseJsonBody(req, schemas.projectHomeAdminRenderBody);
272
- if (!parsed.ok) {
273
- sendJson(res, parsed.status, { ok: false, error: parsed.error, details: parsed.details });
105
+ sendJson(res, 400, { ok: false, error: 'invalid payload', details: e?.issues ?? e?.message });
274
106
  return;
275
107
  }
276
- const { input } = parsed;
277
108
  const resolved = resolveCwd(input.cwd);
278
109
  if (!resolved.ok) {
279
110
  sendJson(res, resolved.status, { ok: false, error: resolved.error });
280
111
  return;
281
112
  }
282
- const validateProjectPageSummary = loadValidator();
283
- const validation = validateProjectPageSummary(input.summary);
284
- const picksErrors = validatePicksShape(input.picks);
285
- if (!validation.ok || picksErrors.length > 0) {
286
- sendJson(res, 400, {
287
- ok: false,
288
- valid: false,
289
- errors: [...toFieldErrors(validation.ok ? [] : validation.errors), ...toFieldErrors(picksErrors)],
290
- });
113
+ const rejection = validateHomeHtml(input.html);
114
+ if (rejection) {
115
+ sendJson(res, 400, { ok: false, error: rejection });
291
116
  return;
292
117
  }
293
-
294
- // Only a route that is actually about to write registers the cwd as a
295
- // write-allowed root — see resolveCwd's header for why this must not
296
- // happen on the GET routes above.
118
+ // Only a route that is actually about to write widens config.cjs's
119
+ // write boundary to this project root.
297
120
  config.addAllowedRoot(resolved.cwd);
298
121
  try {
299
- const result = await serializedPerCwd(resolved.cwd, () => doRender(resolved.cwd, input.summary, input.picks));
300
- sendJson(res, 200, { ok: true, ...result });
122
+ const homePath = opsPath(resolved.cwd, 'project-pages', 'home.html');
123
+ await config.writeTextAtomic(homePath, input.html, { writer: 'project-home' });
124
+ sendJson(res, 200, { ok: true, path: homePath, bytes: Buffer.byteLength(input.html, 'utf8') });
301
125
  } catch (e) {
302
- sendJson(res, 500, { ok: false, error: e?.message ?? 'render failed' });
126
+ sendJson(res, 500, { ok: false, error: e?.message ?? 'write failed' });
303
127
  }
304
128
  });
305
-
306
- // GET /admin/project-home/status?cwd=<abs>
307
- adminHttp.registerRoute('GET', '/admin/project-home/status', async (req, res, query) => {
308
- let input;
309
- try {
310
- input = schemas.projectHomeAdminCwdQuery.parse(Object.fromEntries(query ?? []));
311
- } catch (e) {
312
- sendJson(res, 400, { ok: false, error: 'invalid query', details: e?.issues ?? e?.message });
313
- return;
314
- }
315
- const resolved = resolveCwd(input.cwd);
316
- if (!resolved.ok) {
317
- sendJson(res, resolved.status, { ok: false, error: resolved.error });
318
- return;
319
- }
320
- try {
321
- const status = await getStatus(resolved.cwd);
322
- sendJson(res, 200, { ok: true, ...status });
323
- } catch (e) {
324
- sendJson(res, 400, { ok: false, error: e?.message ?? 'failed to read project-pages status' });
325
- }
326
- });
327
- }
328
-
329
- // Per-cwd in-process promise chain — the render route's only concurrency
330
- // guard. Sufficient because this admin HTTP server is a single Node
331
- // process (Electron main) with no worker pool for these routes; a second
332
- // concurrent render for the SAME cwd simply waits for the first to finish
333
- // writing before it starts, so output/ is never observed half-written by
334
- // two overlapping /render calls (it does NOT defend against a
335
- // project-home-builder Epic's own separate Write-tool calls racing this
336
- // route from a different OS process — see project-pages/README.md's
337
- // concurrency section). Entries are evicted once their chain settles so
338
- // this Map doesn't grow for the life of the process across many cwds.
339
- const renderChains = new Map();
340
- function serializedPerCwd(cwd, fn) {
341
- const prev = renderChains.get(cwd) || Promise.resolve();
342
- const next = prev.catch(() => {}).then(fn);
343
- const settled = next.catch(() => {});
344
- renderChains.set(cwd, settled);
345
- settled.finally(() => {
346
- if (renderChains.get(cwd) === settled) renderChains.delete(cwd);
347
- });
348
- return next;
349
- }
350
-
351
- async function doRender(cwd, summary, picks) {
352
- const renderProjectPages = loadRenderer();
353
- const pages = renderProjectPages(summary, picks);
354
- const generatedAt = new Date().toISOString();
355
- const { summaryPath, picksPath, outputDir } = projectPagesPaths(cwd);
356
-
357
- // The 5 lens files plus summary.json/picks.json have no ordering
358
- // dependency on each other — only manifest.json's presence is the
359
- // "generation complete" signal getStatus() relies on, so it's written
360
- // last, after everything else has landed.
361
- const lensWrites = LENSES.map((lens) => {
362
- const lensPath = path.join(outputDir, `${lens}.html`);
363
- return config.writeTextAtomic(lensPath, pages[lens], { writer: 'project-home' }).then(() => lensPath);
364
- });
365
- const [, , ...lensPaths] = await Promise.all([
366
- config.writeJson(summaryPath, summary, { writer: 'project-home' }).then(() => summaryPath),
367
- config.writeJson(picksPath, picks, { writer: 'project-home' }).then(() => picksPath),
368
- ...lensWrites,
369
- ]);
370
- const manifestPath = path.join(outputDir, 'manifest.json');
371
- await config.writeJson(manifestPath, { generatedAt }, { writer: 'project-home' });
372
-
373
- return { filesWritten: [summaryPath, picksPath, ...lensPaths, manifestPath], generatedAt };
374
- }
375
-
376
- async function getStatus(cwd) {
377
- const { summaryPath, picksPath, outputDir } = projectPagesPaths(cwd);
378
- const [summaryRes, picksRes, manifestRes, ...lensRes] = await Promise.all([
379
- config.readJson(summaryPath),
380
- config.readJson(picksPath),
381
- config.readJson(path.join(outputDir, 'manifest.json')),
382
- ...LENSES.map((lens) => config.readText(path.join(outputDir, `${lens}.html`))),
383
- ]);
384
-
385
- const output = {};
386
- LENSES.forEach((lens, i) => {
387
- const r = lensRes[i];
388
- output[lens] = { exists: r.exists, mtimeMs: r.exists ? r.mtimeMs : null };
389
- });
390
-
391
- return {
392
- summary: { exists: summaryRes.exists, mtimeMs: summaryRes.exists ? summaryRes.mtimeMs : null },
393
- picks: { exists: picksRes.exists, mtimeMs: picksRes.exists ? picksRes.mtimeMs : null },
394
- output,
395
- manifest: {
396
- exists: manifestRes.exists,
397
- generatedAt: manifestRes.exists && manifestRes.data ? (manifestRes.data.generatedAt ?? null) : null,
398
- },
399
- };
400
129
  }
401
130
 
402
- module.exports = { registerAdminRoute };
131
+ module.exports = { registerAdminRoute, validateHomeHtml, MAX_HTML_BYTES };
@@ -34,7 +34,7 @@
34
34
  */
35
35
 
36
36
  const path = require('node:path');
37
- const { projectRootOf, allProjectCwds } = require('../../../scripts/lib/activeSessions.cjs');
37
+ const { projectRootOf, allProjectCwds } = require('./activeSessions.cjs');
38
38
  const { readActiveIndex } = require('./epicMint.cjs');
39
39
  const { isEphemeralCwd } = require('./ephemeralCwd.cjs');
40
40
 
@@ -10,11 +10,11 @@
10
10
  // `nowMs` and `opts` so it's testable without touching the real clock or
11
11
  // disk. appendHistory/readHistory own the actual fs I/O.
12
12
 
13
- const os = require('os');
14
13
  const path = require('path');
15
14
  const fsp = require('fs').promises;
16
15
  const { HISTORY_RETENTION_MS } = require('./schedulerConfig.cjs');
17
16
  const { projectHistoryPath, stateCwds } = require('./queueStore.cjs');
17
+ const { queueHistoryPath } = require('./schedulerPaths.cjs');
18
18
  const { resolveIsFixPlan } = require('./fixPlanSlug.cjs');
19
19
 
20
20
  // Legacy global sidecar — READ-ONLY since 2026-07-31 (federated per-project
@@ -26,8 +26,9 @@ const { resolveIsFixPlan } = require('./fixPlanSlug.cjs');
26
26
  // Overridable via SM_HISTORY_PATH_OVERRIDE so queueHistory.test.cjs can point
27
27
  // this at an isolated tmpdir path instead of racing a live Electron
28
28
  // instance's scheduler.cjs, which writes this same machine-global file.
29
- const HISTORY_PATH = process.env.SM_HISTORY_PATH_OVERRIDE
30
- || path.join(os.homedir(), '.claude', 'session-manager', 'scheduled-plans', 'history.jsonl');
29
+ function historyPath() {
30
+ return process.env.SM_HISTORY_PATH_OVERRIDE || queueHistoryPath();
31
+ }
31
32
 
32
33
  /**
33
34
  * Every history file currently in play: each project's shard + the legacy
@@ -37,12 +38,12 @@ const HISTORY_PATH = process.env.SM_HISTORY_PATH_OVERRIDE
37
38
  * would leak real production history rows into an otherwise-isolated test.
38
39
  */
39
40
  function historyPaths() {
40
- if (process.env.SM_HISTORY_PATH_OVERRIDE) return [HISTORY_PATH];
41
+ if (process.env.SM_HISTORY_PATH_OVERRIDE) return [historyPath()];
41
42
  const paths = [];
42
43
  for (const cwd of stateCwds()) {
43
44
  try { paths.push(projectHistoryPath(cwd)); } catch { /* unusable cwd */ }
44
45
  }
45
- paths.push(HISTORY_PATH);
46
+ paths.push(historyPath());
46
47
  return paths;
47
48
  }
48
49
 
@@ -50,11 +51,18 @@ function historyPathFor(entry) {
50
51
  if (entry && entry.cwd) {
51
52
  try { return projectHistoryPath(entry.cwd); } catch { /* fall through */ }
52
53
  }
53
- return HISTORY_PATH;
54
+ return historyPath();
54
55
  }
55
56
 
57
+ // `kind` keeps a needs_review_entry/needs_review_resolution line (see
58
+ // needsReviewLedger.cjs) from colliding with a TERMINAL row that shares the
59
+ // same slug+runId — an entry and its resolution deliberately reference the
60
+ // SAME runId (that's how they're paired), so without `kind` in the dedupe
61
+ // key the second append would look like a replay of the first and silently
62
+ // never be written. Absent `kind` (every existing terminal row) buckets
63
+ // under 'terminal', so today's on-disk rows and callers are unaffected.
56
64
  function jobKey(job) {
57
- return `${job?.slug ?? ''}|${job?.runId ?? ''}`;
65
+ return `${job?.slug ?? ''}|${job?.runId ?? ''}|${job?.kind ?? 'terminal'}`;
58
66
  }
59
67
 
60
68
  /**
@@ -259,6 +267,12 @@ async function historyTerminalBySlug() {
259
267
  if (!line.trim()) continue;
260
268
  try {
261
269
  const j = JSON.parse(line);
270
+ // needs_review_entry/needs_review_resolution lines (needsReviewLedger.cjs)
271
+ // share this file but aren't a job's terminal record — skip them, or a
272
+ // needs_review episode logged for a slug AFTER its real terminal row
273
+ // (e.g. it re-parked on a later run) would overwrite a true 'completed'
274
+ // with a kind-less, status-less row read as "not terminal".
275
+ if (j?.kind && j.kind !== 'terminal') continue;
262
276
  if (j?.slug) map.set(j.slug, { status: j.status, finishedAt: j.finishedAt, landedCommit: j.landedCommit ?? null });
263
277
  } catch {
264
278
  // corrupt/partial line — ignore
@@ -301,6 +315,11 @@ async function completedSlugsForCwd(cwd) {
301
315
  if (!line.trim()) continue;
302
316
  try {
303
317
  const j = JSON.parse(line);
318
+ // Skip needs_review ledger lines (needsReviewLedger.cjs) — they carry
319
+ // no `status` field, so treating them as a real record would let a
320
+ // later needs_review park/resolution silently blank out this slug's
321
+ // last true terminal status.
322
+ if (j?.kind && j.kind !== 'terminal') continue;
304
323
  if (j?.slug) statusBySlug.set(j.slug, j.status);
305
324
  } catch {
306
325
  // corrupt/partial line — ignore
@@ -314,7 +333,7 @@ async function completedSlugsForCwd(cwd) {
314
333
  }
315
334
 
316
335
  module.exports = {
317
- HISTORY_PATH,
336
+ historyPath,
318
337
  partitionJobs,
319
338
  appendHistory,
320
339
  readHistory,