claude-code-session-manager 0.97.0 → 0.100.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 (430) hide show
  1. package/README.md +3 -3
  2. package/bin/cli.cjs +16 -4
  3. package/bin/node-floor.cjs +41 -0
  4. package/dist/assets/{DataModel-D93FoaOm.js → DataModel-HdR0AxFq.js} +1 -1
  5. package/dist/assets/{History-CXxxqNP1.js → History-Dr4Jr_5z.js} +2 -2
  6. package/dist/assets/{Hooks-CzeYFQjq.js → Hooks-NhrvH3JP.js} +3 -3
  7. package/dist/assets/{Library-D2zPkwOC.js → Library-Dv_EtIMr.js} +1 -1
  8. package/dist/assets/{MarkdownEditor-DbinP78A.js → MarkdownEditor-DTKDWV_n.js} +1 -1
  9. package/dist/assets/{McpServers-Bu_sVGLr.js → McpServers-M034G9B3.js} +2 -2
  10. package/dist/assets/{Memory-BgiI6LZ_.js → Memory-DzF1B4gx.js} +6 -6
  11. package/dist/assets/{Permissions-C1MMzZRm.js → Permissions-DrgbSGWl.js} +3 -3
  12. package/dist/assets/{Plugins-Dhu6wAZi.js → Plugins-Bd3ef35w.js} +2 -2
  13. package/dist/assets/{ProvenanceBadge-C3AcWKMG.js → ProvenanceBadge-DcBHT1Iy.js} +1 -1
  14. package/dist/assets/{SaveBar-plwYm0iO.js → SaveBar-rimJem-Y.js} +1 -1
  15. package/dist/assets/Scheduler-DjgFJl2w.js +16 -0
  16. package/dist/assets/{ScopeSwitcher-DFy0vv_o.js → ScopeSwitcher-CnAzTBrl.js} +1 -1
  17. package/dist/assets/Settings-Dc1F_sSg.js +3 -0
  18. package/dist/assets/{SkillReferenceGraph-DsI7hRPB.js → SkillReferenceGraph-B9aZDecM.js} +1 -1
  19. package/dist/assets/{Skills-3E748UfB.js → Skills-DRXd18gu.js} +2 -2
  20. package/dist/assets/{SystemPrompt-BqmfUJOd.js → SystemPrompt-ahy5-Fwe.js} +1 -1
  21. package/dist/assets/{TagLibrary-CPz6fO8Q.js → TagLibrary-C_tToDeL.js} +1 -1
  22. package/dist/assets/{TiptapBody-D9EoBQ1P.js → TiptapBody-DvdefS6K.js} +1 -1
  23. package/dist/assets/{Toggle-BHAmZNDn.js → Toggle-6Jl5Njc6.js} +1 -1
  24. package/dist/assets/{index-CKH5Uxik.css → index-Bta-hwud.css} +1 -1
  25. package/dist/assets/{index-tn5JIVPj.js → index-vx8O73l8.js} +497 -502
  26. package/dist/assets/{settingsSchema-Crqy_aNz.js → settingsSchema-D6iRguNV.js} +1 -1
  27. package/dist/index.html +2 -2
  28. package/package.json +26 -29
  29. package/plugins/CLAUDE.md +6 -6
  30. package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +23 -19
  31. package/plugins/session-manager-dev/skills/builder/SKILL.md +3 -3
  32. package/plugins/session-manager-dev/skills/develop/SKILL.md +254 -510
  33. package/plugins/session-manager-dev/skills/develop/standards.md +17 -22
  34. package/scripts/README.md +3 -11
  35. package/scripts/audit-ops-hygiene.cjs +3 -3
  36. package/scripts/hooks/lib/guard-prd-writes-policy.cjs +1 -1
  37. package/scripts/hooks/lib/guard-self-schedule-policy.cjs +1 -1
  38. package/scripts/mint-epic.cjs +2 -3
  39. package/scripts/scheduler-mcp-server.cjs +36 -7
  40. package/src/main/agentLibrary.cjs +22 -2
  41. package/src/main/build-info.json +4 -4
  42. package/src/main/config.cjs +41 -38
  43. package/src/main/docEdit.cjs +4 -1
  44. package/src/main/files.cjs +2 -5
  45. package/src/main/health.cjs +1 -1
  46. package/src/main/index.cjs +23 -10
  47. package/src/main/ipcSchemas.cjs +28 -35
  48. package/src/main/lib/agentPersonaSchema.cjs +13 -2
  49. package/src/main/lib/atomicFs.cjs +116 -0
  50. package/src/main/lib/branchSweep.cjs +13 -12
  51. package/src/main/lib/buildTarget.cjs +3 -3
  52. package/src/main/lib/claudeCliCaps.cjs +129 -0
  53. package/src/main/lib/credentials.cjs +2 -4
  54. package/src/main/lib/crossProjectFeedback.cjs +3 -3
  55. package/src/main/lib/cwdClassify.cjs +15 -3
  56. package/src/main/lib/definitionOfDone.cjs +207 -125
  57. package/src/main/lib/dodDrainHook.cjs +11 -5
  58. package/src/main/lib/epicMint.cjs +2 -4
  59. package/src/main/lib/epicStatusMirror.cjs +8 -10
  60. package/src/main/lib/epicWorktreeProjectConfig.cjs +2 -4
  61. package/src/main/lib/gateAuthority.cjs +96 -0
  62. package/src/main/lib/gitExec.cjs +69 -0
  63. package/src/main/lib/gitWorktree.cjs +656 -92
  64. package/src/main/lib/instanceLock.cjs +5 -8
  65. package/src/main/lib/launchFailure.cjs +2 -3
  66. package/src/main/lib/macroLibrary.cjs +386 -0
  67. package/src/main/lib/mcpToolCatalog.cjs +26 -17
  68. package/src/main/lib/needsReviewLedger.cjs +1 -1
  69. package/src/main/lib/opsErrorLog.cjs +11 -1
  70. package/src/main/lib/opsOwnership.cjs +0 -5
  71. package/src/main/lib/pidAlive.cjs +32 -0
  72. package/src/main/lib/prdCreate.cjs +153 -19
  73. package/src/main/lib/prdDisposition.cjs +2 -2
  74. package/src/main/lib/prdGateFiles.cjs +284 -0
  75. package/src/main/lib/prdLocations.cjs +61 -0
  76. package/src/main/lib/prdMigration.cjs +2 -3
  77. package/src/main/lib/prdSizing.cjs +2 -1
  78. package/src/main/lib/promptSessionSchema.cjs +10 -2
  79. package/src/main/lib/rcaReport.cjs +4 -6
  80. package/src/main/lib/reaperHelpers.cjs +8 -1
  81. package/src/main/lib/reservationExpiry.cjs +6 -1
  82. package/src/main/lib/reviewNotice.cjs +263 -0
  83. package/src/main/lib/runClaudeP.cjs +4 -1
  84. package/src/main/lib/schedulerPaths.cjs +22 -2
  85. package/src/main/lib/sessionSlots.cjs +2 -4
  86. package/src/main/lib/shippedPersonaSeeds.cjs +179 -0
  87. package/src/main/lib/timeoutShim.cjs +123 -0
  88. package/src/main/lib/timeoutShimScript.cjs +275 -0
  89. package/src/main/lib/upgradeDrain.cjs +4 -10
  90. package/src/main/lib/watchdogHelpers.cjs +33 -30
  91. package/src/main/lib/workTypeLibrary.cjs +7 -1
  92. package/src/main/promptSessionEvents.cjs +37 -13
  93. package/src/main/queueOps.cjs +17 -6
  94. package/src/main/scheduler.cjs +975 -203
  95. package/src/main/seedAgentPersonas.cjs +278 -23
  96. package/src/main/supervisor.cjs +4 -1
  97. package/src/main/templates/PRD_AUTHORING.md +126 -406
  98. package/src/preload/__tests__/preload-surface.test.cjs +68 -0
  99. package/src/preload/api.d.ts +41 -94
  100. package/src/preload/index.cjs +14 -9
  101. package/src/seed/agents/architect.md +1 -0
  102. package/src/seed/agents/dev-lead.md +17 -18
  103. package/src/seed/agents/project-home-builder.md +1 -0
  104. package/src/seed/agents/validator.md +10 -4
  105. package/dist/assets/HostBilko-nESrTRIg.js +0 -1
  106. package/dist/assets/Scheduler-BVYTeh39.js +0 -16
  107. package/dist/assets/Settings-DIhGgdfs.js +0 -3
  108. package/src/main/__tests__/activeIndexMerge.test.cjs +0 -235
  109. package/src/main/__tests__/agentEffortResolve.test.cjs +0 -117
  110. package/src/main/__tests__/agentLibrary.test.cjs +0 -276
  111. package/src/main/__tests__/agentModelResolve.test.cjs +0 -311
  112. package/src/main/__tests__/agentOverlayWrite.test.cjs +0 -95
  113. package/src/main/__tests__/bilkoHost-deriveSlug.test.cjs +0 -26
  114. package/src/main/__tests__/bilkoHost-integration.test.cjs +0 -118
  115. package/src/main/__tests__/bilkoHostCore.test.cjs +0 -72
  116. package/src/main/__tests__/broadcastCoalescer.test.cjs +0 -122
  117. package/src/main/__tests__/chat-cancel-terminal.test.cjs +0 -120
  118. package/src/main/__tests__/chat-dead-channels.test.cjs +0 -63
  119. package/src/main/__tests__/chat-exit-close-race.test.cjs +0 -146
  120. package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +0 -139
  121. package/src/main/__tests__/chat-preamble-anchors.test.cjs +0 -100
  122. package/src/main/__tests__/chat-queue.test.cjs +0 -97
  123. package/src/main/__tests__/chat-stop-signal.test.cjs +0 -89
  124. package/src/main/__tests__/chatRunner-epic-worktree-execcwd.test.cjs +0 -125
  125. package/src/main/__tests__/chatRunner-session-flag-retry.test.cjs +0 -252
  126. package/src/main/__tests__/classifyPromptTicket.test.cjs +0 -101
  127. package/src/main/__tests__/classifyTranscriptLine.test.cjs +0 -201
  128. package/src/main/__tests__/computeDepHistorySatisfaction.test.cjs +0 -66
  129. package/src/main/__tests__/config-readText-bounded.test.cjs +0 -84
  130. package/src/main/__tests__/configWriteBoundaryOwners.test.cjs +0 -59
  131. package/src/main/__tests__/crossProjectFeedback.test.cjs +0 -334
  132. package/src/main/__tests__/crossProjectFeedbackRoutes.test.cjs +0 -161
  133. package/src/main/__tests__/dep-orphan-archive-health.test.cjs +0 -77
  134. package/src/main/__tests__/develop-skill-failure-modes.test.cjs +0 -70
  135. package/src/main/__tests__/docEdit.test.cjs +0 -244
  136. package/src/main/__tests__/dod-batchkey.test.cjs +0 -183
  137. package/src/main/__tests__/dod-drain-hook.test.cjs +0 -302
  138. package/src/main/__tests__/dod-report.test.cjs +0 -304
  139. package/src/main/__tests__/dod-reverify.test.cjs +0 -285
  140. package/src/main/__tests__/epicContextDigest.test.cjs +0 -174
  141. package/src/main/__tests__/epicMint.test.cjs +0 -332
  142. package/src/main/__tests__/epicMintTelemetryTap.test.cjs +0 -64
  143. package/src/main/__tests__/epicStatusMirror.test.cjs +0 -110
  144. package/src/main/__tests__/epicValidationHook.test.cjs +0 -291
  145. package/src/main/__tests__/exchanges.test.cjs +0 -122
  146. package/src/main/__tests__/exchangesPromptId.test.cjs +0 -61
  147. package/src/main/__tests__/extractJson.test.cjs +0 -51
  148. package/src/main/__tests__/files-reject-credentials.test.cjs +0 -40
  149. package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +0 -556
  150. package/src/main/__tests__/flatPrdTickSweep.test.cjs +0 -110
  151. package/src/main/__tests__/health-build-freshness.test.cjs +0 -39
  152. package/src/main/__tests__/health-claude-md-budget.test.cjs +0 -57
  153. package/src/main/__tests__/health-credentials.test.cjs +0 -81
  154. package/src/main/__tests__/health-delegation-chain.test.cjs +0 -124
  155. package/src/main/__tests__/health-per-project-stall.test.cjs +0 -84
  156. package/src/main/__tests__/health-prd-migration.test.cjs +0 -37
  157. package/src/main/__tests__/health-queue-dispatch.test.cjs +0 -135
  158. package/src/main/__tests__/health-starve-escalation.test.cjs +0 -94
  159. package/src/main/__tests__/health-tick-liveness.test.cjs +0 -179
  160. package/src/main/__tests__/health-usage-poller.test.cjs +0 -144
  161. package/src/main/__tests__/health-worktree-cap-blocked.test.cjs +0 -65
  162. package/src/main/__tests__/heapSnapshot.test.cjs +0 -121
  163. package/src/main/__tests__/historyAggregatorIntraday.test.cjs +0 -313
  164. package/src/main/__tests__/historyDashboard.test.cjs +0 -163
  165. package/src/main/__tests__/historyRollup.test.cjs +0 -333
  166. package/src/main/__tests__/intradayRefresh.test.cjs +0 -39
  167. package/src/main/__tests__/ipcSchemas-dependsOn.test.cjs +0 -39
  168. package/src/main/__tests__/kg-augment.test.cjs +0 -195
  169. package/src/main/__tests__/loadGateDetailTick.test.cjs +0 -31
  170. package/src/main/__tests__/machineProfile.test.cjs +0 -152
  171. package/src/main/__tests__/mcpStatus.test.cjs +0 -61
  172. package/src/main/__tests__/memoryAggregate.test.cjs +0 -109
  173. package/src/main/__tests__/memoryStale.test.cjs +0 -88
  174. package/src/main/__tests__/needsReviewLedger.test.cjs +0 -162
  175. package/src/main/__tests__/openExternalApp-spawn-error.test.cjs +0 -25
  176. package/src/main/__tests__/opsErrorLog.test.cjs +0 -109
  177. package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +0 -173
  178. package/src/main/__tests__/personaMerge.test.cjs +0 -169
  179. package/src/main/__tests__/planValidator.test.cjs +0 -125
  180. package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +0 -176
  181. package/src/main/__tests__/prd-group-allocator.test.cjs +0 -119
  182. package/src/main/__tests__/prdAdminRouteParity.test.cjs +0 -70
  183. package/src/main/__tests__/prdAdminRoutes.test.cjs +0 -718
  184. package/src/main/__tests__/prdAgentType.test.cjs +0 -103
  185. package/src/main/__tests__/prdAuthoringSeed.test.cjs +0 -39
  186. package/src/main/__tests__/prdCreate.test.cjs +0 -979
  187. package/src/main/__tests__/prdCreateDisposition.test.cjs +0 -201
  188. package/src/main/__tests__/prdCreatePlanId.test.cjs +0 -132
  189. package/src/main/__tests__/prdFrontmatterAgentType.test.cjs +0 -117
  190. package/src/main/__tests__/prdFrontmatterDependsOn.test.cjs +0 -136
  191. package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +0 -125
  192. package/src/main/__tests__/prdFrontmatterQuietMachine.test.cjs +0 -108
  193. package/src/main/__tests__/prdLocations.test.cjs +0 -195
  194. package/src/main/__tests__/prdLocationsArchived.test.cjs +0 -201
  195. package/src/main/__tests__/prdMigration.test.cjs +0 -349
  196. package/src/main/__tests__/prdMigrationLegacyAdopt.test.cjs +0 -91
  197. package/src/main/__tests__/prdParserHighWater.test.cjs +0 -74
  198. package/src/main/__tests__/prdParserSourcePromptId.test.cjs +0 -65
  199. package/src/main/__tests__/prdSetDisposition.test.cjs +0 -222
  200. package/src/main/__tests__/prdSizing.test.cjs +0 -106
  201. package/src/main/__tests__/prdSourcePromptIdBackfill.test.cjs +0 -118
  202. package/src/main/__tests__/prdUpdateDependsOn.test.cjs +0 -160
  203. package/src/main/__tests__/proc-role-env.test.cjs +0 -125
  204. package/src/main/__tests__/procname-claude-spawn-sites.test.cjs +0 -304
  205. package/src/main/__tests__/procname-sm-processes.test.cjs +0 -127
  206. package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +0 -177
  207. package/src/main/__tests__/projectPages.test.cjs +0 -137
  208. package/src/main/__tests__/promptSessionEvents.test.cjs +0 -234
  209. package/src/main/__tests__/promptSessionSchema.test.cjs +0 -101
  210. package/src/main/__tests__/promptSessionTranscript.test.cjs +0 -0
  211. package/src/main/__tests__/promptSessionsCreateEpicHandler.test.cjs +0 -159
  212. package/src/main/__tests__/pty-epic-worktree-spawn-cwd.test.cjs +0 -282
  213. package/src/main/__tests__/pty-session-open-telemetry.test.cjs +0 -96
  214. package/src/main/__tests__/pty-write-result.test.cjs +0 -46
  215. package/src/main/__tests__/queue-health-verdict.test.cjs +0 -170
  216. package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +0 -286
  217. package/src/main/__tests__/queue-starvation-per-project.test.cjs +0 -147
  218. package/src/main/__tests__/queueHistory.test.cjs +0 -355
  219. package/src/main/__tests__/queueOps-interactive-ac-lint.test.cjs +0 -65
  220. package/src/main/__tests__/queueOpsArchiveDestination.test.cjs +0 -65
  221. package/src/main/__tests__/queueOpsAutoArchive.test.cjs +0 -154
  222. package/src/main/__tests__/rateLimitPollerStreak.test.cjs +0 -128
  223. package/src/main/__tests__/rcaReport.test.cjs +0 -266
  224. package/src/main/__tests__/reconcileFlatPrdSweep.test.cjs +0 -119
  225. package/src/main/__tests__/reconcileTiming.test.cjs +0 -135
  226. package/src/main/__tests__/runLogRetention.test.cjs +0 -489
  227. package/src/main/__tests__/runVerify-atomic-verdicts.test.cjs +0 -26
  228. package/src/main/__tests__/runVerify-blocked-by-foreign-wip.test.cjs +0 -58
  229. package/src/main/__tests__/runVerify-landed-commit-outranks.test.cjs +0 -191
  230. package/src/main/__tests__/runVerify-policy-denial.test.cjs +0 -89
  231. package/src/main/__tests__/runVerify-transcript-commit-evidence.test.cjs +0 -225
  232. package/src/main/__tests__/runVerify.test.cjs +0 -1784
  233. package/src/main/__tests__/scheduleJobSchema.test.cjs +0 -127
  234. package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +0 -65
  235. package/src/main/__tests__/scheduleJobTransitions.test.cjs +0 -277
  236. package/src/main/__tests__/scheduleJobTransitionsGrep.test.cjs +0 -59
  237. package/src/main/__tests__/scheduleJobTransitionsTelemetryTap.test.cjs +0 -72
  238. package/src/main/__tests__/scheduler-admin-routes.test.cjs +0 -199
  239. package/src/main/__tests__/scheduler-adopted-run-supervision.test.cjs +0 -143
  240. package/src/main/__tests__/scheduler-already-satisfied-on-main.test.cjs +0 -105
  241. package/src/main/__tests__/scheduler-archive-completed-prd.test.cjs +0 -102
  242. package/src/main/__tests__/scheduler-archived-twin-guard.test.cjs +0 -155
  243. package/src/main/__tests__/scheduler-autofix-outcome.test.cjs +0 -188
  244. package/src/main/__tests__/scheduler-autofix-select.test.cjs +0 -439
  245. package/src/main/__tests__/scheduler-autopromote.test.cjs +0 -51
  246. package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +0 -101
  247. package/src/main/__tests__/scheduler-blocked-by-foreign-wip.test.cjs +0 -107
  248. package/src/main/__tests__/scheduler-boot-orphans.test.cjs +0 -153
  249. package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +0 -121
  250. package/src/main/__tests__/scheduler-clear-queue-history.test.cjs +0 -134
  251. package/src/main/__tests__/scheduler-commit-guard-noop.test.cjs +0 -244
  252. package/src/main/__tests__/scheduler-committed-in-window.test.cjs +0 -182
  253. package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +0 -43
  254. package/src/main/__tests__/scheduler-default-eligible-heal.test.cjs +0 -168
  255. package/src/main/__tests__/scheduler-dispatch-loop.test.cjs +0 -58
  256. package/src/main/__tests__/scheduler-effective-concurrency.test.cjs +0 -81
  257. package/src/main/__tests__/scheduler-epic-digest.test.cjs +0 -227
  258. package/src/main/__tests__/scheduler-failed-autoreset.test.cjs +0 -121
  259. package/src/main/__tests__/scheduler-finalize-dispatch-guards.test.cjs +0 -229
  260. package/src/main/__tests__/scheduler-find-prd-dir.test.cjs +0 -75
  261. package/src/main/__tests__/scheduler-fix-plan-path.test.cjs +0 -119
  262. package/src/main/__tests__/scheduler-force-tick-outcome.test.cjs +0 -46
  263. package/src/main/__tests__/scheduler-foreign-wip-manifest.test.cjs +0 -78
  264. package/src/main/__tests__/scheduler-gate-shadow.test.cjs +0 -119
  265. package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +0 -390
  266. package/src/main/__tests__/scheduler-heal-refusal.test.cjs +0 -61
  267. package/src/main/__tests__/scheduler-heartbeat-payload.test.cjs +0 -80
  268. package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +0 -323
  269. package/src/main/__tests__/scheduler-integration-failure-stamp.test.cjs +0 -41
  270. package/src/main/__tests__/scheduler-investigation-clean-skip.test.cjs +0 -63
  271. package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +0 -123
  272. package/src/main/__tests__/scheduler-job-budget.test.cjs +0 -172
  273. package/src/main/__tests__/scheduler-job-overrun.test.cjs +0 -175
  274. package/src/main/__tests__/scheduler-launch-failure.test.cjs +0 -199
  275. package/src/main/__tests__/scheduler-leftover-fields.test.cjs +0 -52
  276. package/src/main/__tests__/scheduler-leftover-quarantine.test.cjs +0 -199
  277. package/src/main/__tests__/scheduler-looks-done.test.cjs +0 -537
  278. package/src/main/__tests__/scheduler-manual-pause.test.cjs +0 -118
  279. package/src/main/__tests__/scheduler-mechanical-recovery.test.cjs +0 -245
  280. package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +0 -46
  281. package/src/main/__tests__/scheduler-needs-review-autoresolve.test.cjs +0 -197
  282. package/src/main/__tests__/scheduler-never-stop.test.cjs +0 -157
  283. package/src/main/__tests__/scheduler-no-dead-end-status.test.cjs +0 -152
  284. package/src/main/__tests__/scheduler-no-orphan-run-dir.test.cjs +0 -81
  285. package/src/main/__tests__/scheduler-notify-originating-tab-transcript.test.cjs +0 -87
  286. package/src/main/__tests__/scheduler-notify-originating-tab.test.cjs +0 -343
  287. package/src/main/__tests__/scheduler-periodic-reverify-guard.test.cjs +0 -217
  288. package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +0 -164
  289. package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +0 -161
  290. package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +0 -178
  291. package/src/main/__tests__/scheduler-quarantine-autoresolve.test.cjs +0 -165
  292. package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +0 -257
  293. package/src/main/__tests__/scheduler-rate-limit-cooldown-freshness.test.cjs +0 -123
  294. package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +0 -152
  295. package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +0 -156
  296. package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +0 -754
  297. package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +0 -87
  298. package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +0 -100
  299. package/src/main/__tests__/scheduler-reconcile-history-backfill.test.cjs +0 -105
  300. package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +0 -203
  301. package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +0 -247
  302. package/src/main/__tests__/scheduler-reset-job-fields-guard.test.cjs +0 -77
  303. package/src/main/__tests__/scheduler-resume-recovery.test.cjs +0 -254
  304. package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +0 -115
  305. package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +0 -380
  306. package/src/main/__tests__/scheduler-sigterm-commit.test.cjs +0 -43
  307. package/src/main/__tests__/scheduler-stall-per-project.test.cjs +0 -126
  308. package/src/main/__tests__/scheduler-starve-escalation.test.cjs +0 -154
  309. package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +0 -252
  310. package/src/main/__tests__/scheduler-stranded-investigation.test.cjs +0 -185
  311. package/src/main/__tests__/scheduler-stuck-failed-escalation.test.cjs +0 -136
  312. package/src/main/__tests__/scheduler-supervisor-record.test.cjs +0 -81
  313. package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +0 -54
  314. package/src/main/__tests__/scheduler-tick-wedge.test.cjs +0 -172
  315. package/src/main/__tests__/scheduler-transient-failure.test.cjs +0 -141
  316. package/src/main/__tests__/scheduler-unreadable-queue-guard.test.cjs +0 -62
  317. package/src/main/__tests__/scheduler-utilization-hold.test.cjs +0 -89
  318. package/src/main/__tests__/scheduler-verify-prd-path.test.cjs +0 -109
  319. package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +0 -234
  320. package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +0 -120
  321. package/src/main/__tests__/scheduler-writeprd-epic-rollback.test.cjs +0 -105
  322. package/src/main/__tests__/schedulerBatchRootBlocker.test.cjs +0 -117
  323. package/src/main/__tests__/schedulerStateSidecarRestore.test.cjs +0 -110
  324. package/src/main/__tests__/seedAgentPersonas.test.cjs +0 -184
  325. package/src/main/__tests__/seedSchedulerMcp.test.cjs +0 -211
  326. package/src/main/__tests__/seedStatus.test.cjs +0 -100
  327. package/src/main/__tests__/seedValidatorPersona.test.cjs +0 -116
  328. package/src/main/__tests__/stop-signal-anchor.test.cjs +0 -75
  329. package/src/main/__tests__/telemetryClient.test.cjs +0 -1055
  330. package/src/main/__tests__/telemetryContract.test.cjs +0 -930
  331. package/src/main/__tests__/telemetrySettings.test.cjs +0 -210
  332. package/src/main/__tests__/transcripts-batch-flush.test.cjs +0 -249
  333. package/src/main/__tests__/transcripts-doFlush-array.test.cjs +0 -124
  334. package/src/main/__tests__/transcripts-paged-reads.test.cjs +0 -241
  335. package/src/main/__tests__/transcripts-worktree-epic-path.test.cjs +0 -154
  336. package/src/main/__tests__/transcriptsUsageFor.test.cjs +0 -206
  337. package/src/main/__tests__/uniquePrdNumbers.test.cjs +0 -153
  338. package/src/main/__tests__/usageSingleFlight.test.cjs +0 -169
  339. package/src/main/__tests__/validationSentinels.test.cjs +0 -84
  340. package/src/main/__tests__/workTypeLibrary.test.cjs +0 -89
  341. package/src/main/bilkoHost.cjs +0 -314
  342. package/src/main/bilkoHostCore.cjs +0 -89
  343. package/src/main/lib/__tests__/active-sessions.test.cjs +0 -251
  344. package/src/main/lib/__tests__/activeIndexRebuild.test.cjs +0 -179
  345. package/src/main/lib/__tests__/agentPersonaSchema.test.cjs +0 -67
  346. package/src/main/lib/__tests__/auditLog.test.cjs +0 -38
  347. package/src/main/lib/__tests__/bootSelfHeal.test.cjs +0 -107
  348. package/src/main/lib/__tests__/branchSweep.test.cjs +0 -164
  349. package/src/main/lib/__tests__/buildIdentity.test.cjs +0 -121
  350. package/src/main/lib/__tests__/buildTarget.test.cjs +0 -52
  351. package/src/main/lib/__tests__/childWithLog.test.cjs +0 -321
  352. package/src/main/lib/__tests__/coldBootPromptSessionsWrite.test.cjs +0 -87
  353. package/src/main/lib/__tests__/crashTelemetry.test.cjs +0 -103
  354. package/src/main/lib/__tests__/credentials-futile-refresh.test.cjs +0 -115
  355. package/src/main/lib/__tests__/cwdClassify.test.cjs +0 -111
  356. package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +0 -95
  357. package/src/main/lib/__tests__/delegationReadiness.test.cjs +0 -1175
  358. package/src/main/lib/__tests__/dispatchLoop.test.cjs +0 -63
  359. package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +0 -244
  360. package/src/main/lib/__tests__/ephemeralCwd.test.cjs +0 -91
  361. package/src/main/lib/__tests__/epicDelegationStats.test.cjs +0 -137
  362. package/src/main/lib/__tests__/epicSpawnCwd.test.cjs +0 -283
  363. package/src/main/lib/__tests__/epicSpawnPlan.test.cjs +0 -196
  364. package/src/main/lib/__tests__/epicTranscriptPath.test.cjs +0 -163
  365. package/src/main/lib/__tests__/epicWorktreeBoot.test.cjs +0 -136
  366. package/src/main/lib/__tests__/epicWorktreeMerge.test.cjs +0 -130
  367. package/src/main/lib/__tests__/epicWorktreeMint.test.cjs +0 -117
  368. package/src/main/lib/__tests__/epicWorktreeProjectConfig.test.cjs +0 -137
  369. package/src/main/lib/__tests__/fixChainDepth.test.cjs +0 -40
  370. package/src/main/lib/__tests__/fixtures/204-mercury-steam-horse.log.txt +0 -13
  371. package/src/main/lib/__tests__/fixtures/scheduler-machine.json.corrupt-1789147548 +0 -34
  372. package/src/main/lib/__tests__/gateFixtures.json +0 -20
  373. package/src/main/lib/__tests__/gitCacheBound.test.cjs +0 -69
  374. package/src/main/lib/__tests__/gitWorktree.test.cjs +0 -1478
  375. package/src/main/lib/__tests__/gitWorktreeSalvage.test.cjs +0 -107
  376. package/src/main/lib/__tests__/gitWorktreeSalvageDelta.test.cjs +0 -153
  377. package/src/main/lib/__tests__/guardShims.test.cjs +0 -158
  378. package/src/main/lib/__tests__/importReferences.spec.cjs +0 -56
  379. package/src/main/lib/__tests__/instanceLock.test.cjs +0 -173
  380. package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +0 -78
  381. package/src/main/lib/__tests__/jobWorktree.test.cjs +0 -199
  382. package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +0 -82
  383. package/src/main/lib/__tests__/landedSinceRun.test.cjs +0 -133
  384. package/src/main/lib/__tests__/launchFailure.test.cjs +0 -220
  385. package/src/main/lib/__tests__/loadGate.test.cjs +0 -303
  386. package/src/main/lib/__tests__/localAdminHttp.test.cjs +0 -214
  387. package/src/main/lib/__tests__/loopDelay.test.cjs +0 -68
  388. package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +0 -107
  389. package/src/main/lib/__tests__/modelCatalog.test.cjs +0 -202
  390. package/src/main/lib/__tests__/opsOwnership.test.cjs +0 -113
  391. package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +0 -328
  392. package/src/main/lib/__tests__/opsRootNestedWrite.test.cjs +0 -51
  393. package/src/main/lib/__tests__/opsRootResolve.test.cjs +0 -149
  394. package/src/main/lib/__tests__/prdDeclaredPaths.test.cjs +0 -82
  395. package/src/main/lib/__tests__/prdDisposition.test.cjs +0 -224
  396. package/src/main/lib/__tests__/procIdentity.test.cjs +0 -119
  397. package/src/main/lib/__tests__/procName.test.cjs +0 -92
  398. package/src/main/lib/__tests__/projectBriefCore.test.cjs +0 -216
  399. package/src/main/lib/__tests__/projectRootResolve.test.cjs +0 -148
  400. package/src/main/lib/__tests__/queueHealth.test.cjs +0 -58
  401. package/src/main/lib/__tests__/queueStoreAtomicWrite.test.cjs +0 -88
  402. package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +0 -190
  403. package/src/main/lib/__tests__/quietMachineLease.test.cjs +0 -39
  404. package/src/main/lib/__tests__/rateLimitWindow.test.cjs +0 -88
  405. package/src/main/lib/__tests__/reaperHelpers.test.cjs +0 -577
  406. package/src/main/lib/__tests__/schedulerBatchDepends.test.cjs +0 -312
  407. package/src/main/lib/__tests__/schedulerBatchFairness.test.cjs +0 -213
  408. package/src/main/lib/__tests__/schedulerBatchLaunchHold.test.cjs +0 -125
  409. package/src/main/lib/__tests__/schedulerBatchProjectCap.test.cjs +0 -127
  410. package/src/main/lib/__tests__/schedulerBatchQuietMachine.test.cjs +0 -109
  411. package/src/main/lib/__tests__/schedulerMcpServerHeadlessRefusal.test.cjs +0 -71
  412. package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +0 -216
  413. package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +0 -183
  414. package/src/main/lib/__tests__/schedulerPaths.test.cjs +0 -226
  415. package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +0 -133
  416. package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +0 -56
  417. package/src/main/lib/__tests__/sessionSlots.test.cjs +0 -144
  418. package/src/main/lib/__tests__/telemetryBacklog.test.cjs +0 -626
  419. package/src/main/lib/__tests__/telemetryBoot.test.cjs +0 -134
  420. package/src/main/lib/__tests__/telemetryConsent.test.cjs +0 -136
  421. package/src/main/lib/__tests__/telemetryCounters.test.cjs +0 -57
  422. package/src/main/lib/__tests__/telemetryCountersMetadataColumn.test.cjs +0 -98
  423. package/src/main/lib/__tests__/terminalRunOutcome.test.cjs +0 -200
  424. package/src/main/lib/__tests__/toolUseClassify.test.cjs +0 -53
  425. package/src/main/lib/__tests__/updateCheck.test.cjs +0 -63
  426. package/src/main/lib/__tests__/upgradeDrain.test.cjs +0 -130
  427. package/src/main/lib/__tests__/usageCircuit.test.cjs +0 -354
  428. package/src/main/lib/__tests__/watchdog-helpers.test.cjs +0 -375
  429. package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +0 -266
  430. package/src/main/lib/kgExchangePairing.cjs +0 -75
