claude-code-session-manager 0.98.0 → 0.101.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 (441) 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-CXaH2GED.js +1 -0
  5. package/dist/assets/{History-Bfdoywlh.js → History-Cerii-HA.js} +2 -2
  6. package/dist/assets/{Hooks-CKDJjTJV.js → Hooks-aXMb5LDy.js} +3 -3
  7. package/dist/assets/{Library-7akfgy67.js → Library-WrJFKEOJ.js} +1 -1
  8. package/dist/assets/{MarkdownEditor-DDesiqet.js → MarkdownEditor-D1BMCXS7.js} +1 -1
  9. package/dist/assets/{McpServers-D4lZtXof.js → McpServers-Cf0davLd.js} +2 -2
  10. package/dist/assets/{Memory-CeI8G1w5.js → Memory-Dn6NHWPe.js} +6 -6
  11. package/dist/assets/Permissions-CTVuFPjg.js +3 -0
  12. package/dist/assets/{Plugins-P4eCXy_S.js → Plugins-C2TyF8Mn.js} +2 -2
  13. package/dist/assets/ProvenanceBadge-F_jwHSwE.js +1 -0
  14. package/dist/assets/{SaveBar-l9jnEMES.js → SaveBar-DA1KVJST.js} +1 -1
  15. package/dist/assets/Scheduler-Bm6Q_hlu.js +16 -0
  16. package/dist/assets/{ScopeSwitcher-CjJi8ST5.js → ScopeSwitcher-BygZlJca.js} +1 -1
  17. package/dist/assets/Settings-BYCiv5KV.js +3 -0
  18. package/dist/assets/{SkillReferenceGraph-CjbO01Sg.js → SkillReferenceGraph-CcLnl9np.js} +1 -1
  19. package/dist/assets/{Skills-BxZ9_fCR.js → Skills-ChffsdTD.js} +2 -2
  20. package/dist/assets/{SystemPrompt-D8vfFb8b.js → SystemPrompt-dNkfHE_M.js} +1 -1
  21. package/dist/assets/{TagLibrary-2vzUo7YL.js → TagLibrary-jVlEuB0m.js} +1 -1
  22. package/dist/assets/{TiptapBody-B8ZfbK90.js → TiptapBody-6_cdwcP0.js} +1 -1
  23. package/dist/assets/{Toggle-BbW4nGGf.js → Toggle-CVXqGpAP.js} +1 -1
  24. package/dist/assets/ViewTabs-DO1YVbpc.js +1 -0
  25. package/dist/assets/{index-CKH5Uxik.css → index-D89EUizB.css} +1 -1
  26. package/dist/assets/{index-Cd98Q1lD.js → index-DbCQPWWY.js} +747 -752
  27. package/dist/assets/{settingsSchema-IhUfucCF.js → settingsSchema-Wrvn-Yz1.js} +1 -1
  28. package/dist/index.html +2 -2
  29. package/package.json +25 -29
  30. package/plugins/CLAUDE.md +6 -6
  31. package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +21 -19
  32. package/plugins/session-manager-dev/skills/builder/SKILL.md +3 -3
  33. package/plugins/session-manager-dev/skills/develop/SKILL.md +254 -510
  34. package/plugins/session-manager-dev/skills/develop/standards.md +17 -22
  35. package/scripts/README.md +3 -11
  36. package/scripts/audit-ops-hygiene.cjs +3 -3
  37. package/scripts/hooks/lib/guard-prd-writes-policy.cjs +1 -1
  38. package/scripts/hooks/lib/guard-self-schedule-policy.cjs +1 -1
  39. package/scripts/mint-epic.cjs +2 -3
  40. package/scripts/scheduler-mcp-server.cjs +64 -7
  41. package/src/main/agentLibrary.cjs +22 -2
  42. package/src/main/build-info.json +4 -4
  43. package/src/main/chatRunner.cjs +4 -3
  44. package/src/main/config.cjs +41 -38
  45. package/src/main/docEdit.cjs +4 -1
  46. package/src/main/files.cjs +2 -5
  47. package/src/main/health.cjs +1 -1
  48. package/src/main/index.cjs +40 -11
  49. package/src/main/ipcSchemas.cjs +35 -34
  50. package/src/main/lib/agentPersonaSchema.cjs +13 -2
  51. package/src/main/lib/atomicFs.cjs +116 -0
  52. package/src/main/lib/branchSweep.cjs +13 -12
  53. package/src/main/lib/buildTarget.cjs +3 -3
  54. package/src/main/lib/builtinMacros.cjs +39 -0
  55. package/src/main/lib/claudeCliCaps.cjs +129 -0
  56. package/src/main/lib/credentials.cjs +2 -4
  57. package/src/main/lib/crossProjectFeedback.cjs +3 -3
  58. package/src/main/lib/cwdClassify.cjs +15 -3
  59. package/src/main/lib/definitionOfDone.cjs +207 -125
  60. package/src/main/lib/dodDrainHook.cjs +11 -5
  61. package/src/main/lib/epicMint.cjs +2 -4
  62. package/src/main/lib/epicStatusMirror.cjs +8 -10
  63. package/src/main/lib/epicWorktreeProjectConfig.cjs +2 -4
  64. package/src/main/lib/gateAuthority.cjs +96 -0
  65. package/src/main/lib/gitExec.cjs +69 -0
  66. package/src/main/lib/gitWorktree.cjs +656 -92
  67. package/src/main/lib/instanceLock.cjs +5 -8
  68. package/src/main/lib/launchFailure.cjs +2 -3
  69. package/src/main/lib/macroLibrary.cjs +480 -0
  70. package/src/main/lib/mcpToolCatalog.cjs +49 -17
  71. package/src/main/lib/needsReviewLedger.cjs +1 -1
  72. package/src/main/lib/opsErrorLog.cjs +11 -1
  73. package/src/main/lib/opsOwnership.cjs +5 -9
  74. package/src/main/lib/pidAlive.cjs +32 -0
  75. package/src/main/lib/prdCreate.cjs +121 -12
  76. package/src/main/lib/prdDisposition.cjs +2 -2
  77. package/src/main/lib/prdGateFiles.cjs +284 -0
  78. package/src/main/lib/prdLocations.cjs +61 -0
  79. package/src/main/lib/prdMigration.cjs +2 -3
  80. package/src/main/lib/prdSizing.cjs +2 -1
  81. package/src/main/lib/projectHomeAdminRoutes.cjs +136 -21
  82. package/src/main/lib/promptSessionSchema.cjs +10 -2
  83. package/src/main/lib/rcaReport.cjs +4 -6
  84. package/src/main/lib/reaperHelpers.cjs +8 -1
  85. package/src/main/lib/reservationExpiry.cjs +6 -1
  86. package/src/main/lib/reviewNotice.cjs +263 -0
  87. package/src/main/lib/runClaudeP.cjs +4 -1
  88. package/src/main/lib/schedulerPaths.cjs +22 -2
  89. package/src/main/lib/sessionSlots.cjs +2 -4
  90. package/src/main/lib/shippedPersonaSeeds.cjs +226 -0
  91. package/src/main/lib/smfileHeaders.cjs +66 -0
  92. package/src/main/lib/timeoutShim.cjs +123 -0
  93. package/src/main/lib/timeoutShimScript.cjs +275 -0
  94. package/src/main/lib/upgradeDrain.cjs +4 -10
  95. package/src/main/lib/watchdogHelpers.cjs +12 -25
  96. package/src/main/lib/workTypeLibrary.cjs +7 -1
  97. package/src/main/projectPages.cjs +43 -13
  98. package/src/main/promptSessionEvents.cjs +37 -13
  99. package/src/main/scheduler.cjs +910 -198
  100. package/src/main/seedAgentPersonas.cjs +279 -24
  101. package/src/main/supervisor.cjs +4 -1
  102. package/src/main/templates/PRD_AUTHORING.md +126 -406
  103. package/src/preload/__tests__/preload-surface.test.cjs +68 -0
  104. package/src/preload/api.d.ts +53 -94
  105. package/src/preload/index.cjs +14 -9
  106. package/src/seed/agents/architect.md +2 -0
  107. package/src/seed/agents/demo-video-builder.md +84 -0
  108. package/src/seed/agents/dev-lead.md +18 -18
  109. package/src/seed/agents/project-home-builder.md +66 -35
  110. package/src/seed/agents/validator.md +10 -4
  111. package/dist/assets/DataModel-DCEgMTjH.js +0 -1
  112. package/dist/assets/HostBilko-CDVHE0hT.js +0 -1
  113. package/dist/assets/Permissions-BGleLja4.js +0 -3
  114. package/dist/assets/ProvenanceBadge-BFczzI1B.js +0 -1
  115. package/dist/assets/Scheduler-C_phpeUS.js +0 -16
  116. package/dist/assets/Settings-BIwZ3FGQ.js +0 -3
  117. package/src/main/__tests__/activeIndexMerge.test.cjs +0 -235
  118. package/src/main/__tests__/agentEffortResolve.test.cjs +0 -117
  119. package/src/main/__tests__/agentLibrary.test.cjs +0 -276
  120. package/src/main/__tests__/agentModelResolve.test.cjs +0 -311
  121. package/src/main/__tests__/agentOverlayWrite.test.cjs +0 -95
  122. package/src/main/__tests__/bilkoHost-deriveSlug.test.cjs +0 -26
  123. package/src/main/__tests__/bilkoHost-integration.test.cjs +0 -118
  124. package/src/main/__tests__/bilkoHostCore.test.cjs +0 -72
  125. package/src/main/__tests__/broadcastCoalescer.test.cjs +0 -122
  126. package/src/main/__tests__/chat-cancel-terminal.test.cjs +0 -120
  127. package/src/main/__tests__/chat-dead-channels.test.cjs +0 -63
  128. package/src/main/__tests__/chat-exit-close-race.test.cjs +0 -146
  129. package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +0 -139
  130. package/src/main/__tests__/chat-preamble-anchors.test.cjs +0 -100
  131. package/src/main/__tests__/chat-queue.test.cjs +0 -97
  132. package/src/main/__tests__/chat-stop-signal.test.cjs +0 -89
  133. package/src/main/__tests__/chatRunner-epic-worktree-execcwd.test.cjs +0 -125
  134. package/src/main/__tests__/chatRunner-session-flag-retry.test.cjs +0 -252
  135. package/src/main/__tests__/classifyPromptTicket.test.cjs +0 -101
  136. package/src/main/__tests__/classifyTranscriptLine.test.cjs +0 -201
  137. package/src/main/__tests__/computeDepHistorySatisfaction.test.cjs +0 -66
  138. package/src/main/__tests__/config-readText-bounded.test.cjs +0 -84
  139. package/src/main/__tests__/configWriteBoundaryOwners.test.cjs +0 -59
  140. package/src/main/__tests__/crossProjectFeedback.test.cjs +0 -334
  141. package/src/main/__tests__/crossProjectFeedbackRoutes.test.cjs +0 -161
  142. package/src/main/__tests__/dep-orphan-archive-health.test.cjs +0 -77
  143. package/src/main/__tests__/develop-skill-failure-modes.test.cjs +0 -70
  144. package/src/main/__tests__/docEdit.test.cjs +0 -244
  145. package/src/main/__tests__/dod-batchkey.test.cjs +0 -183
  146. package/src/main/__tests__/dod-drain-hook.test.cjs +0 -302
  147. package/src/main/__tests__/dod-report.test.cjs +0 -304
  148. package/src/main/__tests__/dod-reverify.test.cjs +0 -285
  149. package/src/main/__tests__/epicContextDigest.test.cjs +0 -174
  150. package/src/main/__tests__/epicMint.test.cjs +0 -332
  151. package/src/main/__tests__/epicMintTelemetryTap.test.cjs +0 -64
  152. package/src/main/__tests__/epicStatusMirror.test.cjs +0 -110
  153. package/src/main/__tests__/epicValidationHook.test.cjs +0 -291
  154. package/src/main/__tests__/exchanges.test.cjs +0 -122
  155. package/src/main/__tests__/exchangesPromptId.test.cjs +0 -61
  156. package/src/main/__tests__/extractJson.test.cjs +0 -51
  157. package/src/main/__tests__/files-reject-credentials.test.cjs +0 -40
  158. package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +0 -556
  159. package/src/main/__tests__/flatPrdTickSweep.test.cjs +0 -110
  160. package/src/main/__tests__/health-build-freshness.test.cjs +0 -39
  161. package/src/main/__tests__/health-claude-md-budget.test.cjs +0 -57
  162. package/src/main/__tests__/health-credentials.test.cjs +0 -81
  163. package/src/main/__tests__/health-delegation-chain.test.cjs +0 -124
  164. package/src/main/__tests__/health-per-project-stall.test.cjs +0 -84
  165. package/src/main/__tests__/health-prd-migration.test.cjs +0 -37
  166. package/src/main/__tests__/health-queue-dispatch.test.cjs +0 -135
  167. package/src/main/__tests__/health-starve-escalation.test.cjs +0 -94
  168. package/src/main/__tests__/health-tick-liveness.test.cjs +0 -179
  169. package/src/main/__tests__/health-usage-poller.test.cjs +0 -144
  170. package/src/main/__tests__/health-worktree-cap-blocked.test.cjs +0 -65
  171. package/src/main/__tests__/heapSnapshot.test.cjs +0 -121
  172. package/src/main/__tests__/historyAggregatorIntraday.test.cjs +0 -313
  173. package/src/main/__tests__/historyDashboard.test.cjs +0 -163
  174. package/src/main/__tests__/historyRollup.test.cjs +0 -333
  175. package/src/main/__tests__/intradayRefresh.test.cjs +0 -39
  176. package/src/main/__tests__/ipcSchemas-dependsOn.test.cjs +0 -39
  177. package/src/main/__tests__/kg-augment.test.cjs +0 -195
  178. package/src/main/__tests__/loadGateDetailTick.test.cjs +0 -31
  179. package/src/main/__tests__/machineProfile.test.cjs +0 -152
  180. package/src/main/__tests__/mcpStatus.test.cjs +0 -61
  181. package/src/main/__tests__/memoryAggregate.test.cjs +0 -109
  182. package/src/main/__tests__/memoryStale.test.cjs +0 -88
  183. package/src/main/__tests__/needsReviewLedger.test.cjs +0 -162
  184. package/src/main/__tests__/openExternalApp-spawn-error.test.cjs +0 -25
  185. package/src/main/__tests__/opsErrorLog.test.cjs +0 -109
  186. package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +0 -173
  187. package/src/main/__tests__/personaMerge.test.cjs +0 -169
  188. package/src/main/__tests__/planValidator.test.cjs +0 -125
  189. package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +0 -176
  190. package/src/main/__tests__/prd-group-allocator.test.cjs +0 -119
  191. package/src/main/__tests__/prdAdminRouteParity.test.cjs +0 -70
  192. package/src/main/__tests__/prdAdminRoutes.test.cjs +0 -718
  193. package/src/main/__tests__/prdAgentType.test.cjs +0 -103
  194. package/src/main/__tests__/prdAuthoringSeed.test.cjs +0 -39
  195. package/src/main/__tests__/prdCreate.test.cjs +0 -979
  196. package/src/main/__tests__/prdCreateAdoption.test.cjs +0 -205
  197. package/src/main/__tests__/prdCreateDisposition.test.cjs +0 -201
  198. package/src/main/__tests__/prdCreatePlanId.test.cjs +0 -132
  199. package/src/main/__tests__/prdFrontmatterAgentType.test.cjs +0 -117
  200. package/src/main/__tests__/prdFrontmatterDependsOn.test.cjs +0 -136
  201. package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +0 -125
  202. package/src/main/__tests__/prdFrontmatterQuietMachine.test.cjs +0 -108
  203. package/src/main/__tests__/prdLocations.test.cjs +0 -195
  204. package/src/main/__tests__/prdLocationsArchived.test.cjs +0 -201
  205. package/src/main/__tests__/prdMigration.test.cjs +0 -349
  206. package/src/main/__tests__/prdMigrationLegacyAdopt.test.cjs +0 -91
  207. package/src/main/__tests__/prdParserHighWater.test.cjs +0 -74
  208. package/src/main/__tests__/prdParserSourcePromptId.test.cjs +0 -65
  209. package/src/main/__tests__/prdSetDisposition.test.cjs +0 -222
  210. package/src/main/__tests__/prdSizing.test.cjs +0 -106
  211. package/src/main/__tests__/prdSourcePromptIdBackfill.test.cjs +0 -118
  212. package/src/main/__tests__/prdUpdateDependsOn.test.cjs +0 -160
  213. package/src/main/__tests__/proc-role-env.test.cjs +0 -125
  214. package/src/main/__tests__/procname-claude-spawn-sites.test.cjs +0 -304
  215. package/src/main/__tests__/procname-sm-processes.test.cjs +0 -127
  216. package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +0 -177
  217. package/src/main/__tests__/projectPages.test.cjs +0 -137
  218. package/src/main/__tests__/promptSessionEvents.test.cjs +0 -234
  219. package/src/main/__tests__/promptSessionSchema.test.cjs +0 -101
  220. package/src/main/__tests__/promptSessionTranscript.test.cjs +0 -0
  221. package/src/main/__tests__/promptSessionsCreateEpicHandler.test.cjs +0 -159
  222. package/src/main/__tests__/pty-epic-worktree-spawn-cwd.test.cjs +0 -282
  223. package/src/main/__tests__/pty-session-open-telemetry.test.cjs +0 -96
  224. package/src/main/__tests__/pty-write-result.test.cjs +0 -46
  225. package/src/main/__tests__/queue-health-verdict.test.cjs +0 -170
  226. package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +0 -286
  227. package/src/main/__tests__/queue-starvation-per-project.test.cjs +0 -147
  228. package/src/main/__tests__/queueHistory.test.cjs +0 -355
  229. package/src/main/__tests__/queueOps-interactive-ac-lint.test.cjs +0 -65
  230. package/src/main/__tests__/queueOpsArchiveDestination.test.cjs +0 -65
  231. package/src/main/__tests__/queueOpsAutoArchive.test.cjs +0 -211
  232. package/src/main/__tests__/rateLimitPollerStreak.test.cjs +0 -128
  233. package/src/main/__tests__/rcaReport.test.cjs +0 -266
  234. package/src/main/__tests__/reconcileFlatPrdSweep.test.cjs +0 -119
  235. package/src/main/__tests__/reconcileTiming.test.cjs +0 -135
  236. package/src/main/__tests__/runLogRetention.test.cjs +0 -489
  237. package/src/main/__tests__/runVerify-atomic-verdicts.test.cjs +0 -26
  238. package/src/main/__tests__/runVerify-blocked-by-foreign-wip.test.cjs +0 -58
  239. package/src/main/__tests__/runVerify-landed-commit-outranks.test.cjs +0 -191
  240. package/src/main/__tests__/runVerify-policy-denial.test.cjs +0 -89
  241. package/src/main/__tests__/runVerify-transcript-commit-evidence.test.cjs +0 -225
  242. package/src/main/__tests__/runVerify.test.cjs +0 -1784
  243. package/src/main/__tests__/scheduleJobSchema.test.cjs +0 -127
  244. package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +0 -65
  245. package/src/main/__tests__/scheduleJobTransitions.test.cjs +0 -277
  246. package/src/main/__tests__/scheduleJobTransitionsGrep.test.cjs +0 -59
  247. package/src/main/__tests__/scheduleJobTransitionsTelemetryTap.test.cjs +0 -72
  248. package/src/main/__tests__/scheduler-admin-routes.test.cjs +0 -199
  249. package/src/main/__tests__/scheduler-adopted-run-supervision.test.cjs +0 -143
  250. package/src/main/__tests__/scheduler-already-satisfied-on-main.test.cjs +0 -105
  251. package/src/main/__tests__/scheduler-archive-completed-prd.test.cjs +0 -102
  252. package/src/main/__tests__/scheduler-archived-twin-guard.test.cjs +0 -155
  253. package/src/main/__tests__/scheduler-autofix-outcome.test.cjs +0 -188
  254. package/src/main/__tests__/scheduler-autofix-select.test.cjs +0 -439
  255. package/src/main/__tests__/scheduler-autopromote.test.cjs +0 -51
  256. package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +0 -101
  257. package/src/main/__tests__/scheduler-blocked-by-foreign-wip.test.cjs +0 -107
  258. package/src/main/__tests__/scheduler-boot-orphans.test.cjs +0 -153
  259. package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +0 -121
  260. package/src/main/__tests__/scheduler-clear-queue-history.test.cjs +0 -134
  261. package/src/main/__tests__/scheduler-commit-guard-noop.test.cjs +0 -244
  262. package/src/main/__tests__/scheduler-committed-in-window.test.cjs +0 -182
  263. package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +0 -43
  264. package/src/main/__tests__/scheduler-default-eligible-heal.test.cjs +0 -168
  265. package/src/main/__tests__/scheduler-dispatch-loop.test.cjs +0 -58
  266. package/src/main/__tests__/scheduler-effective-concurrency.test.cjs +0 -81
  267. package/src/main/__tests__/scheduler-epic-digest.test.cjs +0 -227
  268. package/src/main/__tests__/scheduler-failed-autoreset.test.cjs +0 -121
  269. package/src/main/__tests__/scheduler-finalize-dispatch-guards.test.cjs +0 -229
  270. package/src/main/__tests__/scheduler-find-prd-dir.test.cjs +0 -75
  271. package/src/main/__tests__/scheduler-fix-plan-path.test.cjs +0 -119
  272. package/src/main/__tests__/scheduler-force-tick-outcome.test.cjs +0 -46
  273. package/src/main/__tests__/scheduler-foreign-wip-manifest.test.cjs +0 -78
  274. package/src/main/__tests__/scheduler-gate-shadow.test.cjs +0 -119
  275. package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +0 -390
  276. package/src/main/__tests__/scheduler-heal-refusal.test.cjs +0 -61
  277. package/src/main/__tests__/scheduler-heartbeat-payload.test.cjs +0 -80
  278. package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +0 -323
  279. package/src/main/__tests__/scheduler-integration-failure-stamp.test.cjs +0 -41
  280. package/src/main/__tests__/scheduler-investigation-clean-skip.test.cjs +0 -63
  281. package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +0 -123
  282. package/src/main/__tests__/scheduler-job-budget.test.cjs +0 -172
  283. package/src/main/__tests__/scheduler-job-overrun.test.cjs +0 -175
  284. package/src/main/__tests__/scheduler-launch-failure.test.cjs +0 -199
  285. package/src/main/__tests__/scheduler-leftover-fields.test.cjs +0 -52
  286. package/src/main/__tests__/scheduler-leftover-quarantine.test.cjs +0 -199
  287. package/src/main/__tests__/scheduler-looks-done.test.cjs +0 -537
  288. package/src/main/__tests__/scheduler-manual-pause.test.cjs +0 -118
  289. package/src/main/__tests__/scheduler-mechanical-recovery.test.cjs +0 -245
  290. package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +0 -46
  291. package/src/main/__tests__/scheduler-mutate-reentrancy.test.cjs +0 -117
  292. package/src/main/__tests__/scheduler-needs-review-autoresolve.test.cjs +0 -197
  293. package/src/main/__tests__/scheduler-never-stop.test.cjs +0 -157
  294. package/src/main/__tests__/scheduler-no-dead-end-status.test.cjs +0 -152
  295. package/src/main/__tests__/scheduler-no-orphan-run-dir.test.cjs +0 -81
  296. package/src/main/__tests__/scheduler-notify-originating-tab-transcript.test.cjs +0 -87
  297. package/src/main/__tests__/scheduler-notify-originating-tab.test.cjs +0 -343
  298. package/src/main/__tests__/scheduler-periodic-reverify-guard.test.cjs +0 -217
  299. package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +0 -164
  300. package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +0 -161
  301. package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +0 -178
  302. package/src/main/__tests__/scheduler-quarantine-autoresolve.test.cjs +0 -165
  303. package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +0 -257
  304. package/src/main/__tests__/scheduler-rate-limit-cooldown-freshness.test.cjs +0 -123
  305. package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +0 -152
  306. package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +0 -156
  307. package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +0 -754
  308. package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +0 -87
  309. package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +0 -100
  310. package/src/main/__tests__/scheduler-reconcile-history-backfill.test.cjs +0 -105
  311. package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +0 -203
  312. package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +0 -247
  313. package/src/main/__tests__/scheduler-reset-job-fields-guard.test.cjs +0 -77
  314. package/src/main/__tests__/scheduler-resume-recovery.test.cjs +0 -254
  315. package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +0 -115
  316. package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +0 -380
  317. package/src/main/__tests__/scheduler-sigterm-commit.test.cjs +0 -43
  318. package/src/main/__tests__/scheduler-stall-per-project.test.cjs +0 -126
  319. package/src/main/__tests__/scheduler-starve-escalation.test.cjs +0 -154
  320. package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +0 -252
  321. package/src/main/__tests__/scheduler-stranded-investigation.test.cjs +0 -185
  322. package/src/main/__tests__/scheduler-stuck-failed-escalation.test.cjs +0 -136
  323. package/src/main/__tests__/scheduler-supervisor-record.test.cjs +0 -81
  324. package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +0 -54
  325. package/src/main/__tests__/scheduler-tick-wedge.test.cjs +0 -172
  326. package/src/main/__tests__/scheduler-transient-failure.test.cjs +0 -141
  327. package/src/main/__tests__/scheduler-unreadable-queue-guard.test.cjs +0 -62
  328. package/src/main/__tests__/scheduler-utilization-hold.test.cjs +0 -89
  329. package/src/main/__tests__/scheduler-verify-prd-path.test.cjs +0 -109
  330. package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +0 -234
  331. package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +0 -120
  332. package/src/main/__tests__/scheduler-writeprd-epic-rollback.test.cjs +0 -105
  333. package/src/main/__tests__/schedulerBatchRootBlocker.test.cjs +0 -117
  334. package/src/main/__tests__/schedulerStateSidecarRestore.test.cjs +0 -110
  335. package/src/main/__tests__/seedAgentPersonas.test.cjs +0 -184
  336. package/src/main/__tests__/seedSchedulerMcp.test.cjs +0 -211
  337. package/src/main/__tests__/seedStatus.test.cjs +0 -100
  338. package/src/main/__tests__/seedValidatorPersona.test.cjs +0 -116
  339. package/src/main/__tests__/stop-signal-anchor.test.cjs +0 -75
  340. package/src/main/__tests__/telemetryClient.test.cjs +0 -1055
  341. package/src/main/__tests__/telemetryContract.test.cjs +0 -930
  342. package/src/main/__tests__/telemetrySettings.test.cjs +0 -210
  343. package/src/main/__tests__/transcripts-batch-flush.test.cjs +0 -249
  344. package/src/main/__tests__/transcripts-doFlush-array.test.cjs +0 -124
  345. package/src/main/__tests__/transcripts-paged-reads.test.cjs +0 -241
  346. package/src/main/__tests__/transcripts-worktree-epic-path.test.cjs +0 -154
  347. package/src/main/__tests__/transcriptsUsageFor.test.cjs +0 -206
  348. package/src/main/__tests__/uniquePrdNumbers.test.cjs +0 -153
  349. package/src/main/__tests__/usageSingleFlight.test.cjs +0 -169
  350. package/src/main/__tests__/validationSentinels.test.cjs +0 -84
  351. package/src/main/__tests__/workTypeLibrary.test.cjs +0 -89
  352. package/src/main/bilkoHost.cjs +0 -314
  353. package/src/main/bilkoHostCore.cjs +0 -89
  354. package/src/main/lib/__tests__/active-sessions.test.cjs +0 -251
  355. package/src/main/lib/__tests__/activeIndexRebuild.test.cjs +0 -179
  356. package/src/main/lib/__tests__/agentPersonaSchema.test.cjs +0 -67
  357. package/src/main/lib/__tests__/auditLog.test.cjs +0 -38
  358. package/src/main/lib/__tests__/bootSelfHeal.test.cjs +0 -107
  359. package/src/main/lib/__tests__/branchSweep.test.cjs +0 -164
  360. package/src/main/lib/__tests__/buildIdentity.test.cjs +0 -121
  361. package/src/main/lib/__tests__/buildTarget.test.cjs +0 -52
  362. package/src/main/lib/__tests__/childWithLog.test.cjs +0 -321
  363. package/src/main/lib/__tests__/coldBootPromptSessionsWrite.test.cjs +0 -87
  364. package/src/main/lib/__tests__/crashTelemetry.test.cjs +0 -103
  365. package/src/main/lib/__tests__/credentials-futile-refresh.test.cjs +0 -115
  366. package/src/main/lib/__tests__/cwdClassify.test.cjs +0 -111
  367. package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +0 -95
  368. package/src/main/lib/__tests__/delegationReadiness.test.cjs +0 -1175
  369. package/src/main/lib/__tests__/dispatchLoop.test.cjs +0 -63
  370. package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +0 -244
  371. package/src/main/lib/__tests__/ephemeralCwd.test.cjs +0 -91
  372. package/src/main/lib/__tests__/epicDelegationStats.test.cjs +0 -137
  373. package/src/main/lib/__tests__/epicSpawnCwd.test.cjs +0 -283
  374. package/src/main/lib/__tests__/epicSpawnPlan.test.cjs +0 -196
  375. package/src/main/lib/__tests__/epicTranscriptPath.test.cjs +0 -163
  376. package/src/main/lib/__tests__/epicWorktreeBoot.test.cjs +0 -136
  377. package/src/main/lib/__tests__/epicWorktreeMerge.test.cjs +0 -130
  378. package/src/main/lib/__tests__/epicWorktreeMint.test.cjs +0 -117
  379. package/src/main/lib/__tests__/epicWorktreeProjectConfig.test.cjs +0 -137
  380. package/src/main/lib/__tests__/fixChainDepth.test.cjs +0 -40
  381. package/src/main/lib/__tests__/fixtures/204-mercury-steam-horse.log.txt +0 -13
  382. package/src/main/lib/__tests__/fixtures/scheduler-machine.json.corrupt-1789147548 +0 -34
  383. package/src/main/lib/__tests__/gateFixtures.json +0 -20
  384. package/src/main/lib/__tests__/gitCacheBound.test.cjs +0 -69
  385. package/src/main/lib/__tests__/gitWorktree.test.cjs +0 -1478
  386. package/src/main/lib/__tests__/gitWorktreeSalvage.test.cjs +0 -107
  387. package/src/main/lib/__tests__/gitWorktreeSalvageDelta.test.cjs +0 -153
  388. package/src/main/lib/__tests__/guardShims.test.cjs +0 -158
  389. package/src/main/lib/__tests__/importReferences.spec.cjs +0 -56
  390. package/src/main/lib/__tests__/instanceLock.test.cjs +0 -173
  391. package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +0 -78
  392. package/src/main/lib/__tests__/jobWorktree.test.cjs +0 -199
  393. package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +0 -82
  394. package/src/main/lib/__tests__/landedSinceRun.test.cjs +0 -133
  395. package/src/main/lib/__tests__/launchFailure.test.cjs +0 -220
  396. package/src/main/lib/__tests__/loadGate.test.cjs +0 -303
  397. package/src/main/lib/__tests__/localAdminHttp.test.cjs +0 -214
  398. package/src/main/lib/__tests__/loopDelay.test.cjs +0 -68
  399. package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +0 -107
  400. package/src/main/lib/__tests__/modelCatalog.test.cjs +0 -202
  401. package/src/main/lib/__tests__/opsOwnership.test.cjs +0 -113
  402. package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +0 -328
  403. package/src/main/lib/__tests__/opsRootNestedWrite.test.cjs +0 -51
  404. package/src/main/lib/__tests__/opsRootResolve.test.cjs +0 -149
  405. package/src/main/lib/__tests__/prdDeclaredPaths.test.cjs +0 -82
  406. package/src/main/lib/__tests__/prdDisposition.test.cjs +0 -224
  407. package/src/main/lib/__tests__/procIdentity.test.cjs +0 -119
  408. package/src/main/lib/__tests__/procName.test.cjs +0 -92
  409. package/src/main/lib/__tests__/projectBriefCore.test.cjs +0 -216
  410. package/src/main/lib/__tests__/projectRootResolve.test.cjs +0 -148
  411. package/src/main/lib/__tests__/queueHealth.test.cjs +0 -58
  412. package/src/main/lib/__tests__/queueStoreAtomicWrite.test.cjs +0 -88
  413. package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +0 -190
  414. package/src/main/lib/__tests__/quietMachineLease.test.cjs +0 -39
  415. package/src/main/lib/__tests__/rateLimitWindow.test.cjs +0 -88
  416. package/src/main/lib/__tests__/reaperHelpers.test.cjs +0 -577
  417. package/src/main/lib/__tests__/schedulerBatchDepends.test.cjs +0 -312
  418. package/src/main/lib/__tests__/schedulerBatchFairness.test.cjs +0 -213
  419. package/src/main/lib/__tests__/schedulerBatchLaunchHold.test.cjs +0 -125
  420. package/src/main/lib/__tests__/schedulerBatchProjectCap.test.cjs +0 -127
  421. package/src/main/lib/__tests__/schedulerBatchQuietMachine.test.cjs +0 -109
  422. package/src/main/lib/__tests__/schedulerMcpServerHeadlessRefusal.test.cjs +0 -71
  423. package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +0 -216
  424. package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +0 -183
  425. package/src/main/lib/__tests__/schedulerPaths.test.cjs +0 -226
  426. package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +0 -133
  427. package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +0 -56
  428. package/src/main/lib/__tests__/sessionSlots.test.cjs +0 -144
  429. package/src/main/lib/__tests__/telemetryBacklog.test.cjs +0 -626
  430. package/src/main/lib/__tests__/telemetryBoot.test.cjs +0 -134
  431. package/src/main/lib/__tests__/telemetryConsent.test.cjs +0 -136
  432. package/src/main/lib/__tests__/telemetryCounters.test.cjs +0 -57
  433. package/src/main/lib/__tests__/telemetryCountersMetadataColumn.test.cjs +0 -98
  434. package/src/main/lib/__tests__/terminalRunOutcome.test.cjs +0 -200
  435. package/src/main/lib/__tests__/toolUseClassify.test.cjs +0 -53
  436. package/src/main/lib/__tests__/updateCheck.test.cjs +0 -63
  437. package/src/main/lib/__tests__/upgradeDrain.test.cjs +0 -130
  438. package/src/main/lib/__tests__/usageCircuit.test.cjs +0 -354
  439. package/src/main/lib/__tests__/watchdog-helpers.test.cjs +0 -441
  440. package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +0 -266
  441. package/src/main/lib/kgExchangePairing.cjs +0 -75
