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,172 @@
1
+ /**
2
+ * scheduler-tick-wedge.test.cjs — tickQueue's serialized chain (tickTail) must
3
+ * survive a tick body that never settles: the body is bounded by a watchdog,
4
+ * the chain resets, and a late-resuming stale body is fenced off (no spawn, no
5
+ * mutate). mutateTail is never reset.
6
+ *
7
+ * Run: timeout 120 npx vitest run src/main/__tests__/scheduler-tick-wedge.test.cjs
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ import { test, expect, beforeAll, afterAll } from 'vitest';
13
+ const fs = require('node:fs');
14
+ const os = require('node:os');
15
+ const path = require('node:path');
16
+
17
+ let tmpHome;
18
+ let originalHome;
19
+ let originalClaudeBin;
20
+ let originalAutofix;
21
+ let originalWatchdog;
22
+ let originalClaudeVersion;
23
+ let scheduler;
24
+ let queueStore;
25
+ let auditLog;
26
+ let spawnMarker;
27
+
28
+ function auditKinds() {
29
+ try {
30
+ return fs.readFileSync(auditLog.auditLogPath(), 'utf8').split('\n').filter(Boolean).map((l) => JSON.parse(l).kind);
31
+ } catch { return []; }
32
+ }
33
+
34
+ beforeAll(() => {
35
+ originalHome = process.env.HOME;
36
+ originalClaudeBin = process.env.SM_CLAUDE_BIN;
37
+ originalAutofix = process.env.SM_AUTOFIX_DISABLE;
38
+ originalWatchdog = process.env.SM_TICK_WATCHDOG_MS;
39
+ // This test's spawn marker is written by the stub on ANY invocation, including the
40
+ // `claude --version` probe — so it needs the real probe, not the sandbox's pinned version.
41
+ originalClaudeVersion = process.env.SM_CLAUDE_VERSION;
42
+ delete process.env.SM_CLAUDE_VERSION;
43
+ tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-tick-wedge-'));
44
+ process.env.HOME = tmpHome;
45
+ process.env.SM_AUTOFIX_DISABLE = '1';
46
+ // Any spawn of the "claude" binary drops a marker file: proof spawnJob ran.
47
+ spawnMarker = path.join(tmpHome, 'claude-spawned.marker');
48
+ const stubPath = path.join(tmpHome, 'claude-stub.cjs');
49
+ const body = `require('node:fs').appendFileSync(${JSON.stringify(spawnMarker)}, 'x');\n`
50
+ + `process.stdout.write(JSON.stringify({ type: 'result', subtype: 'success', result: 'ok\\nSCHEDULER_VERDICT: PASS' }) + '\\n');\n`
51
+ + 'process.exit(0);\n';
52
+ fs.writeFileSync(stubPath, `#!${process.execPath}\n${body}`, { mode: 0o755 });
53
+ process.env.SM_CLAUDE_BIN = stubPath;
54
+ fs.mkdirSync(path.join(tmpHome, '.claude', 'session-manager'), { recursive: true });
55
+ fs.mkdirSync(path.join(tmpHome, '.claude', 'projects'), { recursive: true });
56
+ scheduler = require('../scheduler.cjs');
57
+ queueStore = require('../lib/queueStore.cjs');
58
+ auditLog = require('../lib/auditLog.cjs');
59
+ });
60
+
61
+ afterAll(() => {
62
+ process.env.HOME = originalHome;
63
+ for (const [k, v] of [['SM_CLAUDE_BIN', originalClaudeBin], ['SM_AUTOFIX_DISABLE', originalAutofix], ['SM_TICK_WATCHDOG_MS', originalWatchdog], ['SM_CLAUDE_VERSION', originalClaudeVersion]]) {
64
+ if (v === undefined) delete process.env[k]; else process.env[k] = v;
65
+ }
66
+ fs.rmSync(tmpHome, { recursive: true, force: true });
67
+ });
68
+
69
+ test('a never-settling tick body is declared wedged, the chain resets, the stale body is fenced, audit fires once', async () => {
70
+ process.env.SM_TICK_WATCHDOG_MS = '150';
71
+ const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-tick-wedge-project-'));
72
+ const projDir = fs.mkdtempSync(path.join(tmpHome, '.claude', 'projects', 'sm-tick-wedge-fake-'));
73
+ fs.writeFileSync(path.join(projDir, 'session.jsonl'), `${JSON.stringify({ cwd })}\n`, 'utf8');
74
+ const slug = `9003-tick-wedge-${process.pid}`;
75
+ await scheduler.writeQueue({
76
+ jobs: [{ slug, title: 'x', cwd, status: 'pending', dependsOn: [] }],
77
+ config: {},
78
+ paused: null,
79
+ });
80
+
81
+ // The first readMerged (the tick body's first await) hangs until released;
82
+ // every later call delegates to the real reader.
83
+ const realRead = queueStore.readMerged;
84
+ let release;
85
+ const hung = new Promise((r) => { release = r; });
86
+ let calls = 0;
87
+ queueStore.readMerged = function patched(...args) {
88
+ calls += 1;
89
+ if (calls === 1) return hung;
90
+ return realRead.apply(this, args);
91
+ };
92
+
93
+ try {
94
+ const wedgedResult = await scheduler.tickQueue();
95
+ expect(wedgedResult).toMatchObject({ fired: false, reason: 'wedged' });
96
+ expect(auditKinds().filter((k) => k === 'tick_wedged')).toHaveLength(1);
97
+
98
+ // A second wedge in the same episode (no normal tick between) must not re-audit.
99
+ calls = 0;
100
+ const hung2 = new Promise(() => {});
101
+ queueStore.readMerged = function patched2(...args) {
102
+ calls += 1;
103
+ if (calls === 1) return hung2;
104
+ return realRead.apply(this, args);
105
+ };
106
+ const again = await scheduler.tickQueue();
107
+ expect(again).toMatchObject({ reason: 'wedged' });
108
+ expect(auditKinds().filter((k) => k === 'tick_wedged')).toHaveLength(1);
109
+
110
+ // Next tick runs on a fresh chain (real reader, generous budget).
111
+ queueStore.readMerged = realRead;
112
+ process.env.SM_TICK_WATCHDOG_MS = '30000';
113
+ // Launch gates this case controls, so the dispatch assertion below does not
114
+ // depend on host state: the CPU load gate (loadavg1/cores > 0.85 -> 'load-deferred')
115
+ // via tickQueue's bypassLoadGate, and the memory gate ('memory-deferred', reads
116
+ // MemAvailable from /proc/meminfo) via a stubbed fs.readFileSync for that one path.
117
+ const realReadFileSync = fs.readFileSync;
118
+ fs.readFileSync = function patchedReadFileSync(file, ...rest) {
119
+ if (file === '/proc/meminfo') return 'MemAvailable: 67108864 kB\n';
120
+ return realReadFileSync.call(this, file, ...rest);
121
+ };
122
+ let fresh;
123
+ try {
124
+ fresh = await scheduler.tickQueue({ bypassLoadGate: true });
125
+ } finally {
126
+ fs.readFileSync = realReadFileSync;
127
+ }
128
+ expect(fresh.reason).not.toBe('wedged');
129
+
130
+ // Let the fresh tick's own spawn (if any) finish, then clear the marker.
131
+ // The fresh tick must have dispatched: wait for the stub's marker, then for
132
+ // the row to leave running/pending (spawnJob is fire-and-forget).
133
+ const deadline = Date.now() + 10_000;
134
+ while (Date.now() < deadline) {
135
+ const row = queueStore.readMergedSync().jobs.find((j) => j.slug === slug);
136
+ if (fs.existsSync(spawnMarker) && (!row || (row.status !== 'running' && row.status !== 'pending'))) break;
137
+ await new Promise((r) => setTimeout(r, 25));
138
+ }
139
+ expect(fs.existsSync(spawnMarker)).toBe(true);
140
+ fs.rmSync(spawnMarker, { force: true });
141
+
142
+ // Late-resume the stale body with a state that WOULD dispatch a pending job.
143
+ const before = queueStore.readMergedSync();
144
+ const staleState = {
145
+ ...before,
146
+ jobs: [{ slug: `9004-tick-wedge-stale-${process.pid}`, title: 'y', cwd, status: 'pending', dependsOn: [] }],
147
+ config: { ...(before.config || {}) },
148
+ };
149
+ release(staleState);
150
+ await new Promise((r) => setTimeout(r, 300));
151
+ expect(fs.existsSync(spawnMarker)).toBe(false);
152
+ } finally {
153
+ queueStore.readMerged = realRead;
154
+ }
155
+ });
156
+
157
+ test('a slow-but-healthy tick under the budget is not flagged as wedged', async () => {
158
+ process.env.SM_TICK_WATCHDOG_MS = '2000';
159
+ const realRead = queueStore.readMerged;
160
+ const before = auditKinds().filter((k) => k === 'tick_wedged').length;
161
+ queueStore.readMerged = async function slow(...args) {
162
+ await new Promise((r) => setTimeout(r, 100));
163
+ return realRead.apply(this, args);
164
+ };
165
+ try {
166
+ const r = await scheduler.tickQueue();
167
+ expect(r?.reason).not.toBe('wedged');
168
+ expect(auditKinds().filter((k) => k === 'tick_wedged').length).toBe(before);
169
+ } finally {
170
+ queueStore.readMerged = realRead;
171
+ }
172
+ });
@@ -4,12 +4,12 @@
4
4
  * requeue instead of burning a whole run + an auto-fix investigation on an
5
5
  * outage that has nothing to do with the code.
6
6
  *
7
- * Run: timeout 120 node --test src/main/__tests__/scheduler-transient-failure.test.cjs
7
+ * Run: timeout 300 npx vitest run src/main/__tests__/scheduler-transient-failure.test.cjs
8
8
  */