@@ -1,235 +0,0 @@
1
- /**
2
- * activeIndexMerge.test.cjs — unit tests for the main-process read-merge-write
3
- * of a cwd's prompt-sessions/active-index.json (lib/activeIndexMerge.cjs),
4
- * moved here from the renderer (state/promptSessions.ts's old
5
- * persistActiveIndex, commit 3d12e19) so the merge runs inside the same
6
- * withPathLock instance epicMint.cjs's ensureEpic already serializes through.
7
- *
8
- * Covers the two holes a purely-renderer merge left open:
9
- * - resurrection: a tombstoned id must never be written back by a later
10
- * merge call whose memory contribution still thinks it's open.
11
- * - TOCTOU: a mint (ensureEpic) interleaved with a merge call must survive,
12
- * since both now go through the same lock.
13
- *
14
- * Run: timeout 300 npx vitest run src/main/__tests__/activeIndexMerge.test.cjs
15
- */
16
-
17
- 'use strict';
18
-
19
- import { test, expect, afterEach } from 'vitest';
20
- const fsp = require('node:fs/promises');
21
- const os = require('node:os');
22
- const path = require('node:path');
23
- const { mergeActiveIndex } = require('../lib/activeIndexMerge.cjs');
24
- const { ensureEpic, readActiveIndex, MINT_AUTHORITY_NEW_EPIC_UI } = require('../lib/epicMint.cjs');
25
- const { appendResponseEventIfKnown } = require('../promptSessionEvents.cjs');
26
- const config = require('../config.cjs');
27
-
28
- const tmpDirs = [];
29
- afterEach(async () => {
30
- while (tmpDirs.length) {
31
- const d = tmpDirs.pop();
32
- await fsp.rm(d, { recursive: true, force: true });
33
- }
34
- });
35
-
36
- async function mkCwd() {
37
- const cwd = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-activeindexmerge-cwd-'));
38
- // config.cjs's readJson/writeJson restrict to registered roots (home dir by
39
- // default) — register this tmp dir so the merge's config.readJson/writeJson
40
- // calls don't reject it as outside the allowed boundary.
41
- config.addAllowedRoot(cwd);
42
- tmpDirs.push(cwd);
43
- return cwd;
44
- }
45
-
46
- function session(id, overrides = {}) {
47
- return {
48
- id,
49
- cwd: overrides.cwd || '/proj',
50
- goalText: overrides.goalText || `goal for ${id}`,
51
- claudeSessionId: overrides.claudeSessionId || `claude-${id}`,
52
- status: overrides.status || 'active',
53
- createdAt: overrides.createdAt || '2026-08-02T13:48:00.000Z',
54
- completedAt: overrides.completedAt ?? null,
55
- };
56
- }
57
-
58
- function promptEvent(id, promptSessionId, at) {
59
- return { id, promptSessionId, kind: 'prompt', causedByEventId: null, at, text: 'x' };
60
- }
61
-
62
- // Incident 2026-08-02: two Epics created by another (already-running) window
63
- // sat on disk. A second window booted empty and, on its first mutation,
64
- // persisted its own (empty-of-those-two) contribution straight over the file.
65
- // The main-side merge must still preserve every pre-existing on-disk Epic.
66
- test('a fresh caller contributing one Epic preserves pre-existing on-disk Epics untouched', async () => {
67
- const cwd = await mkCwd();
68
- const other = session('psess-other');
69
- await mergeActiveIndex(cwd, {
70
- sessions: { [other.id]: other },
71
- events: { [other.id]: [promptEvent('evt-1', other.id, other.createdAt)] },
72
- });
73
-
74
- const fresh = session('psess-fresh', { createdAt: '2026-08-02T13:50:00.000Z' });
75
- await mergeActiveIndex(cwd, {
76
- sessions: { [fresh.id]: fresh },
77
- events: { [fresh.id]: [promptEvent('evt-2', fresh.id, fresh.createdAt)] },
78
- });
79
-
80
- const index = readActiveIndex(cwd);
81
- expect(index.sessions[other.id]).toEqual(other);
82
- expect(index.sessions[fresh.id]).toEqual(fresh);
83
- expect(index.events[other.id]).toHaveLength(1);
84
- });
85
-
86
- test('memory wins over disk on id collision', async () => {
87
- const cwd = await mkCwd();
88
- const original = session('psess-1', { goalText: 'Fresh title' });
89
- await mergeActiveIndex(cwd, { sessions: { [original.id]: original }, events: {} });
90
-
91
- const updated = { ...original, goalText: 'Updated title' };
92
- await mergeActiveIndex(cwd, { sessions: { [updated.id]: updated }, events: {} });
93
-
94
- const index = readActiveIndex(cwd);
95
- expect(index.sessions[original.id].goalText).toBe('Updated title');
96
- });
97
-
98
- test('removedIds drops the row from the merged write and records a removal tombstone', async () => {
99
- const cwd = await mkCwd();
100
- const s = session('psess-1');
101
- await mergeActiveIndex(cwd, { sessions: { [s.id]: s }, events: {} });
102
-
103
- await mergeActiveIndex(cwd, { sessions: {}, events: {}, removedIds: [s.id] });
104
-
105
- const index = readActiveIndex(cwd);
106
- expect(index.sessions[s.id]).toBeUndefined();
107
- expect(index.tombstones[s.id]).toBeTruthy();
108
- });
109
-
110
- // The resurrection hole this PRD closes: two simulated windows. Window A
111
- // completes (markCompleted) and persists the removal. Window B never
112
- // hydrated that removal — its in-memory snapshot still holds the Epic as
113
- // 'active' — and persists afterward. The tombstone must win: the Epic stays
114
- // gone, not resurrected.
115
- test('regression: a later merge whose memory still holds a removed Epic as active does not resurrect it', async () => {
116
- const cwd = await mkCwd();
117
- const s = session('psess-1', { status: 'active' });
118
- await mergeActiveIndex(cwd, { sessions: { [s.id]: s }, events: { [s.id]: [promptEvent('e1', s.id, s.createdAt)] } });
119
-
120
- // Window A: markCompleted removes it.
121
- await mergeActiveIndex(cwd, { sessions: {}, events: {}, removedIds: [s.id] });
122
- expect(readActiveIndex(cwd).sessions[s.id]).toBeUndefined();
123
-
124
- // Window B: stale in-memory snapshot still has it as 'active', persists
125
- // its own (unrelated) mutation — a resurrection-prone merge.
126
- const windowBOther = session('psess-2', { status: 'proposed' });
127
- await mergeActiveIndex(cwd, {
128
- sessions: { [s.id]: s, [windowBOther.id]: windowBOther },
129
- events: { [s.id]: [promptEvent('e1', s.id, s.createdAt)] },
130
- });
131
-
132
- const index = readActiveIndex(cwd);
133
- expect(index.sessions[s.id]).toBeUndefined();
134
- expect(index.events[s.id]).toBeUndefined();
135
- expect(index.sessions[windowBOther.id]).toEqual(windowBOther);
136
- });
137
-
138
- test('event union: disk-only events are appended and re-sorted chain-aware; memory-only events kept', async () => {
139
- const cwd = await mkCwd();
140
- const s = session('psess-1');
141
- const e1 = promptEvent('e1', s.id, '2026-08-02T13:00:00.000Z');
142
- await mergeActiveIndex(cwd, { sessions: { [s.id]: s }, events: { [s.id]: [e1] } });
143
-
144
- // A scheduler job appended a response event directly to disk between our
145
- // two merge calls (simulated by mutating the file out of band).
146
- const midIndex = readActiveIndex(cwd);
147
- const schedulerEvent = { id: 'e2', promptSessionId: s.id, kind: 'response', causedByEventId: 'e1', at: '2026-08-02T13:05:00.000Z', text: 'done' };
148
- midIndex.events[s.id] = [e1, schedulerEvent];
149
- await config.writeJson(path.join(cwd, 'session-manager-operations', 'prompt-sessions', 'active-index.json'), midIndex, { writer: 'epics' });
150
-
151
- // This caller's memory still only knows about e1 plus its own optimistic
152
- // append e3 (not yet on disk).
153
- const e3 = { id: 'e3', promptSessionId: s.id, kind: 'prd_created', causedByEventId: 'e2', at: '2026-08-02T13:10:00.000Z', prdSlug: 'x' };
154
- await mergeActiveIndex(cwd, { sessions: { [s.id]: s }, events: { [s.id]: [e1, e3] } });
155
-
156
- const finalEvents = readActiveIndex(cwd).events[s.id];
157
- expect(finalEvents.map((e) => e.id)).toEqual(['e1', 'e2', 'e3']);
158
- });
159
-
160
- // Electron's structured-clone IPC transport (unlike JSON.parse) can produce
161
- // an own-enumerable "__proto__" key on a cloned object. A plain-object merge
162
- // target's bracket assignment with that key would re-link its own prototype
163
- // instead of storing a normal entry, silently dropping/corrupting the Epic
164
- // and potentially breaking every later Object.keys/JSON.stringify over it.
165
- test('a "__proto__"-keyed session id merges as an ordinary entry, not a prototype pollution', async () => {
166
- const cwd = await mkCwd();
167
- const polluted = session('__proto__', { goalText: 'polluted' });
168
- // Computed key, not `{ __proto__: polluted }` — the latter is special-cased
169
- // by object-literal syntax to set the prototype rather than create an own
170
- // enumerable "__proto__" property, which wouldn't exercise this hazard.
171
- await mergeActiveIndex(cwd, { sessions: { ['__proto__']: polluted }, events: {} });
172
-
173
- const index = readActiveIndex(cwd);
174
- expect(Object.prototype.hasOwnProperty.call(index.sessions, '__proto__')).toBe(true);
175
- expect(index.sessions.__proto__).toEqual(polluted);
176
- expect(Object.getPrototypeOf({})).toBe(Object.prototype);
177
- });
178
-
179
- test('falls back to writing memory state alone when the disk index is corrupt JSON', async () => {
180
- const cwd = await mkCwd();
181
- const filePath = path.join(cwd, 'session-manager-operations', 'prompt-sessions', 'active-index.json');
182
- await fsp.mkdir(path.dirname(filePath), { recursive: true });
183
- await fsp.writeFile(filePath, '{ not valid json', 'utf8');
184
-
185
- const s = session('psess-1');
186
- await mergeActiveIndex(cwd, { sessions: { [s.id]: s }, events: {} });
187
-
188
- const index = readActiveIndex(cwd);
189
- expect(index.sessions[s.id]).toEqual(s);
190
- });
191
-
192
- // TOCTOU: a mint interleaved with a merge call must survive, since both now
193
- // serialize through the same withPathLock (epicMint.cjs exports it; this
194
- // module requires it via the same module — Node's require cache means one
195
- // lock instance, not two independent maps that could still interleave).
196
- test('mint-during-persist: an ensureEpic mint interleaved with a merge call survives', async () => {
197
- const cwd = await mkCwd();
198
- const existing = session('psess-existing');
199
- await mergeActiveIndex(cwd, { sessions: { [existing.id]: existing }, events: {} });
200
-
201
- const [mintResult] = await Promise.all([
202
- ensureEpic(cwd, { goalText: 'minted during a concurrent merge', mintAuthority: MINT_AUTHORITY_NEW_EPIC_UI }),
203
- mergeActiveIndex(cwd, { sessions: { [existing.id]: existing }, events: {} }),
204
- ]);
205
-
206
- const index = readActiveIndex(cwd);
207
- expect(index.sessions[mintResult.epicId]).toBeDefined();
208
- expect(index.sessions[existing.id]).toBeDefined();
209
- });
210
-
211
- // The third writer: promptSessionEvents.cjs's appendResponseEventIfKnown
212
- // (the scheduler's narrow 'scheduler'-delegation append) used to hold its
213
- // OWN independent lock map — a merge call and a response-event append could
214
- // still race past each other's stale read. It now imports activeIndexPath/
215
- // withPathLock from epicMint.cjs (same module, same require-cache instance),
216
- // so a response-event append interleaved with a merge call must survive too.
217
- test('a scheduler response-event append interleaved with a merge call survives (third writer shares the same lock)', async () => {
218
- const cwd = await mkCwd();
219
- const s = session('psess-1', { status: 'active' });
220
- await mergeActiveIndex(cwd, {
221
- sessions: { [s.id]: s },
222
- events: { [s.id]: [promptEvent('e1', s.id, s.createdAt)] },
223
- });
224
-
225
- const [, appended] = await Promise.all([
226
- mergeActiveIndex(cwd, { sessions: { [s.id]: s }, events: { [s.id]: [promptEvent('e1', s.id, s.createdAt)] } }),
227
- appendResponseEventIfKnown(cwd, s.id, 'PRD finished'),
228
- ]);
229
-
230
- expect(appended).toBe(true);
231
- const index = readActiveIndex(cwd);
232
- expect(index.sessions[s.id]).toBeDefined();
233
- expect(index.events[s.id]).toHaveLength(2);
234
- expect(index.events[s.id][1].kind).toBe('response');
235
- });
@@ -1,117 +0,0 @@
1
- /**
2
- * agentEffortResolve.test.cjs — the effort twin of agentModelResolve.test.cjs:
3
- * persona `effort:` wins (overlay beats global), inherit/absent/dangling → no flag.
4
- *
5
- * Run: timeout 120 npx vitest run src/main/__tests__/agentEffortResolve.test.cjs
6
- */
7
-
8
- 'use strict';
9
-
10
- import { test, expect, afterEach } from 'vitest';
11
- const fs = require('node:fs');
12
- const fsp = require('node:fs/promises');
13
- const os = require('node:os');
14
- const path = require('node:path');
15
- const { resolveEpicEffort, effortArgs } = require('../lib/agentEffortResolve.cjs');
16
-
17
- const tmpDirs = [];
18
- afterEach(async () => {
19
- while (tmpDirs.length) await fsp.rm(tmpDirs.pop(), { recursive: true, force: true });
20
- });
21
-
22
- async function mkTmpDir(prefix) {
23
- const dir = await fsp.mkdtemp(path.join(os.tmpdir(), prefix));
24
- tmpDirs.push(dir);
25
- return dir;
26
- }
27
-
28
- function writeIndex(cwd, sessions) {
29
- const dir = path.join(cwd, 'session-manager-operations', 'prompt-sessions');
30
- fs.mkdirSync(dir, { recursive: true });
31
- fs.writeFileSync(path.join(dir, 'active-index.json'), JSON.stringify({ sessions, events: {} }));
32
- }
33
-
34
- function writePersona(dir, name, frontmatter) {
35
- fs.mkdirSync(dir, { recursive: true });
36
- fs.writeFileSync(path.join(dir, `${name}.md`), ['---', `name: ${name}`, ...frontmatter, '---', 'body'].join('\n'));
37
- }
38
-
39
- const noopValidatePath = (p) => p;
40
-
41
- test('persona effort wins (global) for a matching Epic', async () => {
42
- const cwd = await mkTmpDir('sm-effort-cwd-');
43
- const globalDir = await mkTmpDir('sm-effort-agents-');
44
- writeIndex(cwd, { e1: { id: 'e1', claudeSessionId: 's1', agentType: 'p' } });
45
- writePersona(globalDir, 'p', ['effort: high']);
46
- expect(resolveEpicEffort({ cwd, claudeSessionId: 's1', deps: { globalDir, validatePath: noopValidatePath } }))
47
- .toEqual({ effort: 'high', source: 'persona' });
48
- });
49
-
50
- test('project overlay beats global', async () => {
51
- const cwd = await mkTmpDir('sm-effort-cwd-');
52
- const globalDir = await mkTmpDir('sm-effort-agents-');
53
- const projectDir = await mkTmpDir('sm-effort-proj-');
54
- writePersona(globalDir, 'p', ['effort: low']);
55
- writePersona(projectDir, 'p', ['effort: max']);
56
- expect(resolveEpicEffort({ cwd, agentType: 'p', deps: { globalDir, projectDir, validatePath: noopValidatePath } }))
57
- .toEqual({ effort: 'max', source: 'persona-overlay' });
58
- });
59
-
60
- test('inherit and absent resolve to null effort', async () => {
61
- const cwd = await mkTmpDir('sm-effort-cwd-');
62
- const globalDir = await mkTmpDir('sm-effort-agents-');
63
- writePersona(globalDir, 'inh', ['effort: inherit']);
64
- writePersona(globalDir, 'none', []);
65
- const deps = { globalDir, validatePath: noopValidatePath };
66
- expect(resolveEpicEffort({ cwd, agentType: 'inh', deps })).toEqual({ effort: null, source: 'inherit' });
67
- expect(resolveEpicEffort({ cwd, agentType: 'none', deps })).toEqual({ effort: null, source: 'inherit' });
68
- });
69
-
70
- test('a persona effort of `auto` (a /effort reset verb) resolves to no flag', async () => {
71
- const cwd = await mkTmpDir('sm-effort-cwd-');
72
- const globalDir = await mkTmpDir('sm-effort-agents-');
73
- writePersona(globalDir, 'au', ['effort: auto']);
74
- const deps = { globalDir, validatePath: noopValidatePath };
75
- const r = resolveEpicEffort({ cwd, agentType: 'au', deps });
76
- expect(r.effort).toBeNull();
77
- expect(effortArgs(r.effort)).toEqual([]);
78
- });
79
-
80
- test('dangling agentType / unknown Epic / empty opts resolve to null without throwing', async () => {
81
- const cwd = await mkTmpDir('sm-effort-cwd-');
82
- const globalDir = await mkTmpDir('sm-effort-agents-');
83
- writeIndex(cwd, {});
84
- const deps = { globalDir, validatePath: noopValidatePath };
85
- expect(resolveEpicEffort({ cwd, agentType: 'gone', deps })).toEqual({ effort: null, source: null });
86
- expect(resolveEpicEffort({ cwd, claudeSessionId: 'nope', deps })).toEqual({ effort: null, source: null });
87
- expect(resolveEpicEffort({})).toEqual({ effort: null, source: null });
88
- expect(resolveEpicEffort({ cwd, agentType: '../../etc/passwd', deps })).toEqual({ effort: null, source: null });
89
- });
90
-
91
- test('an unknown level is passed through unchanged (CLI warns and degrades)', async () => {
92
- const cwd = await mkTmpDir('sm-effort-cwd-');
93
- const globalDir = await mkTmpDir('sm-effort-agents-');
94
- writePersona(globalDir, 'odd', ['effort: turbo']);
95
- expect(resolveEpicEffort({ cwd, agentType: 'odd', deps: { globalDir, validatePath: noopValidatePath } }).effort).toBe('turbo');
96
- });
97
-
98
- test('effortArgs emits the pair only for a real level', () => {
99
- expect(effortArgs('high')).toEqual(['--effort', 'high']);
100
- for (const v of [null, undefined, '', 'inherit']) expect(effortArgs(v)).toEqual([]);
101
- });
102
-
103
- // Epic-level override (New Session card) > persona frontmatter > no flag.
104
- test('an Epic-level effort override beats the persona effort; an Epic without the field resolves unchanged', async () => {
105
- const cwd = await mkTmpDir('sm-effort-ovr-cwd-');
106
- const globalDir = await mkTmpDir('sm-effort-ovr-agents-');
107
- writeIndex(cwd, {
108
- withOverride: { id: 'withOverride', claudeSessionId: 's-ovr', agentType: 'p', effort: 'max' },
109
- legacy: { id: 'legacy', claudeSessionId: 's-old', agentType: 'p' },
110
- noPersona: { id: 'noPersona', claudeSessionId: 's-np', effort: 'low' },
111
- });
112
- writePersona(globalDir, 'p', ['effort: low']);
113
- const deps = { globalDir, validatePath: noopValidatePath };
114
- expect(resolveEpicEffort({ cwd, claudeSessionId: 's-ovr', deps })).toEqual({ effort: 'max', source: 'epic' });
115
- expect(resolveEpicEffort({ cwd, claudeSessionId: 's-old', deps })).toEqual({ effort: 'low', source: 'persona' });
116
- expect(resolveEpicEffort({ cwd, claudeSessionId: 's-np', deps })).toEqual({ effort: 'low', source: 'epic' });
117
- });
@@ -1,276 +0,0 @@
1
- /**
2
- * agentLibrary.test.cjs — unit tests for the Agent Library nav page's
3
- * backend: enumerating global `~/.claude/agents/*.md` personas and
4
- * detecting per-project `.claude/agents/<name>.md` overlays among
5
- * currently-open tabs.
6
- *
7
- * Run: timeout 120 npx vitest run src/main/__tests__/agentLibrary.test.cjs
8
- */
9
-
10
- import { test, expect, afterEach } from 'vitest';
11
- const fs = require('node:fs');
12
- const fsp = require('node:fs/promises');
13
- const os = require('node:os');
14
- const path = require('node:path');
15
- const { listPersonas, openProjects, parseTools, savePersona, deletePersona, removeOverride } = require('../agentLibrary.cjs');
16
-
17
- const tmpDirs = [];
18
- afterEach(async () => {
19
- while (tmpDirs.length) {
20
- const d = tmpDirs.pop();
21
- await fsp.rm(d, { recursive: true, force: true });
22
- }
23
- });
24
-
25
- async function mkTmp(prefix) {
26
- const d = await fsp.mkdtemp(path.join(os.tmpdir(), prefix));
27
- tmpDirs.push(d);
28
- return d;
29
- }
30
-
31
- // listPersonas/openProjects take injectable deps; tests pass an identity
32
- // validatePath since the fixture dirs live under os.tmpdir(), not the real
33
- // home directory config.cjs's validatePath would otherwise enforce.
34
- const identityValidatePath = (p) => p;
35
-
36
- // savePersona/deletePersona/removeOverride take an injectable writeTextAtomic
37
- // too, since the real config.cjs one enforces its own home-dir validatePath
38
- // regardless of what's injected for the `validatePath` param here.
39
- async function fakeWriteTextAtomic(abs, text) {
40
- await fsp.mkdir(path.dirname(abs), { recursive: true });
41
- await fsp.writeFile(abs, text, 'utf8');
42
- return { ok: true, mtimeMs: Date.now() };
43
- }
44
-
45
- test('parseTools splits + trims a comma-separated tools frontmatter value', () => {
46
- expect(parseTools('Read, Grep, Glob, Bash')).toEqual(['Read', 'Grep', 'Glob', 'Bash']);
47
- expect(parseTools('')).toEqual([]);
48
- expect(parseTools(undefined)).toEqual([]);
49
- });
50
-
51
- test('openProjects dedups by cwd and uses the last path segment as name', async () => {
52
- const loadSessions = async () => ({
53
- tabs: [
54
- { cwd: '/home/u/Projects/alpha' },
55
- { cwd: '/home/u/Projects/beta/' },
56
- { cwd: '/home/u/Projects/alpha' }, // duplicate — should collapse
57
- { cwd: null }, // malformed — should be skipped
58
- ],
59
- });
60
- const projects = await openProjects({ loadSessions });
61
- expect(projects).toEqual([
62
- { cwd: '/home/u/Projects/alpha', name: 'alpha' },
63
- { cwd: '/home/u/Projects/beta/', name: 'beta' },
64
- ]);
65
- });
66
-
67
- test('listPersonas returns [] when the global agents dir does not exist', async () => {
68
- const missingDir = path.join(os.tmpdir(), 'sm-agent-library-does-not-exist-' + Date.now());
69
- const personas = await listPersonas({
70
- globalDir: missingDir,
71
- loadSessions: async () => ({ tabs: [] }),
72
- validatePath: identityValidatePath,
73
- });
74
- expect(personas).toEqual([]);
75
- });
76
-
77
- test('listPersonas parses frontmatter and reports overridingProjects for open tabs with a local overlay', async () => {
78
- const globalDir = await mkTmp('sm-agent-library-global-');
79
- await fsp.writeFile(
80
- path.join(globalDir, 'builder.md'),
81
- [
82
- '---',
83
- 'name: builder',
84
- 'description: Watch git history and drive the next publish.',
85
- 'tools: Read, Grep, Glob, Bash',
86
- 'tags: feature, bug',
87
- '---',
88
- '',
89
- 'You are the Builder agent.',
90
- '',
91
- ].join('\n'),
92
- );
93
- await fsp.writeFile(
94
- path.join(globalDir, 'debugger.md'),
95
- ['---', 'name: debugger', 'description: Diagnose a failing test.', '---', '', 'Body.', ''].join('\n'),
96
- );
97
-
98
- const projectWithOverlay = await mkTmp('sm-agent-library-project-a-');
99
- await fsp.mkdir(path.join(projectWithOverlay, '.claude', 'agents'), { recursive: true });
100
- await fsp.writeFile(
101
- path.join(projectWithOverlay, '.claude', 'agents', 'builder.md'),
102
- '---\nname: builder\n---\nProject-specific overlay.\n',
103
- );
104
-
105
- const projectWithoutOverlay = await mkTmp('sm-agent-library-project-b-');
106
-
107
- const loadSessions = async () => ({
108
- tabs: [{ cwd: projectWithOverlay }, { cwd: projectWithoutOverlay }],
109
- });
110
-
111
- const personas = await listPersonas({ globalDir, loadSessions, validatePath: identityValidatePath });
112
- expect(personas).toHaveLength(2);
113
-
114
- const byName = Object.fromEntries(personas.map((p) => [p.name, p]));
115
- expect(byName.builder.description).toBe('Watch git history and drive the next publish.');
116
- expect(byName.builder.tags).toEqual(['feature', 'bug']);
117
- expect(byName.debugger.tags).toEqual([]);
118
- expect(byName.builder.tools).toEqual(['Read', 'Grep', 'Glob', 'Bash']);
119
- expect(byName.builder.body).toContain('You are the Builder agent.');
120
- expect(byName.builder.overridingProjects).toEqual([path.basename(projectWithOverlay)]);
121
-
122
- expect(byName.debugger.overridingProjects).toEqual([]);
123
- });
124
-
125
- test('listPersonas skips a project cwd that validatePath rejects, rather than throwing', async () => {
126
- const globalDir = await mkTmp('sm-agent-library-global-');
127
- await fsp.writeFile(path.join(globalDir, 'builder.md'), '---\nname: builder\n---\nBody.\n');
128
-
129
- const rejectedProject = await mkTmp('sm-agent-library-project-rejected-');
130
- await fsp.mkdir(path.join(rejectedProject, '.claude', 'agents'), { recursive: true });
131
- await fsp.writeFile(path.join(rejectedProject, '.claude', 'agents', 'builder.md'), 'overlay\n');
132
-
133
- const loadSessions = async () => ({ tabs: [{ cwd: rejectedProject }] });
134
- // Simulate config.cjs's validatePath throwing for anything under the
135
- // rejected project (as it would for a cwd outside the allowed-roots set)
136
- // while still allowing the global agents dir itself to resolve.
137
- const selectiveValidatePath = (p) => {
138
- if (p.startsWith(rejectedProject)) throw new Error('outside allowed boundaries');
139
- return p;
140
- };
141
-
142
- const personas = await listPersonas({ globalDir, loadSessions, validatePath: selectiveValidatePath });
143
- expect(personas).toHaveLength(1);
144
- expect(personas[0].overridingProjects).toEqual([]);
145
- });
146
-
147
- test('savePersona writes frontmatter + body, and rejects a non-slug name', async () => {
148
- const globalDir = await mkTmp('sm-agent-library-save-');
149
- await savePersona({
150
- name: 'my-agent',
151
- description: 'Does a thing.',
152
- tools: ['Read', 'Grep'],
153
- model: 'opus',
154
- color: 'blue',
155
- tags: ['feature', 'bug'],
156
- body: 'You are my-agent.',
157
- globalDir,
158
- validatePath: identityValidatePath,
159
- writeTextAtomic: fakeWriteTextAtomic,
160
- });
161
- const text = await fsp.readFile(path.join(globalDir, 'my-agent.md'), 'utf8');
162
- expect(text).toContain('name: my-agent');
163
- expect(text).toContain('description: Does a thing.');
164
- expect(text).toContain('tools: Read, Grep');
165
- expect(text).toContain('model: opus');
166
- expect(text).toContain('color: blue');
167
- expect(text).toContain('tags: feature, bug');
168
- expect(text).toContain('You are my-agent.');
169
-
170
- await expect(
171
- savePersona({ name: 'Not Valid', body: '', globalDir, validatePath: identityValidatePath, writeTextAtomic: fakeWriteTextAtomic }),
172
- ).rejects.toThrow(/lowercase, hyphenated/);
173
- });
174
-
175
- test('savePersona with originalName renames — writes the new file and removes the old one', async () => {
176
- const globalDir = await mkTmp('sm-agent-library-rename-');
177
- await fsp.writeFile(path.join(globalDir, 'old-name.md'), '---\nname: old-name\n---\nBody.\n');
178
- await savePersona({
179
- name: 'new-name',
180
- originalName: 'old-name',
181
- description: '',
182
- tools: [],
183
- model: 'inherit',
184
- color: '',
185
- body: 'Body.',
186
- globalDir,
187
- validatePath: identityValidatePath,
188
- writeTextAtomic: fakeWriteTextAtomic,
189
- });
190
- expect(fs.existsSync(path.join(globalDir, 'new-name.md'))).toBe(true);
191
- expect(fs.existsSync(path.join(globalDir, 'old-name.md'))).toBe(false);
192
- });
193
-
194
- test('deletePersona removes the file, and is a no-op when it is already gone', async () => {
195
- const globalDir = await mkTmp('sm-agent-library-delete-');
196
- await fsp.writeFile(path.join(globalDir, 'gone-soon.md'), '---\nname: gone-soon\n---\nBody.\n');
197
- await deletePersona({ name: 'gone-soon', globalDir, validatePath: identityValidatePath });
198
- expect(fs.existsSync(path.join(globalDir, 'gone-soon.md'))).toBe(false);
199
- await expect(deletePersona({ name: 'gone-soon', globalDir, validatePath: identityValidatePath })).resolves.toEqual({ ok: true });
200
- });
201
-
202
- test('removeOverride deletes a project overlay resolved by project name, and rejects an unknown project', async () => {
203
- const project = await mkTmp('sm-agent-library-override-');
204
- await fsp.mkdir(path.join(project, '.claude', 'agents'), { recursive: true });
205
- const overlayPath = path.join(project, '.claude', 'agents', 'builder.md');
206
- await fsp.writeFile(overlayPath, 'overlay\n');
207
- const loadSessions = async () => ({ tabs: [{ cwd: project }] });
208
-
209
- await removeOverride({ name: 'builder', projectName: path.basename(project), loadSessions, validatePath: identityValidatePath });
210
- expect(fs.existsSync(overlayPath)).toBe(false);
211
-
212
- await expect(
213
- removeOverride({ name: 'builder', projectName: 'not-open', loadSessions, validatePath: identityValidatePath }),
214
- ).rejects.toThrow(/project not open/);
215
- });
216
-
217
- test('projects/action/actionLabel round-trip through savePersona -> listPersonas, with newlines preserved', async () => {
218
- const globalDir = await mkTmp('sm-agent-library-action-');
219
- const action = '/builder\n\nCheck git vs the published package and publish anything new.';
220
- await savePersona({
221
- name: 'builder',
222
- description: 'Drives the next publish.',
223
- tools: ['Bash'],
224
- model: 'inherit',
225
- color: '',
226
- tags: ['build'],
227
- projects: ['/home/bilko/Projects/alpha', '/home/bilko/Projects/beta'],
228
- action,
229
- actionLabel: 'Run Build',
230
- body: 'Body.',
231
- globalDir,
232
- validatePath: identityValidatePath,
233
- writeTextAtomic: fakeWriteTextAtomic,
234
- });
235
-
236
- // The frontmatter parser is line-based, so the multi-line action must be
237
- // stored escaped — a raw newline would silently truncate it on read.
238
- const raw = await fsp.readFile(path.join(globalDir, 'builder.md'), 'utf8');
239
- expect(raw).toContain('action: /builder\\n\\nCheck git');
240
- expect(raw).toContain('projects: /home/bilko/Projects/alpha, /home/bilko/Projects/beta');
241
-
242
- const [p] = await listPersonas({ globalDir, loadSessions: async () => ({ tabs: [] }), validatePath: identityValidatePath });
243
- expect(p.projects).toEqual(['/home/bilko/Projects/alpha', '/home/bilko/Projects/beta']);
244
- expect(p.action).toBe(action);
245
- expect(p.actionLabel).toBe('Run Build');
246
- });
247
-
248
- test('a persona with no Action fields reports them as empty/null rather than undefined', async () => {
249
- const globalDir = await mkTmp('sm-agent-library-noaction-');
250
- await fsp.writeFile(path.join(globalDir, 'plain.md'), '---\nname: plain\n---\nBody.\n');
251
- const [p] = await listPersonas({ globalDir, loadSessions: async () => ({ tabs: [] }), validatePath: identityValidatePath });
252
- expect(p.projects).toEqual([]);
253
- expect(p.action).toBeNull();
254
- expect(p.actionLabel).toBeNull();
255
- });
256
-
257
- test('effort: written when set, omitted for inherit/unset, round-trips, and untouched personas gain no line', async () => {
258
- const globalDir = await mkTmp('sm-agent-library-effort-');
259
- const base = { tools: [], color: '', tags: [], body: 'B.', globalDir, validatePath: identityValidatePath, writeTextAtomic: fakeWriteTextAtomic };
260
- await savePersona({ ...base, name: 'with-effort', model: 'opus', effort: 'xhigh' });
261
- await savePersona({ ...base, name: 'inherit-effort', model: 'opus', effort: 'inherit' });
262
- await savePersona({ ...base, name: 'unset-effort', model: 'opus' });
263
- const raw = (n) => fsp.readFile(path.join(globalDir, `${n}.md`), 'utf8');
264
- expect(await raw('with-effort')).toContain('effort: xhigh');
265
- expect(await raw('inherit-effort')).not.toContain('effort:');
266
- expect(await raw('unset-effort')).not.toContain('effort:');
267
-
268
- const ps = await listPersonas({ globalDir, loadSessions: async () => ({ tabs: [] }), validatePath: identityValidatePath });
269
- const by = Object.fromEntries(ps.map((p) => [p.name, p]));
270
- expect(by['with-effort'].effort).toBe('xhigh');
271
- expect(by['unset-effort'].effort).toBeNull();
272
-
273
- // Untouched save of a no-effort persona (UI maps null -> 'inherit') must not add a line.
274
- await savePersona({ ...base, name: 'unset-effort', model: 'opus', effort: by['unset-effort'].effort ?? 'inherit' });
275
- expect(await raw('unset-effort')).not.toContain('effort:');
276
- });