claude-code-session-manager 0.98.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 (431) 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-DCEgMTjH.js → DataModel-HdR0AxFq.js} +1 -1
  5. package/dist/assets/{History-Bfdoywlh.js → History-Dr4Jr_5z.js} +2 -2
  6. package/dist/assets/{Hooks-CKDJjTJV.js → Hooks-NhrvH3JP.js} +3 -3
  7. package/dist/assets/{Library-7akfgy67.js → Library-Dv_EtIMr.js} +1 -1
  8. package/dist/assets/{MarkdownEditor-DDesiqet.js → MarkdownEditor-DTKDWV_n.js} +1 -1
  9. package/dist/assets/{McpServers-D4lZtXof.js → McpServers-M034G9B3.js} +2 -2
  10. package/dist/assets/{Memory-CeI8G1w5.js → Memory-DzF1B4gx.js} +6 -6
  11. package/dist/assets/{Permissions-BGleLja4.js → Permissions-DrgbSGWl.js} +3 -3
  12. package/dist/assets/{Plugins-P4eCXy_S.js → Plugins-Bd3ef35w.js} +2 -2
  13. package/dist/assets/{ProvenanceBadge-BFczzI1B.js → ProvenanceBadge-DcBHT1Iy.js} +1 -1
  14. package/dist/assets/{SaveBar-l9jnEMES.js → SaveBar-rimJem-Y.js} +1 -1
  15. package/dist/assets/Scheduler-DjgFJl2w.js +16 -0
  16. package/dist/assets/{ScopeSwitcher-CjJi8ST5.js → ScopeSwitcher-CnAzTBrl.js} +1 -1
  17. package/dist/assets/Settings-Dc1F_sSg.js +3 -0
  18. package/dist/assets/{SkillReferenceGraph-CjbO01Sg.js → SkillReferenceGraph-B9aZDecM.js} +1 -1
  19. package/dist/assets/{Skills-BxZ9_fCR.js → Skills-DRXd18gu.js} +2 -2
  20. package/dist/assets/{SystemPrompt-D8vfFb8b.js → SystemPrompt-ahy5-Fwe.js} +1 -1
  21. package/dist/assets/{TagLibrary-2vzUo7YL.js → TagLibrary-C_tToDeL.js} +1 -1
  22. package/dist/assets/{TiptapBody-B8ZfbK90.js → TiptapBody-DvdefS6K.js} +1 -1
  23. package/dist/assets/{Toggle-BbW4nGGf.js → Toggle-6Jl5Njc6.js} +1 -1
  24. package/dist/assets/{index-CKH5Uxik.css → index-Bta-hwud.css} +1 -1
  25. package/dist/assets/{index-Cd98Q1lD.js → index-vx8O73l8.js} +497 -502
  26. package/dist/assets/{settingsSchema-IhUfucCF.js → settingsSchema-D6iRguNV.js} +1 -1
  27. package/dist/index.html +2 -2
  28. package/package.json +25 -29
  29. package/plugins/CLAUDE.md +6 -6
  30. package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +21 -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 +121 -12
  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 +12 -25
  91. package/src/main/lib/workTypeLibrary.cjs +7 -1
  92. package/src/main/promptSessionEvents.cjs +37 -13
  93. package/src/main/scheduler.cjs +910 -198
  94. package/src/main/seedAgentPersonas.cjs +278 -23
  95. package/src/main/supervisor.cjs +4 -1
  96. package/src/main/templates/PRD_AUTHORING.md +126 -406
  97. package/src/preload/__tests__/preload-surface.test.cjs +68 -0
  98. package/src/preload/api.d.ts +41 -94
  99. package/src/preload/index.cjs +14 -9
  100. package/src/seed/agents/architect.md +1 -0
  101. package/src/seed/agents/dev-lead.md +17 -18
  102. package/src/seed/agents/project-home-builder.md +1 -0
  103. package/src/seed/agents/validator.md +10 -4
  104. package/dist/assets/HostBilko-CDVHE0hT.js +0 -1
  105. package/dist/assets/Scheduler-C_phpeUS.js +0 -16
  106. package/dist/assets/Settings-BIwZ3FGQ.js +0 -3
  107. package/src/main/__tests__/activeIndexMerge.test.cjs +0 -235
  108. package/src/main/__tests__/agentEffortResolve.test.cjs +0 -117
  109. package/src/main/__tests__/agentLibrary.test.cjs +0 -276
  110. package/src/main/__tests__/agentModelResolve.test.cjs +0 -311
  111. package/src/main/__tests__/agentOverlayWrite.test.cjs +0 -95
  112. package/src/main/__tests__/bilkoHost-deriveSlug.test.cjs +0 -26
  113. package/src/main/__tests__/bilkoHost-integration.test.cjs +0 -118
  114. package/src/main/__tests__/bilkoHostCore.test.cjs +0 -72
  115. package/src/main/__tests__/broadcastCoalescer.test.cjs +0 -122
  116. package/src/main/__tests__/chat-cancel-terminal.test.cjs +0 -120
  117. package/src/main/__tests__/chat-dead-channels.test.cjs +0 -63
  118. package/src/main/__tests__/chat-exit-close-race.test.cjs +0 -146
  119. package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +0 -139
  120. package/src/main/__tests__/chat-preamble-anchors.test.cjs +0 -100
  121. package/src/main/__tests__/chat-queue.test.cjs +0 -97
  122. package/src/main/__tests__/chat-stop-signal.test.cjs +0 -89
  123. package/src/main/__tests__/chatRunner-epic-worktree-execcwd.test.cjs +0 -125
  124. package/src/main/__tests__/chatRunner-session-flag-retry.test.cjs +0 -252
  125. package/src/main/__tests__/classifyPromptTicket.test.cjs +0 -101
  126. package/src/main/__tests__/classifyTranscriptLine.test.cjs +0 -201
  127. package/src/main/__tests__/computeDepHistorySatisfaction.test.cjs +0 -66
  128. package/src/main/__tests__/config-readText-bounded.test.cjs +0 -84
  129. package/src/main/__tests__/configWriteBoundaryOwners.test.cjs +0 -59
  130. package/src/main/__tests__/crossProjectFeedback.test.cjs +0 -334
  131. package/src/main/__tests__/crossProjectFeedbackRoutes.test.cjs +0 -161
  132. package/src/main/__tests__/dep-orphan-archive-health.test.cjs +0 -77
  133. package/src/main/__tests__/develop-skill-failure-modes.test.cjs +0 -70
  134. package/src/main/__tests__/docEdit.test.cjs +0 -244
  135. package/src/main/__tests__/dod-batchkey.test.cjs +0 -183
  136. package/src/main/__tests__/dod-drain-hook.test.cjs +0 -302
  137. package/src/main/__tests__/dod-report.test.cjs +0 -304
  138. package/src/main/__tests__/dod-reverify.test.cjs +0 -285
  139. package/src/main/__tests__/epicContextDigest.test.cjs +0 -174
  140. package/src/main/__tests__/epicMint.test.cjs +0 -332
  141. package/src/main/__tests__/epicMintTelemetryTap.test.cjs +0 -64
  142. package/src/main/__tests__/epicStatusMirror.test.cjs +0 -110
  143. package/src/main/__tests__/epicValidationHook.test.cjs +0 -291
  144. package/src/main/__tests__/exchanges.test.cjs +0 -122
  145. package/src/main/__tests__/exchangesPromptId.test.cjs +0 -61
  146. package/src/main/__tests__/extractJson.test.cjs +0 -51
  147. package/src/main/__tests__/files-reject-credentials.test.cjs +0 -40
  148. package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +0 -556
  149. package/src/main/__tests__/flatPrdTickSweep.test.cjs +0 -110
  150. package/src/main/__tests__/health-build-freshness.test.cjs +0 -39
  151. package/src/main/__tests__/health-claude-md-budget.test.cjs +0 -57
  152. package/src/main/__tests__/health-credentials.test.cjs +0 -81
  153. package/src/main/__tests__/health-delegation-chain.test.cjs +0 -124
  154. package/src/main/__tests__/health-per-project-stall.test.cjs +0 -84
  155. package/src/main/__tests__/health-prd-migration.test.cjs +0 -37
  156. package/src/main/__tests__/health-queue-dispatch.test.cjs +0 -135
  157. package/src/main/__tests__/health-starve-escalation.test.cjs +0 -94
  158. package/src/main/__tests__/health-tick-liveness.test.cjs +0 -179
  159. package/src/main/__tests__/health-usage-poller.test.cjs +0 -144
  160. package/src/main/__tests__/health-worktree-cap-blocked.test.cjs +0 -65
  161. package/src/main/__tests__/heapSnapshot.test.cjs +0 -121
  162. package/src/main/__tests__/historyAggregatorIntraday.test.cjs +0 -313
  163. package/src/main/__tests__/historyDashboard.test.cjs +0 -163
  164. package/src/main/__tests__/historyRollup.test.cjs +0 -333
  165. package/src/main/__tests__/intradayRefresh.test.cjs +0 -39
  166. package/src/main/__tests__/ipcSchemas-dependsOn.test.cjs +0 -39
  167. package/src/main/__tests__/kg-augment.test.cjs +0 -195
  168. package/src/main/__tests__/loadGateDetailTick.test.cjs +0 -31
  169. package/src/main/__tests__/machineProfile.test.cjs +0 -152
  170. package/src/main/__tests__/mcpStatus.test.cjs +0 -61
  171. package/src/main/__tests__/memoryAggregate.test.cjs +0 -109
  172. package/src/main/__tests__/memoryStale.test.cjs +0 -88
  173. package/src/main/__tests__/needsReviewLedger.test.cjs +0 -162
  174. package/src/main/__tests__/openExternalApp-spawn-error.test.cjs +0 -25
  175. package/src/main/__tests__/opsErrorLog.test.cjs +0 -109
  176. package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +0 -173
  177. package/src/main/__tests__/personaMerge.test.cjs +0 -169
  178. package/src/main/__tests__/planValidator.test.cjs +0 -125
  179. package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +0 -176
  180. package/src/main/__tests__/prd-group-allocator.test.cjs +0 -119
  181. package/src/main/__tests__/prdAdminRouteParity.test.cjs +0 -70
  182. package/src/main/__tests__/prdAdminRoutes.test.cjs +0 -718
  183. package/src/main/__tests__/prdAgentType.test.cjs +0 -103
  184. package/src/main/__tests__/prdAuthoringSeed.test.cjs +0 -39
  185. package/src/main/__tests__/prdCreate.test.cjs +0 -979
  186. package/src/main/__tests__/prdCreateAdoption.test.cjs +0 -205
  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 -211
  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-mutate-reentrancy.test.cjs +0 -117
  282. package/src/main/__tests__/scheduler-needs-review-autoresolve.test.cjs +0 -197
  283. package/src/main/__tests__/scheduler-never-stop.test.cjs +0 -157
  284. package/src/main/__tests__/scheduler-no-dead-end-status.test.cjs +0 -152
  285. package/src/main/__tests__/scheduler-no-orphan-run-dir.test.cjs +0 -81
  286. package/src/main/__tests__/scheduler-notify-originating-tab-transcript.test.cjs +0 -87
  287. package/src/main/__tests__/scheduler-notify-originating-tab.test.cjs +0 -343
  288. package/src/main/__tests__/scheduler-periodic-reverify-guard.test.cjs +0 -217
  289. package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +0 -164
  290. package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +0 -161
  291. package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +0 -178
  292. package/src/main/__tests__/scheduler-quarantine-autoresolve.test.cjs +0 -165
  293. package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +0 -257
  294. package/src/main/__tests__/scheduler-rate-limit-cooldown-freshness.test.cjs +0 -123
  295. package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +0 -152
  296. package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +0 -156
  297. package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +0 -754
  298. package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +0 -87
  299. package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +0 -100
  300. package/src/main/__tests__/scheduler-reconcile-history-backfill.test.cjs +0 -105
  301. package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +0 -203
  302. package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +0 -247
  303. package/src/main/__tests__/scheduler-reset-job-fields-guard.test.cjs +0 -77
  304. package/src/main/__tests__/scheduler-resume-recovery.test.cjs +0 -254
  305. package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +0 -115
  306. package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +0 -380
  307. package/src/main/__tests__/scheduler-sigterm-commit.test.cjs +0 -43
  308. package/src/main/__tests__/scheduler-stall-per-project.test.cjs +0 -126
  309. package/src/main/__tests__/scheduler-starve-escalation.test.cjs +0 -154
  310. package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +0 -252
  311. package/src/main/__tests__/scheduler-stranded-investigation.test.cjs +0 -185
  312. package/src/main/__tests__/scheduler-stuck-failed-escalation.test.cjs +0 -136
  313. package/src/main/__tests__/scheduler-supervisor-record.test.cjs +0 -81
  314. package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +0 -54
  315. package/src/main/__tests__/scheduler-tick-wedge.test.cjs +0 -172
  316. package/src/main/__tests__/scheduler-transient-failure.test.cjs +0 -141
  317. package/src/main/__tests__/scheduler-unreadable-queue-guard.test.cjs +0 -62
  318. package/src/main/__tests__/scheduler-utilization-hold.test.cjs +0 -89
  319. package/src/main/__tests__/scheduler-verify-prd-path.test.cjs +0 -109
  320. package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +0 -234
  321. package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +0 -120
  322. package/src/main/__tests__/scheduler-writeprd-epic-rollback.test.cjs +0 -105
  323. package/src/main/__tests__/schedulerBatchRootBlocker.test.cjs +0 -117
  324. package/src/main/__tests__/schedulerStateSidecarRestore.test.cjs +0 -110
  325. package/src/main/__tests__/seedAgentPersonas.test.cjs +0 -184
  326. package/src/main/__tests__/seedSchedulerMcp.test.cjs +0 -211
  327. package/src/main/__tests__/seedStatus.test.cjs +0 -100
  328. package/src/main/__tests__/seedValidatorPersona.test.cjs +0 -116
  329. package/src/main/__tests__/stop-signal-anchor.test.cjs +0 -75
  330. package/src/main/__tests__/telemetryClient.test.cjs +0 -1055
  331. package/src/main/__tests__/telemetryContract.test.cjs +0 -930
  332. package/src/main/__tests__/telemetrySettings.test.cjs +0 -210
  333. package/src/main/__tests__/transcripts-batch-flush.test.cjs +0 -249
  334. package/src/main/__tests__/transcripts-doFlush-array.test.cjs +0 -124
  335. package/src/main/__tests__/transcripts-paged-reads.test.cjs +0 -241
  336. package/src/main/__tests__/transcripts-worktree-epic-path.test.cjs +0 -154
  337. package/src/main/__tests__/transcriptsUsageFor.test.cjs +0 -206
  338. package/src/main/__tests__/uniquePrdNumbers.test.cjs +0 -153
  339. package/src/main/__tests__/usageSingleFlight.test.cjs +0 -169
  340. package/src/main/__tests__/validationSentinels.test.cjs +0 -84
  341. package/src/main/__tests__/workTypeLibrary.test.cjs +0 -89
  342. package/src/main/bilkoHost.cjs +0 -314
  343. package/src/main/bilkoHostCore.cjs +0 -89
  344. package/src/main/lib/__tests__/active-sessions.test.cjs +0 -251
  345. package/src/main/lib/__tests__/activeIndexRebuild.test.cjs +0 -179
  346. package/src/main/lib/__tests__/agentPersonaSchema.test.cjs +0 -67
  347. package/src/main/lib/__tests__/auditLog.test.cjs +0 -38
  348. package/src/main/lib/__tests__/bootSelfHeal.test.cjs +0 -107
  349. package/src/main/lib/__tests__/branchSweep.test.cjs +0 -164
  350. package/src/main/lib/__tests__/buildIdentity.test.cjs +0 -121
  351. package/src/main/lib/__tests__/buildTarget.test.cjs +0 -52
  352. package/src/main/lib/__tests__/childWithLog.test.cjs +0 -321
  353. package/src/main/lib/__tests__/coldBootPromptSessionsWrite.test.cjs +0 -87
  354. package/src/main/lib/__tests__/crashTelemetry.test.cjs +0 -103
  355. package/src/main/lib/__tests__/credentials-futile-refresh.test.cjs +0 -115
  356. package/src/main/lib/__tests__/cwdClassify.test.cjs +0 -111
  357. package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +0 -95
  358. package/src/main/lib/__tests__/delegationReadiness.test.cjs +0 -1175
  359. package/src/main/lib/__tests__/dispatchLoop.test.cjs +0 -63
  360. package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +0 -244
  361. package/src/main/lib/__tests__/ephemeralCwd.test.cjs +0 -91
  362. package/src/main/lib/__tests__/epicDelegationStats.test.cjs +0 -137
  363. package/src/main/lib/__tests__/epicSpawnCwd.test.cjs +0 -283
  364. package/src/main/lib/__tests__/epicSpawnPlan.test.cjs +0 -196
  365. package/src/main/lib/__tests__/epicTranscriptPath.test.cjs +0 -163
  366. package/src/main/lib/__tests__/epicWorktreeBoot.test.cjs +0 -136
  367. package/src/main/lib/__tests__/epicWorktreeMerge.test.cjs +0 -130
  368. package/src/main/lib/__tests__/epicWorktreeMint.test.cjs +0 -117
  369. package/src/main/lib/__tests__/epicWorktreeProjectConfig.test.cjs +0 -137
  370. package/src/main/lib/__tests__/fixChainDepth.test.cjs +0 -40
  371. package/src/main/lib/__tests__/fixtures/204-mercury-steam-horse.log.txt +0 -13
  372. package/src/main/lib/__tests__/fixtures/scheduler-machine.json.corrupt-1789147548 +0 -34
  373. package/src/main/lib/__tests__/gateFixtures.json +0 -20
  374. package/src/main/lib/__tests__/gitCacheBound.test.cjs +0 -69
  375. package/src/main/lib/__tests__/gitWorktree.test.cjs +0 -1478
  376. package/src/main/lib/__tests__/gitWorktreeSalvage.test.cjs +0 -107
  377. package/src/main/lib/__tests__/gitWorktreeSalvageDelta.test.cjs +0 -153
  378. package/src/main/lib/__tests__/guardShims.test.cjs +0 -158
  379. package/src/main/lib/__tests__/importReferences.spec.cjs +0 -56
  380. package/src/main/lib/__tests__/instanceLock.test.cjs +0 -173
  381. package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +0 -78
  382. package/src/main/lib/__tests__/jobWorktree.test.cjs +0 -199
  383. package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +0 -82
  384. package/src/main/lib/__tests__/landedSinceRun.test.cjs +0 -133
  385. package/src/main/lib/__tests__/launchFailure.test.cjs +0 -220
  386. package/src/main/lib/__tests__/loadGate.test.cjs +0 -303
  387. package/src/main/lib/__tests__/localAdminHttp.test.cjs +0 -214
  388. package/src/main/lib/__tests__/loopDelay.test.cjs +0 -68
  389. package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +0 -107
  390. package/src/main/lib/__tests__/modelCatalog.test.cjs +0 -202
  391. package/src/main/lib/__tests__/opsOwnership.test.cjs +0 -113
  392. package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +0 -328
  393. package/src/main/lib/__tests__/opsRootNestedWrite.test.cjs +0 -51
  394. package/src/main/lib/__tests__/opsRootResolve.test.cjs +0 -149
  395. package/src/main/lib/__tests__/prdDeclaredPaths.test.cjs +0 -82
  396. package/src/main/lib/__tests__/prdDisposition.test.cjs +0 -224
  397. package/src/main/lib/__tests__/procIdentity.test.cjs +0 -119
  398. package/src/main/lib/__tests__/procName.test.cjs +0 -92
  399. package/src/main/lib/__tests__/projectBriefCore.test.cjs +0 -216
  400. package/src/main/lib/__tests__/projectRootResolve.test.cjs +0 -148
  401. package/src/main/lib/__tests__/queueHealth.test.cjs +0 -58
  402. package/src/main/lib/__tests__/queueStoreAtomicWrite.test.cjs +0 -88
  403. package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +0 -190
  404. package/src/main/lib/__tests__/quietMachineLease.test.cjs +0 -39
  405. package/src/main/lib/__tests__/rateLimitWindow.test.cjs +0 -88
  406. package/src/main/lib/__tests__/reaperHelpers.test.cjs +0 -577
  407. package/src/main/lib/__tests__/schedulerBatchDepends.test.cjs +0 -312
  408. package/src/main/lib/__tests__/schedulerBatchFairness.test.cjs +0 -213
  409. package/src/main/lib/__tests__/schedulerBatchLaunchHold.test.cjs +0 -125
  410. package/src/main/lib/__tests__/schedulerBatchProjectCap.test.cjs +0 -127
  411. package/src/main/lib/__tests__/schedulerBatchQuietMachine.test.cjs +0 -109
  412. package/src/main/lib/__tests__/schedulerMcpServerHeadlessRefusal.test.cjs +0 -71
  413. package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +0 -216
  414. package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +0 -183
  415. package/src/main/lib/__tests__/schedulerPaths.test.cjs +0 -226
  416. package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +0 -133
  417. package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +0 -56
  418. package/src/main/lib/__tests__/sessionSlots.test.cjs +0 -144
  419. package/src/main/lib/__tests__/telemetryBacklog.test.cjs +0 -626
  420. package/src/main/lib/__tests__/telemetryBoot.test.cjs +0 -134
  421. package/src/main/lib/__tests__/telemetryConsent.test.cjs +0 -136
  422. package/src/main/lib/__tests__/telemetryCounters.test.cjs +0 -57
  423. package/src/main/lib/__tests__/telemetryCountersMetadataColumn.test.cjs +0 -98
  424. package/src/main/lib/__tests__/terminalRunOutcome.test.cjs +0 -200
  425. package/src/main/lib/__tests__/toolUseClassify.test.cjs +0 -53
  426. package/src/main/lib/__tests__/updateCheck.test.cjs +0 -63
  427. package/src/main/lib/__tests__/upgradeDrain.test.cjs +0 -130
  428. package/src/main/lib/__tests__/usageCircuit.test.cjs +0 -354
  429. package/src/main/lib/__tests__/watchdog-helpers.test.cjs +0 -441
  430. package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +0 -266
  431. 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
- });