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,355 +0,0 @@
1
- /**
2
- * queueHistory.test.cjs — unit tests for src/main/lib/queueHistory.cjs and
3
- * scheduler.cjs's selectHistoryJobs merge.
4
- *
5
- * Run: timeout 300 npx vitest run src/main/__tests__/queueHistory.test.cjs
6
- */
7
-
8
- 'use strict';
9
-
10
- import { test, expect, beforeEach, afterEach } from 'vitest';
11
- const fs = require('node:fs');
12
- const os = require('node:os');
13
- const path = require('node:path');
14
-
15
- const RETENTION_MS = 7 * 24 * 60 * 60_000;
16
- const NOW = Date.parse('2026-07-24T12:00:00.000Z');
17
-
18
- function fresh(overrides = {}) {
19
- return {
20
- slug: '01-fresh',
21
- status: 'completed',
22
- runId: 'run-1',
23
- finishedAt: new Date(NOW - 1 * 60 * 60_000).toISOString(), // 1h ago
24
- ...overrides,
25
- };
26
- }
27
-
28
- function old(overrides = {}) {
29
- return {
30
- slug: '02-old',
31
- status: 'completed',
32
- runId: 'run-2',
33
- finishedAt: new Date(NOW - 10 * 24 * 60 * 60_000).toISOString(), // 10d ago
34
- ...overrides,
35
- };
36
- }
37
-
38
- let queueHistory;
39
- let prevOverride;
40
-
41
- beforeEach(() => {
42
- // Point HISTORY_PATH at an isolated tmpdir file (SM_HISTORY_PATH_OVERRIDE)
43
- // instead of the real machine-global history.jsonl — a live Electron
44
- // instance's scheduler.cjs writes that same global file concurrently,
45
- // which races this suite's unlink/append calls and produces real
46
- // production data in place of the expected empty state.
47
- prevOverride = process.env.SM_HISTORY_PATH_OVERRIDE;
48
- process.env.SM_HISTORY_PATH_OVERRIDE = path.join(
49
- os.tmpdir(),
50
- `queueHistory-test-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}.jsonl`
51
- );
52
-
53
- // Fresh require per test so HISTORY_PATH's target file starts clean.
54
- delete require.cache[require.resolve('../lib/queueHistory.cjs')];
55
- queueHistory = require('../lib/queueHistory.cjs');
56
- try { fs.unlinkSync(queueHistory.historyPath()); } catch { /* ok if absent */ }
57
- });
58
-
59
- afterEach(() => {
60
- try { fs.unlinkSync(queueHistory.historyPath()); } catch { /* ok if absent */ }
61
- if (prevOverride === undefined) delete process.env.SM_HISTORY_PATH_OVERRIDE;
62
- else process.env.SM_HISTORY_PATH_OVERRIDE = prevOverride;
63
- });
64
-
65
- // ---------- partitionJobs ----------
66
-
67
- test('partitionJobs: fresh completed job stays hot', () => {
68
- const { hot, toArchive } = queueHistory.partitionJobs([fresh()], NOW);
69
- expect(hot.map((j) => j.slug)).toEqual(['01-fresh']);
70
- expect(toArchive).toEqual([]);
71
- });
72
-
73
- test('partitionJobs: old completed job archives', () => {
74
- const { hot, toArchive } = queueHistory.partitionJobs([old()], NOW);
75
- expect(hot).toEqual([]);
76
- expect(toArchive.map((j) => j.slug)).toEqual(['02-old']);
77
- });
78
-
79
- test('partitionJobs: old failed job archives', () => {
80
- const j = old({ slug: '03-old-failed', status: 'failed', runId: 'run-3' });
81
- const { hot, toArchive } = queueHistory.partitionJobs([j], NOW);
82
- expect(hot).toEqual([]);
83
- expect(toArchive.map((x) => x.slug)).toEqual(['03-old-failed']);
84
- });
85
-
86
- test('partitionJobs: running and pending jobs never archive, regardless of age', () => {
87
- const running = old({ slug: '04-running', status: 'running', runId: 'run-4', finishedAt: null });
88
- const pending = old({ slug: '05-pending', status: 'pending', runId: null, finishedAt: null });
89
- const { hot, toArchive } = queueHistory.partitionJobs([running, pending], NOW);
90
- expect(toArchive).toEqual([]);
91
- expect(hot.map((j) => j.slug).sort()).toEqual(['04-running', '05-pending']);
92
- });
93
-
94
- test('partitionJobs: old skipped job archives (never-ran rows must age out of the hot queue too)', () => {
95
- const j = old({ slug: '03b-old-skipped', status: 'skipped', runId: 'run-3b' });
96
- const { hot, toArchive } = queueHistory.partitionJobs([j], NOW);
97
- expect(hot).toEqual([]);
98
- expect(toArchive.map((x) => x.slug)).toEqual(['03b-old-skipped']);
99
- });
100
-
101
- test('partitionJobs: needs_review jobs never archive', () => {
102
- const j = old({ slug: '06-review', status: 'needs_review', runId: 'run-6' });
103
- const { hot, toArchive } = queueHistory.partitionJobs([j], NOW);
104
- expect(toArchive).toEqual([]);
105
- expect(hot.map((x) => x.slug)).toEqual(['06-review']);
106
- });
107
-
108
- test('partitionJobs: job with no/invalid finishedAt stays hot even if old status', () => {
109
- const j = { slug: '07-nofinish', status: 'completed', runId: 'run-7', finishedAt: null };
110
- const { hot, toArchive } = queueHistory.partitionJobs([j], NOW);
111
- expect(toArchive).toEqual([]);
112
- expect(hot.map((x) => x.slug)).toEqual(['07-nofinish']);
113
- });
114
-
115
- test('partitionJobs: fix-plan linkage keeps an old original hot while its fix is pending', () => {
116
- const original = old({ slug: '08-thing', status: 'failed', runId: 'run-8' });
117
- const fixPlan = { slug: '08-fix-thing', status: 'pending', runId: null, finishedAt: null };
118
- const { hot, toArchive } = queueHistory.partitionJobs([original, fixPlan], NOW);
119
- expect(toArchive).toEqual([]);
120
- expect(hot.map((x) => x.slug).sort()).toEqual(['08-fix-thing', '08-thing']);
121
- });
122
-
123
- test('partitionJobs: fix-plan linkage keeps an old original hot while its fix is running', () => {
124
- const original = old({ slug: '09-thing', status: 'failed', runId: 'run-9' });
125
- const fixPlan = { slug: '09-fix-thing', status: 'running', runId: 'run-9b', finishedAt: null };
126
- const { hot, toArchive } = queueHistory.partitionJobs([original, fixPlan], NOW);
127
- expect(toArchive).toEqual([]);
128
- expect(hot.map((x) => x.slug).sort()).toEqual(['09-fix-thing', '09-thing']);
129
- });
130
-
131
- test('partitionJobs: an old original with a COMPLETED fix-plan is free to archive (no longer protected)', () => {
132
- const original = old({ slug: '10-thing', status: 'completed', runId: 'run-10' });
133
- const fixPlan = old({ slug: '10-fix-thing', status: 'completed', runId: 'run-10b' });
134
- const { hot, toArchive } = queueHistory.partitionJobs([original, fixPlan], NOW);
135
- expect(hot).toEqual([]);
136
- expect(toArchive.map((x) => x.slug).sort()).toEqual(['10-fix-thing', '10-thing']);
137
- });
138
-
139
- test('partitionJobs: respects opts.retentionMs override', () => {
140
- const j = { slug: '11-recent', status: 'completed', runId: 'run-11', finishedAt: new Date(NOW - 2 * 60 * 60_000).toISOString() };
141
- const { toArchive } = queueHistory.partitionJobs([j], NOW, { retentionMs: 60 * 60_000 }); // 1h retention
142
- expect(toArchive.map((x) => x.slug)).toEqual(['11-recent']);
143
- });
144
-
145
- // ---------- appendHistory / readHistory ----------
146
-
147
- test('appendHistory writes entries, readHistory reads them back newest-first', async () => {
148
- const a = old({ slug: 'a', runId: 'ra', finishedAt: new Date(NOW - 3 * 60 * 60_000).toISOString() });
149
- const b = old({ slug: 'b', runId: 'rb', finishedAt: new Date(NOW - 1 * 60 * 60_000).toISOString() });
150
- await queueHistory.appendHistory([a, b]);
151
- const read = await queueHistory.readHistory({ limit: 10 });
152
- // appended in file order a,b; readHistory reverses file order to newest-appended-first
153
- expect(read.map((j) => j.slug)).toEqual(['b', 'a']);
154
- });
155
-
156
- test('appendHistory dedupes by slug+runId against what is already on disk', async () => {
157
- const a = old({ slug: 'dup', runId: 'r1' });
158
- const r1 = await queueHistory.appendHistory([a]);
159
- expect(r1.appended).toBe(1);
160
- const r2 = await queueHistory.appendHistory([a]); // replay of the same batch (crash-recovery scenario)
161
- expect(r2.appended).toBe(0);
162
- const read = await queueHistory.readHistory({ limit: 10 });
163
- expect(read.length).toBe(1);
164
- });
165
-
166
- test('appendHistory: a needs_review ledger line and a terminal row sharing slug+runId do NOT dedupe against each other', async () => {
167
- // An entry and its resolution line (needsReviewLedger.cjs) deliberately
168
- // reference the SAME runId as each other — and a slug's terminal archive
169
- // row can later reuse that same runId too (same run, different `kind`).
170
- // jobKey must bucket by kind or the second/third write looks like a
171
- // crash-replay of the first and silently never lands.
172
- const entryLine = { kind: 'needs_review_entry', slug: 'ledger-dup', runId: 'shared-run', at: new Date(NOW).toISOString(), reason: 'uncommitted_changes', source: 'spawnJob:finalize' };
173
- const resolutionLine = { kind: 'needs_review_resolution', slug: 'ledger-dup', runId: 'shared-run', at: new Date(NOW).toISOString(), resolvedTo: 'completed', ladderRung: 'reverify', source: 'reverifyNeedsReview:heal', dwellMs: 1000 };
174
- const terminalLine = old({ slug: 'ledger-dup', runId: 'shared-run' });
175
- const r1 = await queueHistory.appendHistory([entryLine]);
176
- const r2 = await queueHistory.appendHistory([resolutionLine]);
177
- const r3 = await queueHistory.appendHistory([terminalLine]);
178
- expect(r1.appended).toBe(1);
179
- expect(r2.appended).toBe(1);
180
- expect(r3.appended).toBe(1);
181
- const read = await queueHistory.readHistory({ limit: 10 });
182
- expect(read.filter((j) => j.slug === 'ledger-dup')).toHaveLength(3);
183
- });
184
-
185
- test('readHistory respects limit and returns [] when file is absent', async () => {
186
- const empty = await queueHistory.readHistory({ limit: 5 });
187
- expect(empty).toEqual([]);
188
-
189
- // Realistic append order: oldest finishedAt first (append-only file grows
190
- // forward in time), so s7 is both last-appended and newest.
191
- const entries = Array.from({ length: 8 }, (_, i) => old({ slug: `s${i}`, runId: `r${i}`, finishedAt: new Date(NOW - (7 - i) * 60_000).toISOString() }));
192
- await queueHistory.appendHistory(entries);
193
- const read = await queueHistory.readHistory({ limit: 3 });
194
- expect(read.length).toBe(3);
195
- // Federated readHistory (2026-07-31) orders by finishedAt newest-first.
196
- expect(read.map((j) => j.slug)).toEqual(['s7', 's6', 's5']);
197
- });
198
-
199
- test('readHistory tail-reads correctly when the file is larger than one chunk', async () => {
200
- // Force many small appends so the file exceeds the initial 64KB read chunk.
201
- const bigEntries = Array.from({ length: 2000 }, (_, i) => ({
202
- slug: `bulk-${i}`,
203
- status: 'completed',
204
- runId: `bulk-run-${i}`,
205
- finishedAt: new Date(NOW - i * 1000).toISOString(),
206
- bodyPreview: 'x'.repeat(80),
207
- }));
208
- await queueHistory.appendHistory(bigEntries);
209
- const read = await queueHistory.readHistory({ limit: 5 });
210
- // The tail read only touches the file's end; whatever was read comes back
211
- // ordered by finishedAt newest-first.
212
- expect(read.length).toBe(5);
213
- const t = (j) => Date.parse(j.finishedAt);
214
- for (let i = 1; i < read.length; i++) expect(t(read[i - 1])).toBeGreaterThanOrEqual(t(read[i]));
215
- });
216
-
217
- // ---------- selectHistoryJobs merge (scheduler.cjs) ----------
218
-
219
- test('selectHistoryJobs merges hot jobs[] and history tail, newest-first, deduped', () => {
220
- const { selectHistoryJobs } = require('../scheduler.cjs');
221
- const hotJob = fresh({ slug: 'hot-1', runId: 'rh1' });
222
- const historyEntry = old({ slug: 'hist-1', runId: 'rhist1' });
223
- const merged = selectHistoryJobs([hotJob], 10, [historyEntry]);
224
- expect(merged.map((j) => j.slug)).toEqual(['hot-1', 'hist-1']); // hot is newer
225
- });
226
-
227
- test('selectHistoryJobs: jobs[] entry wins over a duplicate history entry with the same slug+runId', () => {
228
- const { selectHistoryJobs } = require('../scheduler.cjs');
229
- const hotCopy = fresh({ slug: 'dup', runId: 'rd', bodyPreview: 'from-hot' });
230
- const historyCopy = fresh({ slug: 'dup', runId: 'rd', bodyPreview: 'from-history' });
231
- const merged = selectHistoryJobs([hotCopy], 10, [historyCopy]);
232
- expect(merged.length).toBe(1);
233
- expect(merged[0].bodyPreview).toBe('from-hot');
234
- });
235
-
236
- test('selectHistoryJobs: default historyEntries=[] preserves old call shape', () => {
237
- const { selectHistoryJobs } = require('../scheduler.cjs');
238
- const merged = selectHistoryJobs([fresh()], 10);
239
- expect(merged.map((j) => j.slug)).toEqual(['01-fresh']);
240
- });
241
-
242
- test('selectHistoryJobs: needs_review ledger lines (kind !== "terminal") are excluded from the merged result', () => {
243
- // The History view renders ScheduleJob-shaped rows (status, exitCode, ...)
244
- // — a needs_review_entry/needs_review_resolution line (needsReviewLedger.cjs)
245
- // has neither, and must not slip through as a statusless row in that table.
246
- const { selectHistoryJobs } = require('../scheduler.cjs');
247
- const terminalEntry = old({ slug: 'hist-1', runId: 'rhist1' });
248
- const ledgerEntry = { kind: 'needs_review_entry', slug: 'ledger-1', runId: 'rledger1', at: new Date(NOW).toISOString(), reason: 'uncommitted_changes', source: 'spawnJob:finalize' };
249
- const ledgerResolution = { kind: 'needs_review_resolution', slug: 'ledger-1', runId: 'rledger1', at: new Date(NOW).toISOString(), resolvedTo: 'completed', ladderRung: 'reverify', source: 'reverifyNeedsReview:heal', dwellMs: 500 };
250
- const merged = selectHistoryJobs([], 10, [terminalEntry, ledgerEntry, ledgerResolution]);
251
- expect(merged.map((j) => j.slug)).toEqual(['hist-1']);
252
- });
253
-
254
- // ---------- historyTerminalBySlug ----------
255
- //
256
- // Guards against scheduler.cjs's reconcile() resurrecting an already-
257
- // archived-to-history job as a fresh 'pending' entry (and the scheduler
258
- // then genuinely re-executing an already-completed PRD) just because its
259
- // job row already left jobs[]. reconcile() consults this map before
260
- // treating an unmatched on-disk PRD slug as brand-new.
261
-
262
- test('historyTerminalBySlug: returns status + finishedAt for every archived slug', async () => {
263
- const a = old({ slug: 'term-completed', status: 'completed', runId: 'rc' });
264
- const b = old({ slug: 'term-failed', status: 'failed', runId: 'rf' });
265
- await queueHistory.appendHistory([a, b]);
266
-
267
- const map = await queueHistory.historyTerminalBySlug();
268
- expect(map.get('term-completed')).toEqual({ status: 'completed', finishedAt: a.finishedAt, landedCommit: null });
269
- expect(map.get('term-failed')).toEqual({ status: 'failed', finishedAt: b.finishedAt, landedCommit: null });
270
- expect(map.has('never-archived-slug')).toBe(false);
271
- });
272
-
273
- test('historyTerminalBySlug: returns an empty map when history.jsonl is absent', async () => {
274
- const map = await queueHistory.historyTerminalBySlug();
275
- expect(map.size).toBe(0);
276
- });
277
-
278
- test('historyTerminalBySlug: cache invalidates after a new appendHistory call (mtime changes)', async () => {
279
- const a = old({ slug: 'cache-1', runId: 'rca' });
280
- await queueHistory.appendHistory([a]);
281
- const first = await queueHistory.historyTerminalBySlug();
282
- expect(first.has('cache-1')).toBe(true);
283
- expect(first.has('cache-2')).toBe(false);
284
-
285
- const b = old({ slug: 'cache-2', runId: 'rcb' });
286
- await queueHistory.appendHistory([b]);
287
- const second = await queueHistory.historyTerminalBySlug();
288
- expect(second.has('cache-1')).toBe(true);
289
- expect(second.has('cache-2')).toBe(true);
290
- });
291
-
292
- test('historyTerminalBySlug: ignores needs_review ledger lines, even a LATER one for an already-terminal slug', async () => {
293
- // Chronological order matters here: the terminal row lands first, and a
294
- // needs_review_entry for the SAME slug (a later re-run parking again)
295
- // lands after it. Without the kind guard, last-line-wins would replace
296
- // the true 'completed' record with a status-less ledger row.
297
- const terminal = old({ slug: 'mixed-1', status: 'completed', runId: 'rterm' });
298
- await queueHistory.appendHistory([terminal]);
299
- await queueHistory.appendHistory([{
300
- kind: 'needs_review_entry', slug: 'mixed-1', runId: 'rlater', cwd: null,
301
- at: new Date(NOW).toISOString(), reason: 'uncommitted_changes', source: 'spawnJob:finalize',
302
- }]);
303
-
304
- const map = await queueHistory.historyTerminalBySlug();
305
- expect(map.get('mixed-1')).toEqual({ status: 'completed', finishedAt: terminal.finishedAt, landedCommit: null });
306
- });
307
-
308
- // ---------- completedSlugsForCwd (PRD 1122) ----------
309
- //
310
- // Feeds scheduler.cjs's computeDepHistorySatisfaction: a dependsOn slug with
311
- // no live queue row is only satisfied when it shows up here (or in
312
- // prds-archived/) — scoped to ONE project's own history shard so a
313
- // same-named PRD in an unrelated project can never satisfy a dep.
314
-
315
- test('completedSlugsForCwd: returns slugs completed in this project\'s own shard only', async () => {
316
- const projectCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-1122-projA-'));
317
- const otherCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-1122-projB-'));
318
- await queueHistory.appendHistory([
319
- old({ slug: '900-base', status: 'completed', runId: 'ra', cwd: projectCwd }),
320
- old({ slug: '901-failed', status: 'failed', runId: 'rb', cwd: projectCwd }),
321
- old({ slug: '900-base', status: 'completed', runId: 'rc', cwd: otherCwd }),
322
- ]);
323
-
324
- const slugs = await queueHistory.completedSlugsForCwd(projectCwd);
325
- expect(slugs.has('900-base')).toBe(true);
326
- expect(slugs.has('901-failed')).toBe(false);
327
-
328
- // Same slug completed in a DIFFERENT project must not leak across.
329
- const otherSlugs = await queueHistory.completedSlugsForCwd(otherCwd);
330
- expect(otherSlugs.has('900-base')).toBe(true);
331
- const unrelatedCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-1122-projC-'));
332
- expect((await queueHistory.completedSlugsForCwd(unrelatedCwd)).size).toBe(0);
333
- });
334
-
335
- test('completedSlugsForCwd: returns an empty Set when the project has no history shard yet', async () => {
336
- const freshCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-1122-fresh-'));
337
- const slugs = await queueHistory.completedSlugsForCwd(freshCwd);
338
- expect(slugs.size).toBe(0);
339
- });
340
-
341
- test('completedSlugsForCwd: ignores needs_review ledger lines even when they are the newest line for a completed slug', async () => {
342
- const projectCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-1122-ledger-'));
343
- await queueHistory.appendHistory([
344
- old({ slug: '900-base', status: 'completed', runId: 'ra', cwd: projectCwd }),
345
- ]);
346
- // A later needs_review park for a NEW run of the same slug must not blank
347
- // out the earlier 'completed' record read by completedSlugsForCwd.
348
- await queueHistory.appendHistory([{
349
- kind: 'needs_review_entry', slug: '900-base', runId: 'rb', cwd: projectCwd,
350
- at: new Date(NOW).toISOString(), reason: 'uncommitted_changes', source: 'spawnJob:finalize',
351
- }]);
352
-
353
- const slugs = await queueHistory.completedSlugsForCwd(projectCwd);
354
- expect(slugs.has('900-base')).toBe(true);
355
- });
@@ -1,65 +0,0 @@
1
- /**
2
- * queueOps-interactive-ac-lint.test.cjs — lint for AC checkboxes that
3
- * instruct an interactive/GUI-rendering step (xvfb-run, playwright test,
4
- * electron.launch, screenshot, ...) that a headless claude -p executor can
5
- * never satisfy (see the 776/779 exit143 incidents, 2026-07-30). Must key off
6
- * AC-checkbox context specifically — a PRD's Out-of-scope/notes prose telling
7
- * the executor NOT to do this (PRDs 780/787/788's pattern) must not trigger.
8
- *
9
- * Run: timeout 120 npx vitest run src/main/__tests__/queueOps-interactive-ac-lint.test.cjs
10
- */
11
-
12
- 'use strict';
13
-
14
- import { test, expect } from 'vitest';
15
- const fs = require('node:fs');
16
- const os = require('node:os');
17
- const path = require('node:path');
18
- const { lintOneAsync } = require('../queueOps.cjs');
19
-
20
- function writeTmpPrd(body) {
21
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-lint-interactive-'));
22
- const p = path.join(dir, 'test-prd.md');
23
- const raw = `---\ntitle: "test"\ncwd: ~\nestimateMinutes: 10\n---\n\n${body}\n`;
24
- fs.writeFileSync(p, raw, 'utf8');
25
- return p;
26
- }
27
-
28
- test('flags a PRD with xvfb-run as an AC checkbox line', async () => {
29
- const p = writeTmpPrd(
30
- '# Acceptance criteria\n\n- [ ] `xvfb-run -a npx playwright test tests/e2e/foo.spec.ts` passes\n',
31
- );
32
- const { findings } = await lintOneAsync(p);
33
- expect(findings.some((f) => f.rule === 'interactive-xvfb')).toBe(true);
34
- });
35
-
36
- test('flags a PRD with playwright electron.launch as an AC checkbox line', async () => {
37
- const p = writeTmpPrd(
38
- '# Acceptance criteria\n\n- [ ] Playwright `electron.launch` boots the app and captures a screenshot of the Scheduler tab\n',
39
- );
40
- const { findings } = await lintOneAsync(p);
41
- expect(findings.some((f) => f.rule === 'interactive-electron-launch')).toBe(true);
42
- });
43
-
44
- test('does NOT flag Out-of-scope/notes prose that mentions xvfb without being a checkbox line', async () => {
45
- const p = writeTmpPrd(
46
- '# Acceptance criteria\n\n- [ ] `timeout 300 npm run typecheck` passes\n' +
47
- '- [ ] `timeout 300 npx vitest run src/foo.test.ts` passes\n\n' +
48
- 'NOTE: do NOT add a `playwright test ... under xvfb` acceptance criterion here. Two prior\n' +
49
- 'links in this chain stalled and were SIGTERM\'d (exit 143) on exactly that step — a headless\n' +
50
- '`claude -p` executor spawning `xvfb-run`/Playwright hits a tool-use rejection and hangs.\n\n' +
51
- '# Out of scope\n\n' +
52
- '- Making xvfb-run/Playwright actually work inside a headless claude -p job — out of scope\n',
53
- );
54
- const { findings } = await lintOneAsync(p);
55
- expect(findings.filter((f) => f.rule.startsWith('interactive-')).length).toBe(0);
56
- });
57
-
58
- test('a clean PRD with only typecheck/vitest AC is not flagged', async () => {
59
- const p = writeTmpPrd(
60
- '# Acceptance criteria\n\n- [ ] `timeout 300 npm run typecheck` passes\n' +
61
- '- [ ] `timeout 300 npx vitest run src/foo.test.ts` passes\n',
62
- );
63
- const { findings } = await lintOneAsync(p);
64
- expect(findings.filter((f) => f.rule.startsWith('interactive-')).length).toBe(0);
65
- });
@@ -1,65 +0,0 @@
1
- /**
2
- * queueOpsArchiveDestination.test.cjs — an archived PRD lands beside its OWN
3
- * source, not in the machine-level scheduled-plans tree.
4
- *
5
- * PRD sources are per-project and per-Epic (CLAUDE.md's TAB → EPIC → PRD
6
- * model): `<cwd>/session-manager-operations/scheduler/epics/<epic-id>/prds/`.
7
- * `~/.claude/session-manager/scheduled-plans/` holds only run logs and
8
- * PRD_AUTHORING.md. archiveOne resolved its SOURCE per-project (findPrdDir)
9
- * but hardcoded its DESTINATION to the global PRDS_ARCHIVE_DIR, so a manual
10
- * archive yanked a PRD out of its Epic and stranded it in the retired global
11
- * tree — splitting one Epic's history across two locations.
12
- *
13
- * Observed 2026-08-13: archiving 1035-epic-worktree-ui-surfacing put it under
14
- * scheduled-plans/prds-archived/ while its three chain siblings (1032/1033/
15
- * 1034, archived by scheduler.cjs's own on-completion mover) sat correctly in
16
- * the Epic's prds-archived/. Two movers, two destinations, same operation.
17
- *
18
- * Run: timeout 120 npx vitest run src/main/__tests__/queueOpsArchiveDestination.test.cjs
19
- */
20
-
21
- 'use strict';
22
-
23
- import { test, expect } from 'vitest';
24
- const path = require('node:path');
25
- const { archiveDirForSource, PRDS_DIR, PRDS_ARCHIVE_DIR } = require('../queueOps.cjs');
26
-
27
- const EPIC_PRDS = '/home/u/Projects/proj/session-manager-operations/scheduler/epics/my-epic-abc123/prds';
28
- const TS = '2026-08-14T05-03-41-156Z';
29
-
30
- test('an Epic PRD archives into that Epic\'s own prds-archived/, beside its prds/', () => {
31
- const dir = archiveDirForSource(EPIC_PRDS, TS);
32
- expect(dir).toBe(path.join(path.dirname(EPIC_PRDS), 'prds-archived', TS));
33
- // Concretely: same Epic dir, sibling of prds/.
34
- expect(dir).toBe(
35
- `/home/u/Projects/proj/session-manager-operations/scheduler/epics/my-epic-abc123/prds-archived/${TS}`,
36
- );
37
- });
38
-
39
- test('an Epic PRD never lands under the machine-level scheduled-plans tree', () => {
40
- const dir = archiveDirForSource(EPIC_PRDS, TS);
41
- expect(dir.startsWith(PRDS_ARCHIVE_DIR)).toBe(false);
42
- expect(dir).not.toContain('.claude/session-manager/scheduled-plans');
43
- });
44
-
45
- test('the archived PRD stays inside its own project — the ops root never changes', () => {
46
- const dir = archiveDirForSource(EPIC_PRDS, TS);
47
- expect(dir.startsWith('/home/u/Projects/proj/session-manager-operations/')).toBe(true);
48
- });
49
-
50
- test('two Epics in the same project archive to their own separate dirs', () => {
51
- const other = '/home/u/Projects/proj/session-manager-operations/scheduler/epics/other-epic-def456/prds';
52
- expect(archiveDirForSource(EPIC_PRDS, TS)).not.toBe(archiveDirForSource(other, TS));
53
- });
54
-
55
- test('a legacy flat PRD still archives to the global prds-archived/ — old behavior unchanged', () => {
56
- const dir = archiveDirForSource(PRDS_DIR, TS);
57
- expect(dir).toBe(path.join(PRDS_ARCHIVE_DIR, TS));
58
- });
59
-
60
- test('the batch timestamp is shared, so one archive call reads as one event', () => {
61
- const a = archiveDirForSource(EPIC_PRDS, TS);
62
- const b = archiveDirForSource('/home/u/Projects/other/session-manager-operations/scheduler/epics/e/prds', TS);
63
- expect(path.basename(a)).toBe(TS);
64
- expect(path.basename(b)).toBe(TS);
65
- });
@@ -1,154 +0,0 @@
1
- /**
2
- * queueOpsAutoArchive.test.cjs — unit tests for queueOps.cjs's
3
- * selectAutoArchivable predicate + autoArchiveCompleted orchestration
4
- * (auto-archiving completed PRDs' .md files out of the live prds/ dir).
5
- *
6
- * selectAutoArchivable is pure (no fs) so it's fully covered here without
7
- * touching disk. autoArchiveCompleted's disk-mutating path (archiveMany) is
8
- * exercised only via its kill-switch / no-op branches, since archiveMany
9
- * targets the real ~/.claude/session-manager/scheduled-plans/prds-archived
10
- * directory (not test-injectable) — mutating that from a test run would be
11
- * an unwanted side effect on the developer's real filesystem.
12
- *
13
- * Run: timeout 300 npx vitest run src/main/__tests__/queueOpsAutoArchive.test.cjs
14
- */
15
-
16
- 'use strict';
17
-
18
- import { test, expect, afterEach } from 'vitest';
19
-
20
- const RETENTION_MS = 7 * 24 * 60 * 60_000;
21
- const NOW = Date.parse('2026-07-24T12:00:00.000Z');
22
-
23
- const { selectAutoArchivable, autoArchiveCompleted } = require('../queueOps.cjs');
24
-
25
- function fresh(overrides = {}) {
26
- return {
27
- slug: '01-fresh',
28
- status: 'completed',
29
- finishedAt: new Date(NOW - 1 * 60 * 60_000).toISOString(), // 1h ago
30
- ...overrides,
31
- };
32
- }
33
-
34
- function old(overrides = {}) {
35
- return {
36
- slug: '02-old',
37
- status: 'completed',
38
- finishedAt: new Date(NOW - 10 * 24 * 60 * 60_000).toISOString(), // 10d ago
39
- ...overrides,
40
- };
41
- }
42
-
43
- afterEach(() => {
44
- delete process.env.SM_PRD_AUTOARCHIVE_DISABLE;
45
- });
46
-
47
- // ---------- selectAutoArchivable ----------
48
-
49
- test('selectAutoArchivable: fresh completed job stays (not old enough)', () => {
50
- const slugs = selectAutoArchivable([fresh()], { nowMs: NOW });
51
- expect(slugs).toEqual([]);
52
- });
53
-
54
- test('selectAutoArchivable: old completed job archives', () => {
55
- const slugs = selectAutoArchivable([old()], { nowMs: NOW });
56
- expect(slugs).toEqual(['02-old']);
57
- });
58
-
59
- test('selectAutoArchivable: needs_review jobs never archive, regardless of age', () => {
60
- const j = old({ slug: '03-review', status: 'needs_review' });
61
- const slugs = selectAutoArchivable([j], { nowMs: NOW });
62
- expect(slugs).toEqual([]);
63
- });
64
-
65
- test('selectAutoArchivable: failed jobs never archive, regardless of age', () => {
66
- const j = old({ slug: '04-failed', status: 'failed' });
67
- const slugs = selectAutoArchivable([j], { nowMs: NOW });
68
- expect(slugs).toEqual([]);
69
- });
70
-
71
- test('selectAutoArchivable: pending jobs never archive', () => {
72
- const j = old({ slug: '05-pending', status: 'pending', finishedAt: null });
73
- const slugs = selectAutoArchivable([j], { nowMs: NOW });
74
- expect(slugs).toEqual([]);
75
- });
76
-
77
- test('selectAutoArchivable: running jobs never archive', () => {
78
- const j = old({ slug: '06-running', status: 'running', finishedAt: null });
79
- const slugs = selectAutoArchivable([j], { nowMs: NOW });
80
- expect(slugs).toEqual([]);
81
- });
82
-
83
- test('selectAutoArchivable: a completed job with a PENDING NN-fix-<slug> sibling is protected', () => {
84
- const original = old({ slug: '07-thing', status: 'completed' });
85
- const fixPlan = { slug: '07-fix-thing', status: 'pending', finishedAt: null };
86
- const slugs = selectAutoArchivable([original, fixPlan], { nowMs: NOW });
87
- expect(slugs).toEqual([]);
88
- });
89
-
90
- test('selectAutoArchivable: a completed job with a RUNNING NN-fix-<slug> sibling is protected', () => {
91
- const original = old({ slug: '08-thing', status: 'completed' });
92
- const fixPlan = { slug: '08-fix-thing', status: 'running', finishedAt: null };
93
- const slugs = selectAutoArchivable([original, fixPlan], { nowMs: NOW });
94
- expect(slugs).toEqual([]);
95
- });
96
-
97
- test('selectAutoArchivable: a completed job with a COMPLETED fix-plan sibling is free to archive (no longer protected)', () => {
98
- const original = old({ slug: '09-thing', status: 'completed' });
99
- const fixPlan = old({ slug: '09-fix-thing', status: 'completed' });
100
- const slugs = selectAutoArchivable([original, fixPlan], { nowMs: NOW }).sort();
101
- expect(slugs).toEqual(['09-fix-thing', '09-thing']);
102
- });
103
-
104
- test('selectAutoArchivable: job with no/invalid finishedAt never archives even if status completed', () => {
105
- const j = { slug: '10-nofinish', status: 'completed', finishedAt: null };
106
- const slugs = selectAutoArchivable([j], { nowMs: NOW });
107
- expect(slugs).toEqual([]);
108
- });
109
-
110
- test('selectAutoArchivable: respects retentionMs override', () => {
111
- const j = { slug: '11-recent', status: 'completed', finishedAt: new Date(NOW - 2 * 60 * 60_000).toISOString() };
112
- const slugs = selectAutoArchivable([j], { nowMs: NOW, retentionMs: 60 * 60_000 }); // 1h retention
113
- expect(slugs).toEqual(['11-recent']);
114
- });
115
-
116
- test('selectAutoArchivable: defaults retentionMs to the shared HISTORY_RETENTION_MS constant', () => {
117
- const j = { slug: '12-boundary', status: 'completed', finishedAt: new Date(NOW - RETENTION_MS - 1).toISOString() };
118
- const slugs = selectAutoArchivable([j], { nowMs: NOW });
119
- expect(slugs).toEqual(['12-boundary']);
120
- });
121
-
122
- // ---------- autoArchiveCompleted ----------
123
-
124
- test('autoArchiveCompleted: kill switch SM_PRD_AUTOARCHIVE_DISABLE=1 skips entirely', async () => {
125
- process.env.SM_PRD_AUTOARCHIVE_DISABLE = '1';
126
- const result = await autoArchiveCompleted({ jobs: [old()] }, { nowMs: NOW });
127
- expect(result.archived).toBe(0);
128
- expect(result.skipped).toBe('disabled');
129
- });
130
-
131
- test('autoArchiveCompleted: no eligible slugs returns a no-op without touching disk', async () => {
132
- const result = await autoArchiveCompleted({ jobs: [fresh()] }, { nowMs: NOW });
133
- expect(result.ok).toBe(true);
134
- expect(result.archived).toBe(0);
135
- expect(result.archivedTo).toBe(null);
136
- expect(result.results).toEqual([]);
137
- });
138
-
139
- test('autoArchiveCompleted: tolerates a missing/malformed state.jobs', async () => {
140
- const result = await autoArchiveCompleted({}, { nowMs: NOW });
141
- expect(result.archived).toBe(0);
142
- });
143
-
144
- // ---------- archiving never strands a runnable queue job (scheduler PRD:
145
- // archived-prd-skip — job 822-epics-nav-rename fired ENOENT after its PRD
146
- // was archived out from under a still-queued job) ----------
147
-
148
- test('selectAutoArchivable never selects a slug whose job is still pending or running, so auto-archive can never leave a runnable job pointing at an archived PRD', () => {
149
- const pending = old({ slug: '13-pending', status: 'pending', finishedAt: null });
150
- const running = old({ slug: '14-running', status: 'running', finishedAt: null });
151
- const completed = old({ slug: '15-done', status: 'completed' });
152
- const slugs = selectAutoArchivable([pending, running, completed], { nowMs: NOW });
153
- expect(slugs).toEqual(['15-done']);
154
- });