@@ -1,113 +0,0 @@
1
- // opsOwnership.cjs — the single-writer law over a project's ops root.
2
- // vitest globals (test/beforeEach) — same convention as the other .cjs tests.
3
- const assert = require('node:assert/strict');
4
- const path = require('node:path');
5
- const os = require('node:os');
6
- const fs = require('node:fs');
7
- const { parseOpsPath, checkOpsWrite, assertOpsWrite, OWNERS } = require('../opsOwnership.cjs');
8
-
9
- const P = '/home/u/Projects/demo/session-manager-operations';
10
-
11
- test('parseOpsPath: splits namespace and remainder, ignores non-ops paths', () => {
12
- assert.deepEqual(parseOpsPath(`${P}/prompt-sessions/active-index.json`), {
13
- inOps: true, namespace: 'prompt-sessions', relative: 'active-index.json',
14
- });
15
- assert.deepEqual(parseOpsPath(`${P}/scheduler/epics/e1/prds/900-x.md`), {
16
- inOps: true, namespace: 'scheduler', relative: 'epics/e1/prds/900-x.md',
17
- });
18
- assert.equal(parseOpsPath('/home/u/.claude/settings.json').inOps, false);
19
- assert.equal(parseOpsPath(null).inOps, false);
20
- });
21
-
22
- test('paths outside any ops root are not governed by ownership', () => {
23
- assert.deepEqual(checkOpsWrite('/home/u/.claude/settings.json', undefined), { ok: true, error: null });
24
- });
25
-
26
- test('the owner of a namespace may write it', () => {
27
- assert.equal(checkOpsWrite(`${P}/prompt-sessions/active-index.json`, 'epics').ok, true);
28
- assert.equal(checkOpsWrite(`${P}/scheduler/state/queue.json`, 'scheduler').ok, true);
29
- assert.equal(checkOpsWrite(`${P}/project-brief/brief.json`, 'project-home').ok, true);
30
- // project-pages: declared owner is 'project-home', same as project-brief —
31
- // this governs only the app's own /admin/project-home/render write path
32
- // (config.cjs), NOT a project-home-builder Epic's own Write-tool authoring
33
- // of summary.json/picks.json, which never goes through config.cjs at all
34
- // and so is unaffected by this table either way (see project-pages/README.md).
35
- assert.equal(checkOpsWrite(`${P}/project-pages/output/manifest.json`, 'project-home').ok, true);
36
- });
37
-
38
- test('a non-owner is refused, and the error names the owner', () => {
39
- const res = checkOpsWrite(`${P}/project-brief/brief.json`, 'scheduler');
40
- assert.equal(res.ok, false);
41
- assert.match(res.error, /owned by 'project-home'/);
42
- // The exact cross-write this law exists to prevent: another surface
43
- // rewriting the Epic store out from under Epics.
44
- assert.equal(checkOpsWrite(`${P}/prompt-sessions/psess-1.json`, 'scheduler').ok, false);
45
- assert.equal(checkOpsWrite(`${P}/project-pages/output/manifest.json`, 'scheduler').ok, false);
46
- });
47
-
48
- test('an undeclared writer is refused (fail-closed)', () => {
49
- const res = checkOpsWrite(`${P}/scheduler/state/queue.json`, undefined);
50
- assert.equal(res.ok, false);
51
- assert.match(res.error, /did not declare a writer/);
52
- });
53
-
54
- test('a namespace with no declared owner is refused, not silently allowed', () => {
55
- const res = checkOpsWrite(`${P}/brand-new-thing/state.json`, 'scheduler');
56
- assert.equal(res.ok, false);
57
- assert.match(res.error, /no declared owner/);
58
- });
59
-
60
- test('the ops root itself is unownable', () => {
61
- assert.equal(checkOpsWrite(`${P}/loose-file.json`, 'epics').ok, false);
62
- });
63
-
64
- test('the scheduler delegation covers active-index.json and nothing else', () => {
65
- assert.equal(checkOpsWrite(`${P}/prompt-sessions/active-index.json`, 'scheduler').ok, true);
66
- // Not the archives, and not a lookalike nested path.
67
- assert.equal(checkOpsWrite(`${P}/prompt-sessions/psess-9.json`, 'scheduler').ok, false);
68
- assert.equal(checkOpsWrite(`${P}/prompt-sessions/nested/active-index.json`, 'scheduler').ok, false);
69
- });
70
-
71
- test('assertOpsWrite throws for a refused write and is silent for an allowed one', () => {
72
- assert.throws(() => assertOpsWrite(`${P}/project-brief/brief.json`, 'epics'), /single-writer law/);
73
- assert.doesNotThrow(() => assertOpsWrite(`${P}/project-brief/brief.json`, 'project-home'));
74
- });
75
-
76
- test('assertOpsWrite fails closed (not a raw crash) on a relative path that looks like it is inside an ops root', () => {
77
- // A caller that skips opsPath/resolveProjectRoot (which already throw on a
78
- // relative cwd) and builds its own path string by hand must still get a
79
- // graceful, catchable refusal here — this is the last line of defense
80
- // (PRD 1082). Before this guard, classifyCwd's absolute-path check (added
81
- // for the worktree-relative-cwd hazard) left innermostOpsRoot null for a
82
- // relative path, and path.dirname(null) threw an untagged TypeError
83
- // instead of the intended `unknownCwd`-tagged Error.
84
- assert.throws(
85
- () => assertOpsWrite('session-manager-operations/scheduler/state/queue.json', 'scheduler'),
86
- (e) => e.unknownCwd === true && /absolute/.test(e.message),
87
- );
88
- });
89
-
90
- test('every declared owner is a non-empty string (table sanity)', () => {
91
- for (const [ns, owner] of Object.entries(OWNERS)) {
92
- assert.equal(typeof owner, 'string', `${ns} owner must be a string`);
93
- assert.ok(owner.length > 0, `${ns} owner must be non-empty`);
94
- }
95
- });
96
-
97
- // Integration: config.cjs must enforce the law on its real write path, not
98
- // just expose it — a write with the wrong writer has to actually fail.
99
- test('config.writeJson refuses an ops write from a non-owner', async () => {
100
- const config = require('../../config.cjs');
101
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ops-law-'));
102
- config.addAllowedRoot(root);
103
- const target = path.join(root, 'session-manager-operations', 'project-brief', 'brief.json');
104
-
105
- await assert.rejects(() => config.writeJson(target, { a: 1 }, { writer: 'epics' }), /single-writer law/);
106
- await assert.rejects(() => config.writeJson(target, { a: 1 }), /did not declare a writer/);
107
-
108
- const res = await config.writeJson(target, { a: 1 }, { writer: 'project-home' });
109
- assert.equal(res.ok, true);
110
- assert.equal(JSON.parse(fs.readFileSync(target, 'utf8')).a, 1);
111
-
112
- fs.rmSync(root, { recursive: true, force: true });
113
- });
@@ -1,328 +0,0 @@
1
- /**
2
- * opsRootAbsoluteCwd.test.cjs — a project cwd must be an ABSOLUTE path, at
3
- * both ends of the ops-root resolution chain.
4
- *
5
- * The incident this guards (observed live 2026-08-13): five stray
6
- * `session-manager-operations/scheduler/state/queue.json` trees appeared under
7
- * source directories — `src/main/lib/`, `src/main/`, `src/renderer/state/`,
8
- * `src/renderer/components/epics/`, `src/renderer/testUtils/` — each holding
9
- * an empty `{"jobs": []}`. Cause: `path.join()` silently resolves a RELATIVE
10
- * first segment against `process.cwd()`, so a caller handing
11
- * `projectStateDir()` a repo-relative fragment like `src/main/lib` gets a
12
- * plausible-looking absolute path pointing at entirely the wrong place, and a
13
- * whole ops root is materialized there. Nothing threw; the writes "succeeded".
14
- *
15
- * This is the ops-root hazard gitWorktree.cjs's header comment describes,
16
- * reached from the opposite end: not a worktree dir substituted for a project
17
- * cwd, but a relative fragment accepted as one. A PROJECT IS A CWD and a cwd
18
- * is absolute (CLAUDE.md's knownProjectAggregate.ts bullet says the same
19
- * thing renderer-side) — so both layers fail closed rather than guessing.
20
- *
21
- * Run: timeout 120 npx vitest run src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs
22
- */
23
-
24
- 'use strict';
25
-
26
- import { test, expect, afterEach } from 'vitest';
27
- const fs = require('node:fs');
28
- const fsp = require('node:fs/promises');
29
- const os = require('node:os');
30
- const path = require('node:path');
31
- const queueStore = require('../queueStore.cjs');
32
- const { activeProjectCwds } = require('../activeSessions.cjs');
33
-
34
- const tmpDirs = [];
35
-
36
- afterEach(async () => {
37
- while (tmpDirs.length) {
38
- await fsp.rm(tmpDirs.pop(), { recursive: true, force: true }).catch(() => {});
39
- }
40
- });
41
-
42
- // ─── queueStore.projectStateDir — the write-path guard ──────────────────────
43
-
44
- test('projectStateDir refuses a relative cwd instead of resolving it against process.cwd()', () => {
45
- expect(() => queueStore.projectStateDir('src/main/lib')).toThrow(/absolute path/);
46
- expect(() => queueStore.projectStateDir('./somewhere')).toThrow(/absolute path/);
47
- expect(() => queueStore.projectStateDir('..')).toThrow(/absolute path/);
48
- });
49
-
50
- test('projectStateDir names the offending value so a bad caller is findable from the log alone', () => {
51
- expect(() => queueStore.projectStateDir('src/renderer/state')).toThrow(/src\/renderer\/state/);
52
- });
53
-
54
- test('projectStateDir still accepts an absolute cwd and appends the ops-root subpath', () => {
55
- const dir = queueStore.projectStateDir('/projects/foo');
56
- expect(dir).toBe(path.join('/projects/foo', 'session-manager-operations', 'scheduler', 'state'));
57
- });
58
-
59
- test('projectStateDir keeps rejecting a missing/non-string cwd', () => {
60
- expect(() => queueStore.projectStateDir()).toThrow(/cwd is required/);
61
- expect(() => queueStore.projectStateDir('')).toThrow(/cwd is required/);
62
- expect(() => queueStore.projectStateDir(42)).toThrow(/cwd is required/);
63
- });
64
-
65
- // ─── activeSessions.addCwd — the upstream guard ─────────────────────────────
66
- //
67
- // Building a transcript fixture whose recorded `cwd` is relative is the whole
68
- // point: statSync('src/main/lib') SUCCEEDS whenever this process happens to
69
- // run from the repo root, which is exactly how such a value used to survive
70
- // into the project list and reach projectStateDir.
71
-
72
- async function mkProjectsDir(rows) {
73
- const projectsDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-opsroot-projects-'));
74
- tmpDirs.push(projectsDir);
75
- for (const [slug, cwd] of Object.entries(rows)) {
76
- const dir = path.join(projectsDir, slug);
77
- fs.mkdirSync(dir, { recursive: true });
78
- fs.writeFileSync(path.join(dir, 'session.jsonl'), `${JSON.stringify({ cwd })}\n`, 'utf8');
79
- }
80
- return projectsDir;
81
- }
82
-
83
- test('a transcript recording a RELATIVE cwd never enters the project list', async () => {
84
- const projectsDir = await mkProjectsDir({ relative: 'src/main/lib' });
85
- // Sanity: the bad value really would have passed the old existence check.
86
- expect(fs.existsSync('src/main/lib')).toBe(true);
87
-
88
- const cwds = activeProjectCwds(Infinity, { projectsDir });
89
- expect(cwds).not.toContain('src/main/lib');
90
- expect(cwds).toEqual([]);
91
- });
92
-
93
- test('an absolute cwd that exists is still collected', async () => {
94
- const real = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-opsroot-real-'));
95
- tmpDirs.push(real);
96
- const projectsDir = await mkProjectsDir({ good: real });
97
-
98
- const cwds = activeProjectCwds(Infinity, { projectsDir });
99
- expect(cwds).toContain(real);
100
- });
101
-
102
- test('an absolute cwd pointing at a FILE (not a directory) is rejected — a file is not a project', async () => {
103
- const holder = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-opsroot-file-'));
104
- tmpDirs.push(holder);
105
- const filePath = path.join(holder, 'not-a-dir.txt');
106
- fs.writeFileSync(filePath, 'x', 'utf8');
107
- const projectsDir = await mkProjectsDir({ afile: filePath });
108
-
109
- const cwds = activeProjectCwds(Infinity, { projectsDir });
110
- expect(cwds).not.toContain(filePath);
111
- });
112
-
113
- test('an absolute cwd that no longer exists on disk is rejected', async () => {
114
- const projectsDir = await mkProjectsDir({ gone: path.join(os.tmpdir(), 'sm-opsroot-definitely-not-here-12345') });
115
-
116
- const cwds = activeProjectCwds(Infinity, { projectsDir });
117
- expect(cwds).toEqual([]);
118
- });
119
-
120
- test('the guards compose: no relative cwd from a transcript can reach an ops-root write', async () => {
121
- const projectsDir = await mkProjectsDir({ relative: 'src/renderer/state', alsoRelative: './src/main' });
122
- const cwds = activeProjectCwds(Infinity, { projectsDir });
123
- expect(cwds).toEqual([]);
124
- // And even if one somehow did, the write path itself refuses it.
125
- expect(() => queueStore.projectStateDir('src/renderer/state')).toThrow(/absolute path/);
126
- });
127
-
128
- // ─── the second incident: an ABSOLUTE cwd INSIDE an ops root ────────────────
129
- //
130
- // Reported from starry-night-ships on 2026-08-30: 14 stray
131
- // `.../session-manager-operations/scheduler/state/queue.json` stubs, each
132
- // nested under another ops directory (`scheduler/prds/`, `prompt-sessions/`,
133
- // `scheduler/epics/<id>/prds/`), every one holding `{"jobs": []}` and mtimes
134
- // spanning 8 days. The 2026-08-13 absolute-path guard above could not catch
135
- // them: these cwds ARE absolute and DO exist. They came from transcripts
136
- // where an agent had `cd`'d into the artifact directory it was writing, so
137
- // the last transcript row recorded that subdirectory as the session cwd.
138
-
139
- test('a transcript cwd inside an ops root is normalized to the project root, not taken literally', async () => {
140
- const project = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-opsroot-nested-'));
141
- tmpDirs.push(project);
142
- const prdDir = path.join(project, 'session-manager-operations', 'scheduler', 'epics', 'e-1', 'prds');
143
- fs.mkdirSync(prdDir, { recursive: true });
144
- const projectsDir = await mkProjectsDir({ nested: prdDir });
145
-
146
- const cwds = activeProjectCwds(Infinity, { projectsDir });
147
- expect(cwds).toContain(project);
148
- expect(cwds).not.toContain(prdDir);
149
- });
150
-
151
- test('normalization keeps the active-project signal rather than dropping the row', () => {
152
- const { projectRootOf } = require('../activeSessions.cjs');
153
- expect(projectRootOf('/p/session-manager-operations/prompt-sessions')).toBe('/p');
154
- expect(projectRootOf('/p/session-manager-operations')).toBe('/p');
155
- expect(projectRootOf('/p/session-manager-operations/scheduler/state')).toBe('/p');
156
- // Already-nested strays truncate at the FIRST segment — the inner one is
157
- // itself the bug, never a project.
158
- expect(projectRootOf('/p/session-manager-operations/x/session-manager-operations/y')).toBe('/p');
159
- // A plain project cwd is untouched.
160
- expect(projectRootOf('/p/src/main')).toBe('/p/src/main');
161
- // A directory that merely CONTAINS the name as a substring is not a segment.
162
- expect(projectRootOf('/p/session-manager-operations-archive')).toBe('/p/session-manager-operations-archive');
163
- });
164
-
165
- test('projectStateDir fails closed on an ops-internal cwd, naming the offending value', () => {
166
- expect(() => queueStore.projectStateDir('/p/session-manager-operations/scheduler/prds'))
167
- .toThrow(/must be a project root/);
168
- expect(() => queueStore.projectStateDir('/p/session-manager-operations/scheduler/prds'))
169
- .toThrow(/scheduler\/prds/);
170
- // The guard is segment-exact, not a substring match.
171
- expect(queueStore.projectStateDir('/p/session-manager-operations-archive'))
172
- .toBe(path.join('/p/session-manager-operations-archive', 'session-manager-operations', 'scheduler', 'state'));
173
- });
174
-
175
- // The writeSplit half of this guard lives in opsRootNestedWrite.test.cjs —
176
- // it must override HOME before requiring queueStore (writeSplit persists
177
- // machine state to a homedir path), which cannot be done from this file.
178
-
179
- // ─── the third incident: a cwd INSIDE a linked git worktree ─────────────────
180
- //
181
- // Verified live 2026-09-01 in starry-night-ships: a transcript's last `cwd`
182
- // row pointed at `/tmp/session-manager-epic-worktrees/<hash>/<epicId>` — an
183
- // absolute, existing directory, so it was accepted as a project and the real
184
- // project root (`/home/bilko/Projects/starry-night-ships`) never entered the
185
- // list at all. `worktreeMainRootOf` reverses a linked worktree's `.git` FILE
186
- // (`gitdir: <main>/.git/worktrees/<name>`) back to `<main>`.
187
-
188
- const { worktreeMainRootOf } = require('../activeSessions.cjs');
189
- const { KIND_CONFIG: WORKTREE_KIND_CONFIG } = require('../gitWorktree.cjs');
190
-
191
- // A worktree's MAIN tree root must live outside os.tmpdir() for these fixtures
192
- // to double as a realistic "real project" for the tmp-drop-guard tests below
193
- // (a real project is never itself under /tmp — only the linked worktree is).
194
- // `test-results/` is already gitignored in this repo.
195
- const NON_TMP_SCRATCH_ROOT = path.join(process.cwd(), 'test-results', 'opsroot-fixtures');
196
-
197
- async function mkNonTmpDir(prefix) {
198
- fs.mkdirSync(NON_TMP_SCRATCH_ROOT, { recursive: true });
199
- return fsp.mkdtemp(path.join(NON_TMP_SCRATCH_ROOT, prefix));
200
- }
201
-
202
- async function makeWorktreeFixture() {
203
- const main = await mkNonTmpDir('sm-opsroot-main-');
204
- tmpDirs.push(main);
205
- fs.mkdirSync(path.join(main, '.git'), { recursive: true });
206
- const worktree = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-opsroot-worktree-'));
207
- tmpDirs.push(worktree);
208
- const worktreeName = 'sm-epic-abc123';
209
- const worktreeGitFile = path.join(worktree, '.git');
210
- const adminDir = path.join(main, '.git', 'worktrees', worktreeName);
211
- fs.mkdirSync(adminDir, { recursive: true });
212
- // Real `git worktree add` writes this back-reference (the admin dir's own
213
- // `gitdir` file points at the linked worktree's `.git` FILE) — required by
214
- // worktreeMainRootOf's round-trip check so a crafted `.git` file elsewhere
215
- // on disk can't redirect resolution to an arbitrary directory.
216
- fs.writeFileSync(path.join(adminDir, 'gitdir'), `${worktreeGitFile}\n`, 'utf8');
217
- fs.writeFileSync(worktreeGitFile, `gitdir: ${adminDir}\n`, 'utf8');
218
- return { main, worktree };
219
- }
220
-
221
- test('worktreeMainRootOf resolves a linked worktree .git FILE back to the main tree root', async () => {
222
- const { main, worktree } = await makeWorktreeFixture();
223
- expect(worktreeMainRootOf(worktree)).toBe(main);
224
- });
225
-
226
- test('worktreeMainRootOf returns the ancestor unchanged when .git is a real directory', async () => {
227
- const main = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-opsroot-plain-main-'));
228
- tmpDirs.push(main);
229
- fs.mkdirSync(path.join(main, '.git'), { recursive: true });
230
- const nested = path.join(main, 'src', 'main', 'lib');
231
- fs.mkdirSync(nested, { recursive: true });
232
- expect(worktreeMainRootOf(nested)).toBe(main);
233
- });
234
-
235
- test('worktreeMainRootOf returns null on a garbled .git file instead of throwing', async () => {
236
- const dir = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-opsroot-garbled-'));
237
- tmpDirs.push(dir);
238
- fs.writeFileSync(path.join(dir, '.git'), 'not a gitdir line at all', 'utf8');
239
- expect(worktreeMainRootOf(dir)).toBeNull();
240
-
241
- const dir2 = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-opsroot-garbled2-'));
242
- tmpDirs.push(dir2);
243
- fs.writeFileSync(path.join(dir2, '.git'), 'gitdir: /some/path/without/the/marker\n', 'utf8');
244
- expect(worktreeMainRootOf(dir2)).toBeNull();
245
- });
246
-
247
- test('worktreeMainRootOf refuses a crafted .git file that redirects to an arbitrary directory with no back-reference', async () => {
248
- // A `.git` FILE is an ordinary file — nothing stops an untrusted repo from
249
- // shipping one at a nested path pointing anywhere. Without verifying the
250
- // admin dir's own `gitdir` back-reference, this would let a crafted repo
251
- // redirect activeSessions' project-root resolution (and therefore
252
- // queueStore's ops-root writes) to an attacker-chosen absolute directory.
253
- const arbitraryTarget = await mkNonTmpDir('sm-opsroot-arbitrary-target-');
254
- tmpDirs.push(arbitraryTarget);
255
- const attackerDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-opsroot-attacker-'));
256
- tmpDirs.push(attackerDir);
257
- // No admin dir/back-reference file exists under arbitraryTarget/.git/worktrees/.
258
- fs.writeFileSync(
259
- path.join(attackerDir, '.git'),
260
- `gitdir: ${path.join(arbitraryTarget, '.git', 'worktrees', 'x')}\n`,
261
- 'utf8',
262
- );
263
- expect(worktreeMainRootOf(attackerDir)).toBeNull();
264
- });
265
-
266
- test('worktreeMainRootOf refuses a crafted .git file whose admin dir exists but back-references a DIFFERENT worktree', async () => {
267
- const { main } = await makeWorktreeFixture();
268
- const attackerDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-opsroot-attacker2-'));
269
- tmpDirs.push(attackerDir);
270
- // Points at a REAL admin dir (main's own), but that admin dir's gitdir
271
- // back-reference points at the ORIGINAL worktree, not attackerDir.
272
- fs.writeFileSync(
273
- path.join(attackerDir, '.git'),
274
- `gitdir: ${path.join(main, '.git', 'worktrees', 'sm-epic-abc123')}\n`,
275
- 'utf8',
276
- );
277
- expect(worktreeMainRootOf(attackerDir)).toBeNull();
278
- });
279
-
280
- test('worktreeMainRootOf returns null for a plain non-git directory', async () => {
281
- const dir = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-opsroot-nogit-'));
282
- tmpDirs.push(dir);
283
- expect(worktreeMainRootOf(dir)).toBeNull();
284
- });
285
-
286
- test('projectRootOf resolves a worktree cwd to the main tree root', async () => {
287
- const { main, worktree } = await makeWorktreeFixture();
288
- const { projectRootOf } = require('../activeSessions.cjs');
289
- expect(projectRootOf(worktree)).toBe(main);
290
- });
291
-
292
- test('projectRootOf resolves a cwd nested inside a worktree\'s OWN ops tree to the main root', async () => {
293
- const { main, worktree } = await makeWorktreeFixture();
294
- const { projectRootOf } = require('../activeSessions.cjs');
295
- const nestedOps = path.join(worktree, 'session-manager-operations', 'scheduler', 'state');
296
- fs.mkdirSync(nestedOps, { recursive: true });
297
- expect(projectRootOf(nestedOps)).toBe(main);
298
- });
299
-
300
- test('activeProjectCwds resolves a worktree cwd to the real project and drops unresolvable tmp cwds', async () => {
301
- const { main, worktree } = await makeWorktreeFixture();
302
- // A bare, .git-less directory directly under the managed epic-worktree
303
- // root — the shape a torn-down or mid-teardown worktree leaves behind.
304
- // worktreeMainRootOf can't resolve it (no .git), so only the drop-guard's
305
- // prefixDropRoots check keeps it out of the project list.
306
- fs.mkdirSync(WORKTREE_KIND_CONFIG.epic.root, { recursive: true });
307
- const bareWorktreeRoot = await fsp.mkdtemp(path.join(WORKTREE_KIND_CONFIG.epic.root, 'sm-opsroot-bare-'));
308
- tmpDirs.push(bareWorktreeRoot);
309
- const projectsDir = await mkProjectsDir({
310
- resolvable: worktree,
311
- unresolvable: bareWorktreeRoot,
312
- bareTmpdir: os.tmpdir(),
313
- });
314
-
315
- const cwds = activeProjectCwds(Infinity, { projectsDir });
316
- expect(cwds).toContain(main);
317
- expect(cwds).not.toContain(worktree);
318
- expect(cwds).not.toContain(bareWorktreeRoot);
319
- expect(cwds).not.toContain(os.tmpdir());
320
- });
321
-
322
- test('a main-tree root resolved from a worktree dedupes against a directly-observed cwd for the same project', async () => {
323
- const { main, worktree } = await makeWorktreeFixture();
324
- const projectsDir = await mkProjectsDir({ direct: main, viaWorktree: worktree });
325
-
326
- const cwds = activeProjectCwds(Infinity, { projectsDir });
327
- expect(cwds.filter((c) => c === main)).toHaveLength(1);
328
- });
@@ -1,51 +0,0 @@
1
- /**
2
- * opsRootNestedWrite.test.cjs — writeSplit must never materialize a second
3
- * ops root beneath the first.
4
- *
5
- * Companion to opsRootAbsoluteCwd.test.cjs; separate file because HOME must
6
- * be overridden BEFORE requiring queueStore — MACHINE_STATE_PATH is baked
7
- * into a top-level const from os.homedir() at require time, and writeSplit
8
- * writes it on every call. (Learned the hard way: running this assertion in
9
- * the shared file overwrote the real ~/.claude/session-manager/
10
- * scheduler-machine.json, dropping the user's firePolicy and concurrencyCap.)
11
- *
12
- * Run: timeout 120 npx vitest run src/main/lib/__tests__/opsRootNestedWrite.test.cjs
13
- */
14
-
15
- 'use strict';
16
-
17
- import { test, expect, afterEach } from 'vitest';
18
- const fs = require('node:fs');
19
- const fsp = require('node:fs/promises');
20
- const os = require('node:os');
21
- const path = require('node:path');
22
-
23
- const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-opsroot-home-'));
24
- process.env.HOME = tmpHome;
25
-
26
- const queueStore = require('../queueStore.cjs');
27
-
28
- const tmpDirs = [tmpHome];
29
- afterEach(async () => {
30
- while (tmpDirs.length > 1) {
31
- await fsp.rm(tmpDirs.pop(), { recursive: true, force: true }).catch(() => {});
32
- }
33
- });
34
-
35
- test('one ops-internal cwd in a write batch is skipped without losing the other projects', async () => {
36
- const good = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-opsroot-good-'));
37
- tmpDirs.push(good);
38
- const bad = path.join(good, 'session-manager-operations', 'scheduler', 'epics', 'e-1', 'prds');
39
- fs.mkdirSync(bad, { recursive: true });
40
-
41
- await queueStore.writeSplit({
42
- config: {},
43
- sourceCwds: [bad, good],
44
- jobs: [{ slug: 'keeper', cwd: good }, { slug: 'stray', cwd: bad }],
45
- });
46
-
47
- const written = JSON.parse(fs.readFileSync(queueStore.projectQueuePath(good), 'utf8'));
48
- expect(written.jobs.map((j) => j.slug)).toEqual(['keeper']);
49
- // The doubled-suffix stub the starry-night-ships report was about.
50
- expect(fs.existsSync(path.join(bad, 'session-manager-operations'))).toBe(false);
51
- });
@@ -1,149 +0,0 @@
1
- 'use strict';
2
- // Run: timeout 120 npx vitest run src/main/lib/__tests__/opsRootResolve.test.cjs
3
- //
4
- // PRD 1082 — opsOwnership.cjs is THE ONE ops-root resolver. Every reader and
5
- // writer of `<project>/session-manager-operations/` builds its path through
6
- // resolveProjectRoot / resolveOpsRoot / opsPath, which normalize a worktree or
7
- // ops-internal cwd to the real project and refuse an ephemeral one. Before
8
- // this, each namespace did its own `path.join(cwd, 'session-manager-
9
- // operations', ...)`, so the worktree-cwd hazard had to be fixed one call site
10
- // at a time (2026-08-30, PRDs 1073/1074/1081).
11
- const assert = require('node:assert/strict');
12
- const fs = require('node:fs');
13
- const fsp = require('node:fs/promises');
14
- const os = require('node:os');
15
- const path = require('node:path');
16
- const {
17
- resolveProjectRoot, resolveOpsRoot, opsPath, assertOpsWrite, OPS_ROOT_DIR,
18
- } = require('../opsOwnership.cjs');
19
- const { KIND_CONFIG } = require('../gitWorktree.cjs');
20
-
21
- const tmpDirs = [];
22
- afterEach(async () => {
23
- while (tmpDirs.length) {
24
- await fsp.rm(tmpDirs.pop(), { recursive: true, force: true }).catch(() => {});
25
- }
26
- });
27
-
28
- /** A fake main tree (has a real `.git` DIRECTORY) outside os.tmpdir(). */
29
- async function mkMainTree() {
30
- const base = path.join(os.homedir(), '.cache', 'sm-opsroot-resolve-tests');
31
- fs.mkdirSync(base, { recursive: true });
32
- const main = await fsp.mkdtemp(path.join(base, 'main-'));
33
- tmpDirs.push(main);
34
- fs.mkdirSync(path.join(main, '.git'), { recursive: true });
35
- return main;
36
- }
37
-
38
- /** A linked worktree of `main` (a `.git` FILE + the admin back-reference git writes). */
39
- async function mkLinkedWorktree(main) {
40
- const worktree = await fsp.mkdtemp(path.join(os.tmpdir(), 'sm-opsroot-resolve-wt-'));
41
- tmpDirs.push(worktree);
42
- const name = 'sm-epic-test';
43
- const adminDir = path.join(main, '.git', 'worktrees', name);
44
- fs.mkdirSync(adminDir, { recursive: true });
45
- const gitFile = path.join(worktree, '.git');
46
- fs.writeFileSync(path.join(adminDir, 'gitdir'), `${gitFile}\n`);
47
- fs.writeFileSync(gitFile, `gitdir: ${adminDir}\n`);
48
- return worktree;
49
- }
50
-
51
- // ─── byte-equality regression for the common case ──────────────────────────
52
-
53
- test('a plain project root resolves to <root>/session-manager-operations, byte-identical to the old join', async () => {
54
- const main = await mkMainTree();
55
- assert.equal(resolveProjectRoot(main), main);
56
- assert.equal(resolveOpsRoot(main), path.join(main, OPS_ROOT_DIR));
57
- assert.equal(
58
- opsPath(main, 'prompt-sessions', 'active-index.json'),
59
- path.join(main, 'session-manager-operations', 'prompt-sessions', 'active-index.json'),
60
- );
61
- });
62
-
63
- test('a plain directory with no enclosing .git anywhere is returned unchanged', () => {
64
- const p = '/definitely/not/a/real/project';
65
- assert.equal(resolveProjectRoot(p), p);
66
- assert.equal(opsPath(p, 'logs'), path.join(p, OPS_ROOT_DIR, 'logs'));
67
- });
68
-
69
- // ─── normalization ──────────────────────────────────────────────────────────
70
-
71
- test('a linked worktree root resolves to its MAIN tree', async () => {
72
- const main = await mkMainTree();
73
- const wt = await mkLinkedWorktree(main);
74
- assert.equal(resolveProjectRoot(wt), main);
75
- assert.equal(opsPath(wt, 'scheduler', 'state'), path.join(main, OPS_ROOT_DIR, 'scheduler', 'state'));
76
- });
77
-
78
- test("a cwd deep inside a worktree's OWN ops tree resolves to the main tree's ops root", async () => {
79
- const main = await mkMainTree();
80
- const wt = await mkLinkedWorktree(main);
81
- const nested = path.join(wt, OPS_ROOT_DIR, 'scheduler', 'state');
82
- fs.mkdirSync(nested, { recursive: true });
83
- assert.equal(resolveOpsRoot(nested), path.join(main, OPS_ROOT_DIR));
84
- });
85
-
86
- test('an ops-internal cwd of a plain project is truncated to the project (default normalize)', async () => {
87
- const main = await mkMainTree();
88
- const inside = path.join(main, OPS_ROOT_DIR, 'scheduler', 'epics', 'e1', 'prds');
89
- assert.equal(resolveProjectRoot(inside), main);
90
- });
91
-
92
- test("a subdirectory of a git repo resolves to the repo root (the 2026-08-13 'src/main' stray-root shape)", async () => {
93
- const main = await mkMainTree();
94
- const sub = path.join(main, 'src', 'main', 'lib');
95
- fs.mkdirSync(sub, { recursive: true });
96
- assert.equal(resolveProjectRoot(sub), main);
97
- });
98
-
99
- test('a nested project that is its OWN git repo keeps its own root', async () => {
100
- const outer = await mkMainTree();
101
- const inner = path.join(outer, 'apps', 'inner');
102
- fs.mkdirSync(path.join(inner, '.git'), { recursive: true });
103
- assert.equal(resolveProjectRoot(inner), inner);
104
- });
105
-
106
- // ─── refusals ───────────────────────────────────────────────────────────────
107
-
108
- test("opsInternal:'refuse' throws on an ops-internal cwd instead of normalizing (the writer posture)", async () => {
109
- const main = await mkMainTree();
110
- const inside = path.join(main, OPS_ROOT_DIR, 'scheduler', 'state');
111
- assert.throws(() => resolveProjectRoot(inside, { opsInternal: 'refuse' }), /not a path inside session-manager-operations/);
112
- });
113
-
114
- test('os.tmpdir() itself is refused with a tagged ephemeral error', () => {
115
- assert.throws(() => resolveOpsRoot(os.tmpdir()), (e) => e.ephemeral === true && /ephemeral/.test(e.message));
116
- });
117
-
118
- test('a bare, .git-less dir under a managed worktree root is refused (cannot be normalized)', async () => {
119
- fs.mkdirSync(KIND_CONFIG.epic.root, { recursive: true });
120
- const bare = await fsp.mkdtemp(path.join(KIND_CONFIG.epic.root, 'sm-opsroot-resolve-bare-'));
121
- tmpDirs.push(bare);
122
- assert.throws(() => opsPath(bare, 'logs'), (e) => e.ephemeral === true);
123
- });
124
-
125
- test('relative, empty and non-string cwds throw (never resolved against process.cwd())', () => {
126
- assert.throws(() => resolveProjectRoot('src/main/lib'), /absolute/);
127
- assert.throws(() => resolveProjectRoot(''), /required/);
128
- assert.throws(() => resolveProjectRoot(null), /required/);
129
- assert.throws(() => resolveProjectRoot(42), /required/);
130
- });
131
-
132
- // ─── the single-writer gate is also the last line ──────────────────────────
133
-
134
- test('assertOpsWrite refuses a well-owned write whose project root is a linked worktree', async () => {
135
- const main = await mkMainTree();
136
- const wt = await mkLinkedWorktree(main);
137
- const target = path.join(wt, OPS_ROOT_DIR, 'scheduler', 'state', 'queue.json');
138
- assert.throws(() => assertOpsWrite(target, 'scheduler'), (e) => e.ephemeral === true);
139
- // ...while the same write against the main tree is fine.
140
- assert.doesNotThrow(() => assertOpsWrite(path.join(main, OPS_ROOT_DIR, 'scheduler', 'state', 'queue.json'), 'scheduler'));
141
- });
142
-
143
- test('assertOpsWrite still refuses the wrong writer before it ever looks at the root', async () => {
144
- const main = await mkMainTree();
145
- assert.throws(
146
- () => assertOpsWrite(path.join(main, OPS_ROOT_DIR, 'scheduler', 'state', 'queue.json'), 'epics'),
147
- /single-writer law/,
148
- );
149
- });