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,251 @@
1
+ 'use strict';
2
+
3
+ // Run: timeout 120 npx vitest run src/main/lib/__tests__/active-sessions.test.cjs
4
+
5
+ import { vi } from 'vitest';
6
+ const assert = require('node:assert/strict');
7
+ const fs = require('node:fs');
8
+ const os = require('node:os');
9
+ const path = require('node:path');
10
+ const { activeProjectCwds, allProjectCwds, bustProjectCwdCache } = require('../activeSessions.cjs');
11
+
12
+ function tmpDir() {
13
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'active-sessions-test-'));
14
+ }
15
+
16
+ function writeTranscript(projectsDir, slug, fileName, cwd, mtimeMsAgo) {
17
+ const projDir = path.join(projectsDir, slug);
18
+ fs.mkdirSync(projDir, { recursive: true });
19
+ const fp = path.join(projDir, fileName);
20
+ fs.writeFileSync(fp, JSON.stringify({ cwd }) + '\n');
21
+ const mtime = new Date(Date.now() - mtimeMsAgo);
22
+ fs.utimesSync(fp, mtime, mtime);
23
+ return fp;
24
+ }
25
+
26
+ test('two recent + one stale + one nonexistent → returns exactly the two recent existing cwds', () => {
27
+ const base = tmpDir();
28
+ try {
29
+ const projectsDir = path.join(base, 'projects');
30
+ const cwdA = path.join(base, 'proj-a');
31
+ const cwdB = path.join(base, 'proj-b');
32
+ const cwdC = path.join(base, 'proj-c'); // stale — exists on disk
33
+ const cwdMissing = path.join(base, 'proj-missing'); // never created
34
+ fs.mkdirSync(cwdA);
35
+ fs.mkdirSync(cwdB);
36
+ fs.mkdirSync(cwdC);
37
+
38
+ const MAX_AGE_MIN = 90;
39
+ writeTranscript(projectsDir, 'slug-a', 'a.jsonl', cwdA, 10 * 60 * 1000);
40
+ writeTranscript(projectsDir, 'slug-b', 'b.jsonl', cwdB, 10 * 60 * 1000);
41
+ writeTranscript(projectsDir, 'slug-c', 'c.jsonl', cwdC, 120 * 60 * 1000);
42
+ writeTranscript(projectsDir, 'slug-missing', 'm.jsonl', cwdMissing, 10 * 60 * 1000);
43
+
44
+ const result = activeProjectCwds(MAX_AGE_MIN, { projectsDir });
45
+
46
+ assert.equal(
47
+ result.length, 2,
48
+ `expected 2 cwds, got ${result.length}: ${JSON.stringify(result)}`,
49
+ );
50
+ assert.ok(result.includes(cwdA), `cwdA must be in result`);
51
+ assert.ok(result.includes(cwdB), `cwdB must be in result`);
52
+ assert.ok(!result.includes(cwdC), 'stale cwdC must not appear');
53
+ assert.ok(!result.includes(cwdMissing), 'nonexistent path must not appear');
54
+ } finally {
55
+ fs.rmSync(base, { recursive: true, force: true });
56
+ }
57
+ });
58
+
59
+ test('duplicate cwd across two project dirs → deduped to one entry', () => {
60
+ const base = tmpDir();
61
+ try {
62
+ const projectsDir = path.join(base, 'projects');
63
+ const cwdA = path.join(base, 'proj-a');
64
+ fs.mkdirSync(cwdA);
65
+
66
+ writeTranscript(projectsDir, 'slug-1', 'a.jsonl', cwdA, 5 * 60 * 1000);
67
+ writeTranscript(projectsDir, 'slug-2', 'b.jsonl', cwdA, 5 * 60 * 1000);
68
+
69
+ const result = activeProjectCwds(90, { projectsDir });
70
+ assert.equal(result.length, 1, 'duplicate cwd must be deduped');
71
+ assert.equal(result[0], cwdA);
72
+ } finally {
73
+ fs.rmSync(base, { recursive: true, force: true });
74
+ }
75
+ });
76
+
77
+ test('missing projects dir → empty result', () => {
78
+ const base = tmpDir();
79
+ try {
80
+ const result = activeProjectCwds(90, { projectsDir: path.join(base, 'no-such-dir') });
81
+ assert.deepEqual(result, []);
82
+ } finally {
83
+ fs.rmSync(base, { recursive: true, force: true });
84
+ }
85
+ });
86
+
87
+ test('prompts.jsonl absent → transcripts still detected (sole detection path)', () => {
88
+ const base = tmpDir();
89
+ try {
90
+ // No knowledge-log / prompts.jsonl anywhere in this tmp tree — the
91
+ // function must not need it. logPath, if passed, is a harmless no-op.
92
+ const projectsDir = path.join(base, 'projects');
93
+ const cwdA = path.join(base, 'proj-a');
94
+ fs.mkdirSync(cwdA);
95
+
96
+ writeTranscript(projectsDir, 'slug-a', 'a.jsonl', cwdA, 5 * 60 * 1000);
97
+
98
+ const missingLogPath = path.join(base, 'knowledge-log', 'prompts.jsonl');
99
+ assert.ok(!fs.existsSync(missingLogPath), 'sanity: log path must not exist');
100
+
101
+ const result = activeProjectCwds(90, { projectsDir, logPath: missingLogPath });
102
+ assert.deepEqual(result, [cwdA]);
103
+ } finally {
104
+ fs.rmSync(base, { recursive: true, force: true });
105
+ }
106
+ });
107
+
108
+ test('fresh transcript mtime with no prompt log present → cwd is returned', () => {
109
+ const base = tmpDir();
110
+ try {
111
+ const projectsDir = path.join(base, 'projects');
112
+ const cwdA = path.join(base, 'proj-fresh');
113
+ fs.mkdirSync(cwdA);
114
+
115
+ writeTranscript(projectsDir, 'slug-fresh', 'fresh.jsonl', cwdA, 60 * 1000); // 1 min ago
116
+
117
+ const result = activeProjectCwds(90, { projectsDir });
118
+ assert.equal(result.length, 1);
119
+ assert.equal(result[0], cwdA);
120
+ } finally {
121
+ fs.rmSync(base, { recursive: true, force: true });
122
+ }
123
+ });
124
+
125
+ test('garbage lines in transcript are skipped, cwd read from last parseable line', () => {
126
+ const base = tmpDir();
127
+ try {
128
+ const projectsDir = path.join(base, 'projects');
129
+ const cwdA = path.join(base, 'proj-a');
130
+ fs.mkdirSync(cwdA);
131
+
132
+ const projDir = path.join(projectsDir, 'slug-a');
133
+ fs.mkdirSync(projDir, { recursive: true });
134
+ const fp = path.join(projDir, 'a.jsonl');
135
+ fs.writeFileSync(fp, [
136
+ JSON.stringify({ cwd: cwdA }),
137
+ 'not json at all!!!',
138
+ '{ broken',
139
+ ].join('\n') + '\n');
140
+ const mtime = new Date(Date.now() - 5 * 60 * 1000);
141
+ fs.utimesSync(fp, mtime, mtime);
142
+
143
+ const result = activeProjectCwds(90, { projectsDir });
144
+ assert.equal(result.length, 1);
145
+ assert.equal(result[0], cwdA);
146
+ } finally {
147
+ fs.rmSync(base, { recursive: true, force: true });
148
+ }
149
+ });
150
+
151
+ test('5 consecutive allProjectCwds() calls with the same args perform ONE directory scan', () => {
152
+ const base = tmpDir();
153
+ try {
154
+ const projectsDir = path.join(base, 'projects');
155
+ const cwdA = path.join(base, 'proj-a');
156
+ fs.mkdirSync(cwdA);
157
+ writeTranscript(projectsDir, 'slug-a', 'a.jsonl', cwdA, 5 * 60 * 1000);
158
+
159
+ bustProjectCwdCache();
160
+ const readdirSpy = vi.spyOn(fs, 'readdirSync');
161
+
162
+ const first = allProjectCwds({ projectsDir });
163
+ const callsAfterFirst = readdirSpy.mock.calls.length;
164
+ assert.ok(callsAfterFirst > 0, 'first call must actually scan');
165
+
166
+ for (let i = 0; i < 4; i++) {
167
+ const again = allProjectCwds({ projectsDir });
168
+ assert.deepEqual(again, first);
169
+ }
170
+
171
+ assert.equal(
172
+ readdirSpy.mock.calls.length, callsAfterFirst,
173
+ 'the 4 repeat calls must not perform any additional directory scan',
174
+ );
175
+ } finally {
176
+ vi.restoreAllMocks();
177
+ fs.rmSync(base, { recursive: true, force: true });
178
+ }
179
+ });
180
+
181
+ test('bustProjectCwdCache() forces the very next call to rescan', () => {
182
+ const base = tmpDir();
183
+ try {
184
+ const projectsDir = path.join(base, 'projects');
185
+ const cwdA = path.join(base, 'proj-a');
186
+ fs.mkdirSync(cwdA);
187
+ writeTranscript(projectsDir, 'slug-a', 'a.jsonl', cwdA, 5 * 60 * 1000);
188
+
189
+ bustProjectCwdCache();
190
+ allProjectCwds({ projectsDir }); // warm the cache
191
+
192
+ const readdirSpy = vi.spyOn(fs, 'readdirSync');
193
+ allProjectCwds({ projectsDir });
194
+ assert.equal(readdirSpy.mock.calls.length, 0, 'a cache hit must not scan');
195
+
196
+ bustProjectCwdCache();
197
+ allProjectCwds({ projectsDir });
198
+ assert.ok(readdirSpy.mock.calls.length > 0, 'a bust must force a rescan on the next call');
199
+ } finally {
200
+ vi.restoreAllMocks();
201
+ fs.rmSync(base, { recursive: true, force: true });
202
+ }
203
+ });
204
+
205
+ test('allProjectCwds() and activeProjectCwds(90) do not share a cache entry', () => {
206
+ const base = tmpDir();
207
+ try {
208
+ const projectsDir = path.join(base, 'projects');
209
+ const cwdFresh = path.join(base, 'proj-fresh');
210
+ const cwdStale = path.join(base, 'proj-stale');
211
+ fs.mkdirSync(cwdFresh);
212
+ fs.mkdirSync(cwdStale);
213
+ writeTranscript(projectsDir, 'slug-fresh', 'fresh.jsonl', cwdFresh, 5 * 60 * 1000);
214
+ writeTranscript(projectsDir, 'slug-stale', 'stale.jsonl', cwdStale, 120 * 60 * 1000);
215
+
216
+ bustProjectCwdCache();
217
+ // allProjectCwds (maxAgeMin Infinity, maxCwds 500) sees both — cached
218
+ // first under its own key.
219
+ const all = allProjectCwds({ projectsDir });
220
+ assert.ok(all.includes(cwdFresh) && all.includes(cwdStale));
221
+
222
+ // activeProjectCwds(90) must recompute under its OWN key, not return
223
+ // allProjectCwds' cached (Infinity, 500) result — proving the cache key
224
+ // includes maxAgeMin/maxCwds, not just projectsDir.
225
+ const active = activeProjectCwds(90, { projectsDir });
226
+ assert.ok(active.includes(cwdFresh), 'fresh cwd must be present');
227
+ assert.ok(!active.includes(cwdStale), 'stale cwd must be excluded by the 90-min filter');
228
+ } finally {
229
+ fs.rmSync(base, { recursive: true, force: true });
230
+ }
231
+ });
232
+
233
+ test('missing projectsDir returns [] and does not poison the cache once the dir appears', () => {
234
+ const base = tmpDir();
235
+ try {
236
+ const projectsDir = path.join(base, 'projects');
237
+ bustProjectCwdCache();
238
+
239
+ assert.deepEqual(activeProjectCwds(90, { projectsDir }), []);
240
+ assert.deepEqual(activeProjectCwds(90, { projectsDir }), []);
241
+
242
+ const cwdA = path.join(base, 'proj-a');
243
+ fs.mkdirSync(cwdA);
244
+ writeTranscript(projectsDir, 'slug-a', 'a.jsonl', cwdA, 5 * 60 * 1000);
245
+
246
+ const result = activeProjectCwds(90, { projectsDir });
247
+ assert.deepEqual(result, [cwdA], 'the dir appearing later must be visible, not stuck on a cached []');
248
+ } finally {
249
+ fs.rmSync(base, { recursive: true, force: true });
250
+ }
251
+ });
@@ -0,0 +1,38 @@
1
+ /**
2
+ * auditLog.test.cjs — readTail returns a bounded reverse tail of complete lines.
3
+ *
4
+ * Run: timeout 120 npx vitest run src/main/lib/__tests__/auditLog.test.cjs
5
+ */
6
+ 'use strict';
7
+
8
+ import { test, expect, beforeAll, afterAll } from 'vitest';
9
+ const fs = require('node:fs');
10
+ const os = require('node:os');
11
+ const path = require('node:path');
12
+ const { readTail } = require('../auditLog.cjs');
13
+
14
+ let dir;
15
+ beforeAll(() => { dir = fs.mkdtempSync(path.join(os.tmpdir(), 'auditlog-tail-')); });
16
+ afterAll(() => { fs.rmSync(dir, { recursive: true, force: true }); });
17
+
18
+ test('missing file → []', () => {
19
+ expect(readTail(1024, path.join(dir, 'nope.jsonl'))).toEqual([]);
20
+ });
21
+
22
+ test('window larger than file → every line', () => {
23
+ const f = path.join(dir, 'small.jsonl');
24
+ fs.writeFileSync(f, '{"n":1}\n{"n":2}\n');
25
+ expect(readTail(1_000_000, f)).toEqual(['{"n":1}', '{"n":2}']);
26
+ });
27
+
28
+ test('bounded window drops the partial leading line and keeps the newest', () => {
29
+ const f = path.join(dir, 'big.jsonl');
30
+ const lines = Array.from({ length: 1000 }, (_, i) => JSON.stringify({ n: i, pad: 'x'.repeat(50) }));
31
+ fs.writeFileSync(f, lines.join('\n') + '\n');
32
+ const tail = readTail(500, f);
33
+ expect(tail.length).toBeGreaterThan(0);
34
+ expect(tail.length).toBeLessThan(20);
35
+ expect(tail[tail.length - 1]).toBe(lines[999]);
36
+ for (const l of tail) expect(() => JSON.parse(l)).not.toThrow();
37
+ expect(readTail(0, f)).toEqual([]);
38
+ });
@@ -0,0 +1,107 @@
1
+ /**
2
+ * bootSelfHeal.test.cjs — the epic-index self-heal scan must be SCHEDULED
3
+ * off app.whenReady()'s synchronous critical path, not executed inline, so
4
+ * the ~270ms allProjectCwds() scan can't compete with the renderer's first
5
+ * IPC round trips (schedule.state, billing.fetch, teams.list) for the event
6
+ * loop at boot.
7
+ *
8
+ * Run: timeout 300 npx vitest run src/main/lib/__tests__/bootSelfHeal.test.cjs
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ import { test, expect, vi } from 'vitest';
14
+ const { EventEmitter } = require('node:events');
15
+ const { scheduleBootSelfHeal, runEpicIndexSelfHeal } = require('../bootSelfHeal.cjs');
16
+
17
+ function flush(times = 2) {
18
+ return (async () => {
19
+ for (let i = 0; i < times; i++) {
20
+ await new Promise((resolve) => { setImmediate(resolve); });
21
+ }
22
+ })();
23
+ }
24
+
25
+ function makeDeps() {
26
+ return {
27
+ fs: { existsSync: vi.fn(() => true), readFileSync: vi.fn(() => '{}') },
28
+ allProjectCwds: vi.fn(() => ['/proj-a']),
29
+ promptSessionsActiveIndexPath: (cwd) => `${cwd}/active-index.json`,
30
+ rebuildActiveIndex: vi.fn(() => ({ rows: [], skipped: [] })),
31
+ logs: { writeLine: vi.fn() },
32
+ };
33
+ }
34
+
35
+ function fakeWindow() {
36
+ const webContents = new EventEmitter();
37
+ webContents.isDestroyed = () => false;
38
+ return { isDestroyed: () => false, webContents };
39
+ }
40
+
41
+ test('scheduleBootSelfHeal does not run the scan synchronously — only after did-finish-load', async () => {
42
+ const deps = makeDeps();
43
+ const mainWindow = fakeWindow();
44
+
45
+ scheduleBootSelfHeal(mainWindow, deps);
46
+ expect(deps.allProjectCwds).not.toHaveBeenCalled();
47
+
48
+ mainWindow.webContents.emit('did-finish-load');
49
+ expect(deps.allProjectCwds).toHaveBeenCalledTimes(1);
50
+ await flush();
51
+ });
52
+
53
+ test('a window destroyed before did-finish-load falls back to an unconditional setImmediate run', async () => {
54
+ const deps = makeDeps();
55
+ const mainWindow = fakeWindow();
56
+
57
+ scheduleBootSelfHeal(mainWindow, deps);
58
+ mainWindow.webContents.emit('destroyed');
59
+ expect(deps.allProjectCwds).not.toHaveBeenCalled();
60
+
61
+ await flush();
62
+ expect(deps.allProjectCwds).toHaveBeenCalledTimes(1);
63
+ });
64
+
65
+ test('a headless / no-window boot still self-heals via setImmediate', async () => {
66
+ const deps = makeDeps();
67
+
68
+ scheduleBootSelfHeal(null, deps);
69
+ expect(deps.allProjectCwds).not.toHaveBeenCalled();
70
+
71
+ await flush();
72
+ expect(deps.allProjectCwds).toHaveBeenCalledTimes(1);
73
+ });
74
+
75
+ test('runs exactly once per boot even if did-finish-load and destroyed both fire', async () => {
76
+ const deps = makeDeps();
77
+ const mainWindow = fakeWindow();
78
+
79
+ scheduleBootSelfHeal(mainWindow, deps);
80
+ mainWindow.webContents.emit('did-finish-load');
81
+ mainWindow.webContents.emit('destroyed');
82
+ await flush();
83
+
84
+ expect(deps.allProjectCwds).toHaveBeenCalledTimes(1);
85
+ });
86
+
87
+ test('runEpicIndexSelfHeal rebuilds only a project whose index is missing or unparseable — a clean index is never rewritten', async () => {
88
+ const files = {
89
+ '/clean-proj/active-index.json': '{"sessions":{}}',
90
+ '/broken-proj/active-index.json': '{ not json',
91
+ };
92
+ const fs = {
93
+ existsSync: (p) => Object.prototype.hasOwnProperty.call(files, p),
94
+ readFileSync: (p) => files[p],
95
+ };
96
+ const rebuildActiveIndex = vi.fn(() => ({ rows: [], skipped: [] }));
97
+ const logs = { writeLine: vi.fn() };
98
+ const allProjectCwds = () => ['/clean-proj', '/broken-proj', '/missing-proj'];
99
+ const promptSessionsActiveIndexPath = (cwd) => `${cwd}/active-index.json`;
100
+
101
+ await runEpicIndexSelfHeal({ fs, allProjectCwds, promptSessionsActiveIndexPath, rebuildActiveIndex, logs });
102
+
103
+ expect(rebuildActiveIndex).toHaveBeenCalledTimes(2);
104
+ expect(rebuildActiveIndex).toHaveBeenCalledWith('/broken-proj');
105
+ expect(rebuildActiveIndex).toHaveBeenCalledWith('/missing-proj');
106
+ expect(rebuildActiveIndex).not.toHaveBeenCalledWith('/clean-proj');
107
+ });
@@ -0,0 +1,121 @@
1
+ /**
2
+ * buildIdentity.test.cjs — resolveBuildIdentity() resolution order:
3
+ * build-info.json first, git fallback only when a real .git sits AT the
4
+ * package root (never walking up), else null.
5
+ *
6
+ * Each test injects a distinct fixture `packageRoot` (see buildIdentity.cjs's
7
+ * `opts.packageRoot`), which also keeps memoization scoped per-root so the
8
+ * three cases don't clobber each other's cached result in one process.
9
+ *
10
+ * Run: timeout 120 npx vitest run src/main/lib/__tests__/buildIdentity.test.cjs
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ import { test, expect, afterEach } from 'vitest';
16
+ const fs = require('node:fs');
17
+ const os = require('node:os');
18
+ const path = require('node:path');
19
+ const { execFileSync } = require('node:child_process');
20
+ const { resolveBuildIdentity } = require('../buildIdentity.cjs');
21
+
22
+ const cleanupDirs = [];
23
+
24
+ function mkFixtureRoot(prefix) {
25
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
26
+ cleanupDirs.push(dir);
27
+ fs.mkdirSync(path.join(dir, 'src', 'main'), { recursive: true });
28
+ return dir;
29
+ }
30
+
31
+ function writePackageJson(root, version) {
32
+ fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ name: 'fixture-pkg', version }));
33
+ }
34
+
35
+ function mkRealGitRepo(root) {
36
+ execFileSync('git', ['-C', root, 'init', '-q']);
37
+ execFileSync('git', ['-C', root, 'config', 'user.email', 'test@test.com']);
38
+ execFileSync('git', ['-C', root, 'config', 'user.name', 'Test']);
39
+ fs.writeFileSync(path.join(root, 'README.md'), 'fixture\n');
40
+ execFileSync('git', ['-C', root, 'add', 'README.md']);
41
+ execFileSync('git', ['-C', root, 'commit', '-q', '-m', 'init']);
42
+ return execFileSync('git', ['-C', root, 'rev-parse', '--short', 'HEAD'], { encoding: 'utf8' }).trim();
43
+ }
44
+
45
+ afterEach(() => {
46
+ while (cleanupDirs.length) {
47
+ const dir = cleanupDirs.pop();
48
+ fs.rmSync(dir, { recursive: true, force: true });
49
+ }
50
+ });
51
+
52
+ test('build-info.json present: its fields win over both git and package.json', () => {
53
+ const root = mkFixtureRoot('sm-buildid-present-');
54
+ writePackageJson(root, '0.1.0');
55
+ fs.writeFileSync(path.join(root, 'src', 'main', 'build-info.json'), JSON.stringify({
56
+ version: '9.9.9',
57
+ gitSha: 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeef',
58
+ gitShortSha: 'deadbee',
59
+ gitBranch: 'main',
60
+ builtAt: '2026-01-01T00:00:00.000Z',
61
+ dirty: false,
62
+ }));
63
+
64
+ const identity = resolveBuildIdentity({ packageRoot: root, bootedAt: '2026-02-02T00:00:00.000Z' });
65
+
66
+ expect(identity.version).toBe('9.9.9');
67
+ expect(identity.codeSha).toBe('deadbee');
68
+ expect(identity.builtAt).toBe('2026-01-01T00:00:00.000Z');
69
+ expect(identity.appPath).toBe(root);
70
+ expect(identity.bootedAt).toBe('2026-02-02T00:00:00.000Z');
71
+ });
72
+
73
+ test('build-info.json absent, real .git at the package root: codeSha falls back to git, builtAt stays null', () => {
74
+ const root = mkFixtureRoot('sm-buildid-git-');
75
+ writePackageJson(root, '1.2.3');
76
+ const sha = mkRealGitRepo(root);
77
+
78
+ const identity = resolveBuildIdentity({ packageRoot: root });
79
+
80
+ expect(identity.version).toBe('1.2.3');
81
+ expect(identity.codeSha).toBe(sha);
82
+ expect(identity.builtAt).toBeNull();
83
+ });
84
+
85
+ test('build-info.json absent, no .git at the package root: codeSha is null, never walks up to an ancestor .git', () => {
86
+ const ancestor = mkFixtureRoot('sm-buildid-ancestor-');
87
+ mkRealGitRepo(ancestor); // a real repo one level UP from the fixture root — must never be found
88
+ const root = path.join(ancestor, 'nested-pkg');
89
+ fs.mkdirSync(path.join(root, 'src', 'main'), { recursive: true });
90
+ writePackageJson(root, '2.0.0');
91
+
92
+ const identity = resolveBuildIdentity({ packageRoot: root });
93
+
94
+ expect(identity.version).toBe('2.0.0');
95
+ expect(identity.codeSha).toBeNull();
96
+ expect(identity.builtAt).toBeNull();
97
+ });
98
+
99
+ test('bootedAt is echoed back verbatim, never re-captured, and defaults to null', () => {
100
+ const root = mkFixtureRoot('sm-buildid-bootedat-');
101
+ writePackageJson(root, '1.0.0');
102
+
103
+ expect(resolveBuildIdentity({ packageRoot: root }).bootedAt).toBeNull();
104
+ expect(resolveBuildIdentity({ packageRoot: root, bootedAt: 'fixed-value' }).bootedAt).toBe('fixed-value');
105
+ });
106
+
107
+ test('installChannel is reused from machineProfile.resolveInstallChannel, keyed off appPath', () => {
108
+ const root = mkFixtureRoot('sm-buildid-channel-');
109
+ writePackageJson(root, '1.0.0');
110
+ const prevSmDev = process.env.SM_DEV;
111
+ delete process.env.SM_DEV;
112
+ try {
113
+ const identity = resolveBuildIdentity({ packageRoot: root });
114
+ // fixture root lives under os.tmpdir(), not an .npm/_npx cache path, and
115
+ // SM_DEV is unset here, so resolveInstallChannel falls through to 'unknown'.
116
+ expect(identity.installChannel).toBe('unknown');
117
+ } finally {
118
+ if (prevSmDev === undefined) delete process.env.SM_DEV;
119
+ else process.env.SM_DEV = prevSmDev;
120
+ }
121
+ });
@@ -0,0 +1,111 @@
1
+ // cwdClassify.cjs — one classifier for "what project is this path", failing
2
+ // CLOSED on the unprovable worktree case. Fixtures live under a per-run
3
+ // mkdtemp sandbox, never the live roots.
4
+ const assert = require('node:assert/strict');
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+ const { classifyCwd, nearestGitEntry, worktreeMainRootOf } = require('../cwdClassify.cjs');
9
+ const { assertOpsWrite, resolveOpsRoot } = require('../opsOwnership.cjs');
10
+ const { isEphemeralCwd } = require('../ephemeralCwd.cjs');
11
+ const { projectRootOf } = require('../activeSessions.cjs');
12
+
13
+ const OPS = 'session-manager-operations';
14
+ let sandbox;
15
+ const real = (p) => fs.realpathSync(p);
16
+
17
+ beforeEach(() => { sandbox = real(fs.mkdtempSync(path.join(os.tmpdir(), 'sm-cwdclassify-'))); });
18
+ afterEach(() => { fs.rmSync(sandbox, { recursive: true, force: true }); });
19
+
20
+ function mkWorktree({ backRef = true } = {}) {
21
+ const main = path.join(sandbox, 'main');
22
+ const wt = path.join(sandbox, 'wt');
23
+ const admin = path.join(main, '.git', 'worktrees', 'wt1');
24
+ fs.mkdirSync(admin, { recursive: true });
25
+ fs.mkdirSync(wt, { recursive: true });
26
+ fs.writeFileSync(path.join(wt, '.git'), `gitdir: ${admin}\n`);
27
+ if (backRef) fs.writeFileSync(path.join(admin, 'gitdir'), `${path.join(wt, '.git')}\n`);
28
+ return { main, wt };
29
+ }
30
+
31
+ test('healthy worktree → worktree, projectRoot is the main tree', () => {
32
+ const { main, wt } = mkWorktree();
33
+ const c = classifyCwd(wt);
34
+ assert.equal(c.kind, 'worktree');
35
+ assert.equal(c.projectRoot, main);
36
+ assert.equal(projectRootOf(wt), main);
37
+ assert.equal(isEphemeralCwd(wt), true);
38
+ assert.equal(worktreeMainRootOf(wt), main);
39
+ });
40
+
41
+ test('broken back-reference → unknown; ops writes refused, resolution and dispatch stay open', () => {
42
+ const { wt } = mkWorktree({ backRef: false });
43
+ const c = classifyCwd(wt);
44
+ assert.equal(c.kind, 'unknown');
45
+ assert.equal(c.projectRoot, wt);
46
+ assert.match(c.reason, /back-reference/);
47
+ assert.equal(projectRootOf(wt), wt); // total — never throws
48
+ assert.equal(resolveOpsRoot(wt), path.join(wt, OPS)); // reads/dispatch unaffected
49
+ assert.throws(
50
+ () => assertOpsWrite(path.join(wt, OPS, 'scheduler', 'state', 'queue.json'), 'scheduler'),
51
+ (e) => e.unknownCwd === true && /back-reference/.test(e.message),
52
+ );
53
+ });
54
+
55
+ test('garbled .git file → unknown', () => {
56
+ const dir = path.join(sandbox, 'garbled');
57
+ fs.mkdirSync(dir);
58
+ fs.writeFileSync(path.join(dir, '.git'), 'not a pointer\n');
59
+ assert.equal(classifyCwd(dir).kind, 'unknown');
60
+ });
61
+
62
+ test('separate-git-dir → project (gitdir not under worktrees/)', () => {
63
+ const dir = path.join(sandbox, 'sep');
64
+ fs.mkdirSync(dir);
65
+ fs.writeFileSync(path.join(dir, '.git'), `gitdir: ${path.join(sandbox, 'elsewhere.git')}\n`);
66
+ const c = classifyCwd(dir);
67
+ assert.equal(c.kind, 'project');
68
+ assert.equal(c.projectRoot, dir);
69
+ assert.doesNotThrow(() => assertOpsWrite(path.join(dir, OPS, 'scheduler', 'state', 'queue.json'), 'scheduler'));
70
+ });
71
+
72
+ test('submodule (relative gitdir) → project rooted at the submodule', () => {
73
+ const sup = path.join(sandbox, 'super');
74
+ const sub = path.join(sup, 'libs', 'sub');
75
+ fs.mkdirSync(path.join(sup, '.git', 'modules', 'sub'), { recursive: true });
76
+ fs.mkdirSync(path.join(sub, 'src'), { recursive: true });
77
+ fs.writeFileSync(path.join(sub, '.git'), 'gitdir: ../../.git/modules/sub\n');
78
+ const c = classifyCwd(path.join(sub, 'src'));
79
+ assert.equal(c.kind, 'project');
80
+ assert.equal(c.projectRoot, sub);
81
+ });
82
+
83
+ test('plain project (.git dir, and no git at all) → project', () => {
84
+ const proj = path.join(sandbox, 'proj');
85
+ fs.mkdirSync(path.join(proj, '.git'), { recursive: true });
86
+ fs.mkdirSync(path.join(proj, 'src'));
87
+ assert.deepEqual(
88
+ { k: classifyCwd(path.join(proj, 'src')).kind, r: classifyCwd(path.join(proj, 'src')).projectRoot },
89
+ { k: 'project', r: proj },
90
+ );
91
+ const bare = path.join(sandbox, 'nogit');
92
+ fs.mkdirSync(bare);
93
+ assert.equal(classifyCwd(bare).projectRoot, bare);
94
+ assert.equal(nearestGitEntry(bare), null);
95
+ });
96
+
97
+ test('nested ops root: outermost (indexOf) and innermost (lastIndexOf) are both reported, neither unified', () => {
98
+ const proj = path.join(sandbox, 'proj');
99
+ fs.mkdirSync(path.join(proj, '.git'), { recursive: true });
100
+ const nested = path.join(proj, OPS, 'scheduler', 'prds', OPS, 'scheduler', 'state');
101
+ const c = classifyCwd(nested);
102
+ assert.equal(c.projectRoot, proj);
103
+ assert.equal(c.outermostOpsRoot, path.join(proj, OPS));
104
+ assert.equal(c.innermostOpsRoot, path.join(proj, OPS, 'scheduler', 'prds', OPS));
105
+ assert.equal(classifyCwd(proj).outermostOpsRoot, null);
106
+ });
107
+
108
+ test('ephemeral: os.tmpdir() itself', () => {
109
+ assert.equal(classifyCwd(path.resolve(os.tmpdir())).kind, 'ephemeral');
110
+ assert.equal(isEphemeralCwd(os.tmpdir()), true);
111
+ });