9
9
 
10
10
  'use strict';
11
11
 
12
- const { test } = require('node:test');
12
+ import { test } from 'vitest';
13
13
  const assert = require('node:assert/strict');
14
14
  const fs = require('node:fs');
15
15
  const os = require('node:os');
@@ -18,6 +18,7 @@ import { test, expect, beforeAll, afterAll, afterEach } from 'vitest';
18
18
  const fs = require('node:fs');
19
19
  const os = require('node:os');
20
20
  const path = require('node:path');
21
+ const claudeStub = require('../../../tests/helpers/claudeStub.cjs');
21
22
  const { execFileSync } = require('node:child_process');
22
23
 
23
24
  let tmpHome;
@@ -30,6 +31,24 @@ function git(args, cwd) {
30
31
  return execFileSync('git', args, { cwd, encoding: 'utf8' });
31
32
  }
32
33
 
34
+ // A completed job's PRD is archived immediately (archiveCompletedPrd, called
35
+ // for EVERY job that reaches effectiveStatus:'completed' — not just a
36
+ // retention-aged one), and the very next reconcile() pass (spawnJob's own
37
+ // broadcast({flush:true}) right after finishing) then finds the row's PRD
38
+ // gone and backfills+drops it from queue.json into history.jsonl in that
39
+ // SAME pass — see scheduler-reconcile-history-backfill.test.cjs. So a job
40
+ // that actually completed is found in EITHER queue.json OR history.jsonl,
41
+ // never guaranteed to still be the former.
42
+ function readTerminalRow(queuePath, projectCwd, slug) {
43
+ const jobs = JSON.parse(fs.readFileSync(queuePath, 'utf8')).jobs;
44
+ const row = jobs.find((j) => j.slug === slug);
45
+ if (row) return row;
46
+ const historyPath = path.join(projectCwd, 'session-manager-operations', 'scheduler', 'state', 'history.jsonl');
47
+ if (!fs.existsSync(historyPath)) return undefined;
48
+ const lines = fs.readFileSync(historyPath, 'utf8').trim().split('\n').filter(Boolean);
49
+ return lines.map((l) => JSON.parse(l)).reverse().find((j) => j.slug === slug);
50
+ }
51
+
33
52
  function initRepo(dir) {
34
53
  fs.mkdirSync(dir, { recursive: true });
35
54
  git(['init', '-q'], dir);
@@ -61,8 +80,9 @@ function writeProjectQueue(cwd, jobs) {
61
80
  // test can prove whether — and where — it was actually spawned, then emits a
62
81
  // clean stream-json success result.
63
82
  function writeClaudeStub() {
64
- const stubPath = path.join(os.tmpdir(), `sm-claude-stub-capdefer-${process.pid}-${Math.floor(Math.random() * 1e9)}.cjs`);
65
- const body = `
83
+ return claudeStub.writeClaudeStub({
84
+ allowCommitUnder: os.tmpdir(),
85
+ body: `
66
86
  const fs = require('fs');
67
87
  const path = require('path');
68
88
  const { execFileSync } = require('child_process');
@@ -73,14 +93,13 @@ function writeClaudeStub() {
73
93
  // fallback case), where the run parks in needs_review instead, which is
74
94
  // fine — this test only cares whether it ran in place, not its verdict.
75
95
  try {
76
- execFileSync('git', ['add', '-A'], { cwd: process.cwd() });
77
- execFileSync('git', ['commit', '-q', '-m', 'stub commit'], { cwd: process.cwd() });
96
+ runGit(['add', '-A']);
97
+ runGit(['commit', '-q', '-m', 'stub commit']);
78
98
  } catch { /* not a git repo — best-effort only */ }
79
99
  process.stdout.write(JSON.stringify({ type: 'result', subtype: 'success', result: 'ok\\nSCHEDULER_VERDICT: PASS' }) + '\\n');
80
100
  process.exit(0);
81
- `;
82
- fs.writeFileSync(stubPath, `#!${process.execPath}\n${body}\n`, { mode: 0o755 });
83
- return stubPath;
101
+ `,
102
+ });
84
103
  }
85
104
 
86
105
  beforeAll(() => {
@@ -111,7 +130,13 @@ test('a job held behind the worktree cap stays pending with a heldReason, is nev
111
130
  registerActiveProject(projectCwd);
112
131
 
113
132
  const slug = `1112-test-held-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
114
- const prdsDir = path.join(projectCwd, 'session-manager-operations', 'scheduler', 'prds');
133
+ // Epic-scoped, not the legacy flat prds/ dir: the flat dir is retired and
134
+ // swept into prds-archived/ by consolidateFlatPrds on every reconcile()
135
+ // pass once a row is no longer 'pending'/'running' — which would strand
136
+ // this test's row the moment it completes (reconcile()'s terminal-job
137
+ // "PRD gone -> archived on purpose" drop, see
138
+ // scheduler-reconcile-history-backfill.test.cjs).
139
+ const prdsDir = path.join(projectCwd, 'session-manager-operations', 'scheduler', 'epics', 'test-fixture-epic', 'prds');
115
140
  fs.mkdirSync(prdsDir, { recursive: true });
116
141
  fs.writeFileSync(path.join(prdsDir, `${slug}.md`), 'Held behind the worktree cap.', 'utf8');
117
142
  const queuePath = writeProjectQueue(projectCwd, [{ slug, status: 'pending', cwd: projectCwd }]);
@@ -147,8 +172,8 @@ test('a job held behind the worktree cap stays pending with a heldReason, is nev
147
172
 
148
173
  await spawnJob({ slug, cwd: projectCwd }, `${runId}-2`, runDir, projectCwd);
149
174
 
150
- jobs = JSON.parse(fs.readFileSync(queuePath, 'utf8')).jobs;
151
- row = jobs.find((j) => j.slug === slug);
175
+ row = readTerminalRow(queuePath, projectCwd, slug);
176
+ expect(row).toBeTruthy();
152
177
  expect(row.status).toBe('completed');
153
178
  expect(row.heldReason).toBeUndefined();
154
179
  } finally {
@@ -163,7 +188,8 @@ test('a non-git cwd still falls back to running in place (unchanged behaviour
163
188
  registerActiveProject(projectCwd);
164
189
 
165
190
  const slug = `1112-test-nongit-${process.pid}-${Math.floor(Math.random() * 1e6)}`;
166
- const prdsDir = path.join(projectCwd, 'session-manager-operations', 'scheduler', 'prds');
191
+ // Epic-scoped — see the sibling test above for why not the flat prds/ dir.
192
+ const prdsDir = path.join(projectCwd, 'session-manager-operations', 'scheduler', 'epics', 'test-fixture-epic', 'prds');
167
193
  fs.mkdirSync(prdsDir, { recursive: true });
168
194
  fs.writeFileSync(path.join(prdsDir, `${slug}.md`), 'Non-git cwd, must run in place.', 'utf8');
169
195
  const queuePath = writeProjectQueue(projectCwd, [{ slug, status: 'pending', cwd: projectCwd }]);
@@ -187,6 +213,20 @@ test('a non-git cwd still falls back to running in place (unchanged behaviour
187
213
  expect(row.worktreeFallbackReason).toMatch(/not a git repository/);
188
214
  // Ran IN PLACE — the marker landed directly in projectCwd, not a worktree.
189
215
  expect(fs.existsSync(path.join(projectCwd, 'ran-here.marker'))).toBe(true);
216
+
217
+ // A job losing worktree isolation must never be a silent downgrade
218
+ // discoverable only by reading queue.json afterwards — it must also land
219
+ // as a warn line in the durable ops error log, naming the slug, cwd, and
220
+ // reason.
221
+ const { todayFile } = require('../lib/opsErrorLog.cjs');
222
+ const errorLogPath = todayFile(projectCwd);
223
+ expect(fs.existsSync(errorLogPath)).toBe(true);
224
+ const logLines = fs.readFileSync(errorLogPath, 'utf8').trim().split('\n').map((l) => JSON.parse(l));
225
+ const fallbackLine = logLines.find((l) => l.scope === 'scheduler' && l.level === 'warn' && l.message.includes(slug));
226
+ expect(fallbackLine).toBeTruthy();
227
+ expect(fallbackLine.message).toMatch(/SHARED working tree/);
228
+ expect(fallbackLine.message).toMatch(/not a git repository/);
229
+ expect(fallbackLine.meta).toMatchObject({ slug, cwd: projectCwd, reason: expect.stringMatching(/not a git repository/) });
190
230
  } finally {
191
231
  fs.rmSync(projectCwd, { recursive: true, force: true });
192
232
  fs.rmSync(runDir, { recursive: true, force: true });
@@ -23,6 +23,7 @@ import { test, expect, beforeAll, afterAll, afterEach } from 'vitest';
23
23
  const fs = require('node:fs');
24
24
  const os = require('node:os');
25
25
  const path = require('node:path');
26
+ const claudeStub = require('../../../tests/helpers/claudeStub.cjs');
26
27
 
27
28
  let tmpHome;
28
29
  let originalHome;
@@ -48,16 +49,13 @@ afterEach(() => {
48
49
  // where it actually ran), captures the '-p' prompt, then emits a stream-json
49
50
  // success result and exits 0.
50
51
  function writeClaudeStub() {
51
- const stubPath = path.join(os.tmpdir(), `sm-claude-stub-${process.pid}-${Math.floor(Math.random() * 1e9)}.cjs`);
52
- const body = `
52
+ return claudeStub.writeClaudeStub({ body: `
53
53
  const fs = require('fs');
54
54
  const path = require('path');
55
55
  fs.writeFileSync(path.join(process.cwd(), 'ran-here.marker'), 'yes', 'utf8');
56
56
  process.stdout.write(JSON.stringify({ type: 'result', subtype: 'success', result: 'ok' }) + '\\n');
57
57
  process.exit(0);
58
- `;
59
- fs.writeFileSync(stubPath, `#!${process.execPath}\n${body}\n`, { mode: 0o755 });
60
- return stubPath;
58
+ ` });
61
59
  }
62
60
 
63
61
  test('executeJob spawns the child in execCwd (worktree) while resolving the PRD from job.cwd (main tree)', async () => {
@@ -0,0 +1,157 @@
1
+ /**
2
+ * usageSingleFlight.test.cjs — usage.cjs's fetchUsage() is the single owner
3
+ * of caching, single-flight de-duplication, and the shared usageCircuit for
4
+ * the /api/oauth/usage meter. Before this, the renderer's billing:fetch IPC
5
+ * handler owned the 30s OK-cache alone and the scheduler's pollLoop called
6
+ * fetchUsage() directly with no cache/dedup — two independent request
7
+ * streams against a rate-limiting endpoint, and a success on one path never
8
+ * reset the other's failure streak. This suite proves: (1) concurrent
9
+ * fetchUsage() callers coalesce into exactly one network fetch, (2) a
10
+ * success resets the shared circuit's streak regardless of which "caller"
11
+ * triggered the preceding failures, and (3) a 429's Retry-After suppresses
12
+ * the next request until it elapses.
13
+ *
14
+ * Never hits the live billing endpoint — global.fetch is stubbed throughout,
15
+ * and HOME is pointed at a scratch dir so no real credentials/cache file on
16
+ * this machine is ever read.
17
+ *
18
+ * usage.cjs (and config.cjs/credentials.cjs, which it requires) are loaded
19
+ * via Node's own require() — this is a .cjs test, not transformed ESM — so
20
+ * vi.mock()/vi.resetModules() do NOT intercept them the way they would for
21
+ * an ESM import graph; both also bake os.homedir() into module-level
22
+ * constants at first require (config.cjs's allowedRoots/WRITE_PREFIXES,
23
+ * usage.cjs's CACHE_PATH, credentials.cjs's CREDS_PATH). So this suite
24
+ * follows historyRollup.test.cjs's pattern instead: swap $HOME to a fresh
25
+ * tmp dir with a valid, far-from-expiry fake credentials file, purge
26
+ * Node's require.cache for every affected module, and re-require fresh
27
+ * each test.
28
+ *
29
+ * Run: timeout 120 npx vitest run src/main/__tests__/usageSingleFlight.test.cjs
30
+ */
31
+
32
+ 'use strict';
33
+
34
+ import { test, expect, vi, beforeEach, afterEach } from 'vitest';
35
+ import fs from 'node:fs';
36
+ import os from 'node:os';
37
+ import path from 'node:path';
38
+
39
+ function jsonResponse(status, body, headers = {}) {
40
+ return {
41
+ status,
42
+ ok: status >= 200 && status < 300,
43
+ text: async () => JSON.stringify(body),
44
+ json: async () => body,
45
+ headers: { get: (k) => headers[k.toLowerCase()] ?? null },
46
+ };
47
+ }
48
+
49
+ const usagePayload = { five_hour: { utilization: 10, resets_at: null }, seven_day: { utilization: 10, resets_at: null }, seven_day_sonnet: null, seven_day_opus: null, extra_usage: null };
50
+
51
+ const MODULES_TO_RELOAD = [
52
+ '../usage.cjs',
53
+ '../config.cjs',
54
+ '../lib/credentials.cjs',
55
+ '../lib/usageCircuit.cjs',
56
+ ];
57
+
58
+ function purgeRequireCache() {
59
+ for (const m of MODULES_TO_RELOAD) {
60
+ try { delete require.cache[require.resolve(m)]; } catch { /* not loaded yet */ }
61
+ }
62
+ }
63
+
64
+ let realHome;
65
+ let tmpHome;
66
+
67
+ beforeEach(() => {
68
+ realHome = process.env.HOME;
69
+ tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'usage-single-flight-test-'));
70
+ process.env.HOME = tmpHome;
71
+ fs.mkdirSync(path.join(tmpHome, '.claude'), { recursive: true });
72
+ fs.writeFileSync(
73
+ path.join(tmpHome, '.claude', '.credentials.json'),
74
+ JSON.stringify({
75
+ claudeAiOauth: {
76
+ accessToken: 'test-access-token',
77
+ refreshToken: 'test-refresh-token',
78
+ expiresAt: Date.now() + 1_000 * 60 * 60 * 24, // 24h out — never "expiring soon"
79
+ subscriptionType: 'max',
80
+ rateLimitTier: 'default_claude_max_20x',
81
+ },
82
+ }),
83
+ );
84
+ purgeRequireCache();
85
+ });
86
+
87
+ afterEach(() => {
88
+ process.env.HOME = realHome;
89
+ vi.unstubAllGlobals();
90
+ vi.restoreAllMocks();
91
+ purgeRequireCache();
92
+ fs.rmSync(tmpHome, { recursive: true, force: true });
93
+ });
94
+
95
+ test('two concurrent fetchUsage() calls produce exactly one HTTP fetch', async () => {
96
+ const { fetchUsage } = require('../usage.cjs');
97
+ let fetchCalls = 0;
98
+ vi.stubGlobal('fetch', vi.fn(async () => {
99
+ fetchCalls++;
100
+ // Slow enough that both callers are guaranteed to be in-flight together.
101
+ await new Promise((resolve) => setTimeout(resolve, 20));
102
+ return jsonResponse(200, usagePayload);
103
+ }));
104
+
105
+ const [a, b] = await Promise.all([fetchUsage(), fetchUsage()]);
106
+
107
+ expect(fetchCalls).toBe(1);
108
+ expect(a.kind).toBe('ok');
109
+ expect(b.kind).toBe('ok');
110
+ });
111
+
112
+ test('an ok result resets the shared circuit streak built up by prior failures', async () => {
113
+ const usage = require('../usage.cjs');
114
+ const circuit = usage.__usageCircuitForTest;
115
+
116
+ let call = 0;
117
+ vi.stubGlobal('fetch', vi.fn(async () => {
118
+ call++;
119
+ if (call <= 3) return jsonResponse(500, { error: 'boom' });
120
+ return jsonResponse(200, usagePayload);
121
+ }));
122
+
123
+ // Three consecutive transient failures — simulating the scheduler's poller
124
+ // failing repeatedly — trip the circuit open.
125
+ await usage.fetchUsage();
126
+ await usage.fetchUsage();
127
+ await usage.fetchUsage();
128
+ expect(circuit.state()).toBe('open');
129
+
130
+ // A later success — as if it came from the renderer's independent
131
+ // billing:fetch call, but reached through the SAME shared fetchUsage() —
132
+ // must reset the streak the scheduler's calls built up, since both
133
+ // callers share one circuit instance.
134
+ const ok = await usage.fetchUsage();
135
+ expect(ok.kind).toBe('ok');
136
+ expect(circuit.state()).toBe('closed');
137
+ });
138
+
139
+ test('a 429 with Retry-After suppresses the next request until it elapses', async () => {
140
+ const usage = require('../usage.cjs');
141
+
142
+ let fetchCalls = 0;
143
+ vi.stubGlobal('fetch', vi.fn(async () => {
144
+ fetchCalls++;
145
+ return jsonResponse(429, { error: { type: 'rate_limit_error' } }, { 'retry-after': '60' });
146
+ }));
147
+
148
+ const first = await usage.fetchUsage();
149
+ expect(first.kind).toBe('meter_rate_limited');
150
+ expect(first.retryAfterMs).toBe(60_000);
151
+
152
+ const second = await usage.fetchUsage();
153
+ expect(second.kind).toBe('meter_rate_limited');
154
+ expect(second.suppressed).toBe(true);
155
+ // No second network call — the suppression window hasn't elapsed.
156
+ expect(fetchCalls).toBe(1);
157
+ });
@@ -46,7 +46,7 @@ test('WORK_TYPES matches the tag ids declared in tagLibrary.ts TAG_LIBRARY, in o
46
46
  expect(tagMatches).toEqual(WORK_TYPES);
47
47
  });
48
48
 
49
- test('api.d.ts tag unions (line ~1166 and ~1687) both contain exactly the same 6 values', () => {
49
+ test('api.d.ts tag unions both contain exactly the same 6 values', () => {
50
50
  const src = fs.readFileSync(API_D_TS_PATH, 'utf8');
51
51
  const unionMatches = [...src.matchAll(/tag\?:\s*((?:'[^']+'\s*\|?\s*)+);/g)].map((m) =>
52
52
  m[1]
@@ -46,6 +46,10 @@ const {
46
46
  const WRITER = 'bilko-host';
47
47
  const PROJECT_PAGE_LENSES = new Set(['home', 'marketing', 'feature', 'architecture']);
48
48
 
49
+ function homePagePath(cwd) {
50
+ return opsPath(cwd, 'project-pages', 'home.html');
51
+ }
52
+
49
53
  function bilkoHostDir(cwd) {
50
54
  return opsPath(cwd, 'bilko-host');
51
55
  }
@@ -103,9 +107,10 @@ async function writeDocuments(cwd, documents) {
103
107
  async function get({ cwd }) {
104
108
  const realCwd = config.validatePath(cwd);
105
109
 
106
- const marketingResult = await config.readText(
107
- opsPath(realCwd, 'project-pages', 'output', 'marketing.html'),
108
- );
110
+ // The single Project Home page (project-pages/home.html) seeds the root
111
+ // document. `hasMarketingPage` is the renderer's legacy field name for
112
+ // "a root-document source page exists".
113
+ const marketingResult = await config.readText(homePagePath(realCwd));
109
114
  const pkgResult = await config.readJson(path.join(realCwd, 'package.json'));
110
115
  const pkg = pkgResult.exists && !pkgResult.parseError ? pkgResult.data : null;
111
116
 
@@ -142,15 +147,15 @@ async function get({ cwd }) {
142
147
  };
143
148
  }
144
149
 
145
- /** Seeds documents.json with a single root document (the Marketing Project Page) if it doesn't exist yet. */
150
+ /** Seeds documents.json with a single root document (the Project Home page, home.html) if it doesn't exist yet. */
146
151
  async function ensureSeededDocuments(cwd) {
147
152
  const existing = await readDocuments(cwd);
148
153
  if (existing) return existing;
149
154
  const seeded = [{
150
155
  id: crypto.randomUUID(),
151
156
  subpath: '',
152
- title: 'Project (Marketing)',
153
- source: { kind: 'project-page-lens', lens: 'marketing' },
157
+ title: 'Project (Home)',
158
+ source: { kind: 'project-page-lens', lens: 'home' },
154
159
  addedAt: new Date().toISOString(),
155
160
  }];
156
161
  await writeDocuments(cwd, seeded);
@@ -215,8 +220,12 @@ async function removeDocument({ cwd, id }) {
215
220
 
216
221
  async function resolveDocumentHtml(cwd, doc) {
217
222
  if (doc.source.kind === 'project-page-lens') {
223
+ // Only `home` is generated now (one home.html); the other lens names
224
+ // are legacy and resolve to their old output/<lens>.html if one exists.
218
225
  const result = await config.readText(
219
- opsPath(cwd, 'project-pages', 'output', `${doc.source.lens}.html`),
226
+ doc.source.lens === 'home'
227
+ ? homePagePath(cwd)
228
+ : opsPath(cwd, 'project-pages', 'output', `${doc.source.lens}.html`),
220
229
  );
221
230
  if (!result.exists) {
222
231
  throw new Error(`document "${doc.title}" points at the ${doc.source.lens} Project Page, but it hasn't been generated yet`);
@@ -0,0 +1,8 @@
1
+ {
2
+ "version": "0.88.0",
3
+ "gitSha": "8b723b765b5cb9f867b2ae244011b54ab3c81ef0",
4
+ "gitShortSha": "8b723b7",
5
+ "gitBranch": "HEAD",
6
+ "builtAt": "2026-09-19T14:31:26.080Z",
7
+ "dirty": false
8
+ }
@@ -44,7 +44,7 @@
44
44
 
45
45
  const { spawn } = require('node:child_process');
46
46
  const { ipcMain } = require('electron');
47
- const { resolveClaudeBin } = require('./lib/claudeBin.cjs');
47
+ const { resolveClaudeBin, claudeSpawnTarget } = require('./lib/claudeBin.cjs');
48
48
  const { cleanChildEnv, pathWithUserBins } = require('./lib/cleanEnv.cjs');
49
49
  const { recordExchange } = require('./exchanges.cjs');
50
50
  const { classifyToolUse } = require('./lib/toolUseClassify.cjs');
@@ -530,11 +530,14 @@ function executeRun({ tabId, sessionId, prompt, cwd, resume, silent, onSilentRes
530
530
  const fullPrompt = STOP_SIGNAL_INSTRUCTION + CHAT_MODE_TRUTH_INSTRUCTION + prompt;
531
531
 
532
532
  // sessionId is the Epic's claudeSessionId for an Epic-backed tab (see
533
- // comment above childEnv) — resolve that Epic's agentType persona model
534
- // so Chat and Terminal views of the same Epic launch with the same
535
- // model. Never throws; falls back to the hardcoded 'sonnet' literal when
536
- // there's no Epic, no agentType, or the persona has no model/'inherit' —
537
- // --model must never be left unpinned (CLAUDE.md model-pinning rule).
533
+ // comment above childEnv) — resolve that Epic's agentType persona model,
534
+ // project-overlay-then-global (agentModelResolve.cjs's
535
+ // readOverlayAwarePersonaModel). Terminal (EpicTerminalPane.tsx) calls
536
+ // this SAME function via the agents:resolve-epic-model IPC, so the two
537
+ // views of one Epic agree by construction, not by convention. Never
538
+ // throws; falls back to the hardcoded 'sonnet' literal when there's no
539
+ // Epic, no agentType, or the persona has no model/'inherit' — --model
540
+ // must never be left unpinned (CLAUDE.md model-pinning rule).
538
541
  const model = agentModelResolve.resolveEpicModel({ cwd, claudeSessionId: sessionId });
539
542
 
540
543
  // Build argv as an array — no shell: true, no string interpolation
@@ -568,7 +571,9 @@ function executeRun({ tabId, sessionId, prompt, cwd, resume, silent, onSilentRes
568
571
  // real-time NDJSON streaming; stderr piped for error-message capture.
569
572
  let child;
570
573
  try {
571
- child = spawn(claudeBin, args, {
574
+ const target = claudeSpawnTarget('chat', sessionId, claudeBin);
575
+ child = spawn(target.command, args, {
576
+ ...(target.argv0 ? { argv0: target.argv0 } : {}),
572
577
  cwd: execCwd,
573
578
  env: childEnv,
574
579
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -148,9 +148,8 @@ function validateWrite(realAbs) {
148
148
  // (PRD 825; scheduler.cjs remote.writePrd)
149
149
  // project-brief projectBrief.cjs's brief.json (PRD 837)
150
150
  // bilko-host bilkoHost.cjs's bundle prep
151
- // project-pages projectHomeAdminRoutes.cjs's app-side render path
152
- // (summary.json/picks.json/output/*; PRD:
153
- // project-home-admin-routes)
151
+ // project-pages projectHomeAdminRoutes.cjs's project_home_write
152
+ // path (home.html)
154
153
  // Paths come from opsOwnership.opsPath (PRD 1082) — the one ops-root
155
154
  // resolver — which THROWS for an ephemeral root (a linked worktree that
156
155
  // pty.cjs registered as an allowed root for its spawn cwd, or
@@ -17,7 +17,8 @@
17
17
  const { ipcMain } = require('electron');
18
18
  const { spawn } = require('node:child_process');
19
19
  const crypto = require('node:crypto');
20
- const { resolveClaudeBin } = require('./lib/claudeBin.cjs');
20
+ const { resolveClaudeBin, claudeSpawnTarget } = require('./lib/claudeBin.cjs');
21
+ const { withProcRole } = require('./lib/cleanEnv.cjs');
21
22
  const { extractJson } = require('./lib/extractJson.cjs');
22
23
  const { assertInsideHome } = require('./lib/insideHome.cjs');
23
24
  const { expandHome } = require('./lib/expandHome.cjs');
@@ -99,7 +100,8 @@ function runClaude(prompt, { model = 'sonnet', timeoutMs = 90_000, systemPrompt
99
100
  // stdin MUST be closed ('ignore') — `claude -p` otherwise blocks waiting
100
101
  // for piped stdin and returns empty. SM_KG_INTERNAL=1 tells the
101
102
  // prompt-logging hook to skip this invocation.
102
- const child = spawn(bin, args, { env: { ...process.env, SM_KG_INTERNAL: '1' }, stdio: ['ignore', 'pipe', 'pipe'] });
103
+ const target = claudeSpawnTarget('aux', 'docedit', bin);
104
+ const child = spawn(target.command, args, { env: withProcRole({ ...process.env, SM_KG_INTERNAL: '1' }, 'aux'), stdio: ['ignore', 'pipe', 'pipe'], ...(target.argv0 ? { argv0: target.argv0 } : {}) });
103
105
  let out = '';
104
106
  let err = '';
105
107
  // Cap accumulated stdout — the model output shouldn't grow `out` without