claude-code-session-manager 0.111.0 → 0.112.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 (282) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/dist/assets/{AgentLibrary-rBO1daqM.js → AgentLibrary-RwPvEgqm.js} +2 -2
  3. package/dist/assets/{Button-DsEYRZg0.js → Button-36j3PzYv.js} +1 -1
  4. package/dist/assets/{DataModel-D8sbvvBC.js → DataModel-DUhANo4l.js} +1 -1
  5. package/dist/assets/{History-BEHLtxia.js → History-BeZzNjmv.js} +2 -2
  6. package/dist/assets/{Hooks-CKpvlXrg.js → Hooks-iQ8845Hp.js} +3 -3
  7. package/dist/assets/{Library-CeF4qh9d.js → Library-DPE1h-1L.js} +6 -6
  8. package/dist/assets/{ListDetail-DcfmxPFk.js → ListDetail-PStOuQAo.js} +1 -1
  9. package/dist/assets/{MarkdownEditor-CvK1pyto.js → MarkdownEditor-DPw-N2-m.js} +1 -1
  10. package/dist/assets/{McpServers-D-h2ihzJ.js → McpServers-CUyRTNep.js} +2 -2
  11. package/dist/assets/{Memory-BnF89x8v.js → Memory-bZ2xjBGy.js} +3 -3
  12. package/dist/assets/{Panel-CQY5zano.js → Panel-_VZ_uXNg.js} +1 -1
  13. package/dist/assets/{Permissions-DLukmmsz.js → Permissions-PPRmfNbK.js} +3 -3
  14. package/dist/assets/{Plugins-0Pf0OZ4l.js → Plugins-BFcvfvlx.js} +2 -2
  15. package/dist/assets/{ProvenanceBadge-vC7Tj-JP.js → ProvenanceBadge-B1WUknak.js} +1 -1
  16. package/dist/assets/RemoteMachines-D5V0lzgW.js +3 -0
  17. package/dist/assets/{SaveBar-DjijiUb0.js → SaveBar-Dw64FVdM.js} +1 -1
  18. package/dist/assets/{Scheduler-Cm3EXZvE.js → Scheduler-C8s_pJ3I.js} +9 -9
  19. package/dist/assets/{ScopeSwitcher-B-P-kSfq.js → ScopeSwitcher-D-nKDMAE.js} +1 -1
  20. package/dist/assets/{Settings-DYDlp2m_.js → Settings-CQSrR4mO.js} +4 -4
  21. package/dist/assets/{SkillReferenceGraph-Epv2GJmj.js → SkillReferenceGraph-BbZFMHim.js} +1 -1
  22. package/dist/assets/{Skills-DHO0N0C2.js → Skills-Bzyrh6EQ.js} +1 -1
  23. package/dist/assets/SystemPrompt-DP6i3Dx0.js +1 -0
  24. package/dist/assets/TagLibrary-iip0C6Bl.js +1 -0
  25. package/dist/assets/{TiptapBody-DAMO3DhO.js → TiptapBody-Cr-hxX8L.js} +1 -1
  26. package/dist/assets/Toggle-7WpPzcZH.js +1 -0
  27. package/dist/assets/{ViewTabs-CGX6MAhc.js → ViewTabs-9lQCRwWJ.js} +1 -1
  28. package/dist/assets/{historyTotals-vgfk8EOL.js → historyTotals-BrFxw5J1.js} +1 -1
  29. package/dist/assets/{index-CPieAFsl.js → index-CTBpf-26.js} +250 -250
  30. package/dist/assets/{settingsSchema-D0FxNrTy.js → settingsSchema-Dh5Kl7VO.js} +1 -1
  31. package/dist/assets/{useAgentsDirWatch-DCu6BNYN.js → useAgentsDirWatch-CMpx0d04.js} +1 -1
  32. package/dist/assets/{useRemotePeers-D4cdJDyh.js → useRemotePeers-CI2axd-L.js} +1 -1
  33. package/dist/index.html +1 -1
  34. package/package.json +3 -2
  35. package/plugins/CLAUDE.md +5 -8
  36. package/plugins/session-manager-dev/.claude-plugin/plugin.json +3 -6
  37. package/plugins/session-manager-dev/skills/builder/1-classify-and-bump/SKILL.md +2 -2
  38. package/plugins/session-manager-dev/skills/builder/SKILL.md +2 -3
  39. package/plugins/session-manager-dev/skills/develop/SKILL.md +1 -1
  40. package/plugins/session-manager-dev/skills/develop/standards.md +3 -3
  41. package/scripts/README.md +3 -3
  42. package/scripts/audit-ops-hygiene.cjs +4 -4
  43. package/scripts/hooks/guard-destructive-git.cjs +4 -4
  44. package/scripts/hooks/guard-inline-implementation.cjs +1 -1
  45. package/scripts/hooks/guard-prd-writes.cjs +2 -2
  46. package/scripts/hooks/lib/guard-destructive-git-policy.cjs +1 -1
  47. package/scripts/mint-epic.cjs +2 -2
  48. package/scripts/scheduler-mcp-server.cjs +4 -4
  49. package/src/main/agentLibrary.cjs +5 -5
  50. package/src/main/agentsIpc.cjs +76 -0
  51. package/src/main/build-info.json +4 -4
  52. package/src/main/chatRunner.cjs +12 -12
  53. package/src/main/config.cjs +2 -2
  54. package/src/main/crashDiagnostics.cjs +2 -2
  55. package/src/main/docEdit.cjs +5 -5
  56. package/src/main/files.cjs +2 -2
  57. package/src/main/git.cjs +1 -1
  58. package/src/main/health.cjs +20 -18
  59. package/src/main/heapSnapshot.cjs +1 -1
  60. package/src/main/historyAggregator.cjs +2 -2
  61. package/src/main/index.cjs +44 -132
  62. package/src/main/ipcSchemas.cjs +10 -10
  63. package/src/main/lib/CLAUDE.md +48 -0
  64. package/src/main/lib/activeSessions.cjs +1 -1
  65. package/src/main/lib/classifyPromptTicket.cjs +2 -2
  66. package/src/main/lib/{agentModelResolve.cjs → claude/agentModelResolve.cjs} +7 -7
  67. package/src/main/lib/{claudeBin.cjs → claude/claudeBin.cjs} +4 -4
  68. package/src/main/lib/{effectiveModelInfo.cjs → claude/effectiveModelInfo.cjs} +9 -9
  69. package/src/main/lib/{modelCatalog.cjs → claude/modelCatalog.cjs} +3 -3
  70. package/src/main/lib/{rateLimitDetect.cjs → claude/rateLimitDetect.cjs} +1 -1
  71. package/src/main/lib/{rateLimitWindow.cjs → claude/rateLimitWindow.cjs} +1 -1
  72. package/src/main/lib/{runClaudeP.cjs → claude/runClaudeP.cjs} +1 -1
  73. package/src/main/lib/credentials.cjs +3 -3
  74. package/src/main/lib/cwdClassify.cjs +1 -1
  75. package/src/main/lib/{activeIndexMerge.cjs → epic/activeIndexMerge.cjs} +3 -3
  76. package/src/main/lib/{activeIndexRebuild.cjs → epic/activeIndexRebuild.cjs} +1 -1
  77. package/src/main/lib/{delegationReadiness.cjs → epic/delegationReadiness.cjs} +21 -29
  78. package/src/main/lib/{epicContextDigest.cjs → epic/epicContextDigest.cjs} +1 -1
  79. package/src/main/lib/{epicDelegationStats.cjs → epic/epicDelegationStats.cjs} +2 -2
  80. package/src/main/lib/{epicMint.cjs → epic/epicMint.cjs} +11 -11
  81. package/src/main/lib/{epicSpawnPlan.cjs → epic/epicSpawnPlan.cjs} +3 -3
  82. package/src/main/lib/{epicStatusMirror.cjs → epic/epicStatusMirror.cjs} +2 -2
  83. package/src/main/lib/{epicTranscriptDiagnostic.cjs → epic/epicTranscriptDiagnostic.cjs} +2 -2
  84. package/src/main/lib/{epicTranscriptPath.cjs → epic/epicTranscriptPath.cjs} +2 -2
  85. package/src/main/lib/{epicValidationHook.cjs → epic/epicValidationHook.cjs} +3 -3
  86. package/src/main/lib/{epicWorktreeBoot.cjs → epic/epicWorktreeBoot.cjs} +1 -1
  87. package/src/main/lib/{epicWorktreeMerge.cjs → epic/epicWorktreeMerge.cjs} +3 -3
  88. package/src/main/lib/{epicWorktreeMint.cjs → epic/epicWorktreeMint.cjs} +3 -3
  89. package/src/main/lib/{epicWorktreeProjectConfig.cjs → epic/epicWorktreeProjectConfig.cjs} +4 -4
  90. package/src/main/lib/{promptSessionsCreateEpic.cjs → epic/promptSessionsCreateEpic.cjs} +4 -4
  91. package/src/main/lib/{crossProjectFeedback.cjs → feedback-remote/crossProjectFeedback.cjs} +7 -7
  92. package/src/main/lib/{customerFeedbackClient.cjs → feedback-remote/customerFeedbackClient.cjs} +3 -3
  93. package/src/main/lib/{customerFeedbackInbox.cjs → feedback-remote/customerFeedbackInbox.cjs} +2 -2
  94. package/src/main/lib/{customerFeedbackIpc.cjs → feedback-remote/customerFeedbackIpc.cjs} +1 -1
  95. package/src/main/lib/{localAdminHttp.cjs → feedback-remote/localAdminHttp.cjs} +2 -2
  96. package/src/main/lib/{macroAdminRoutes.cjs → feedback-remote/macroAdminRoutes.cjs} +1 -1
  97. package/src/main/lib/{projectHomeAdminRoutes.cjs → feedback-remote/projectHomeAdminRoutes.cjs} +4 -4
  98. package/src/main/lib/{relayClientAuth.cjs → feedback-remote/relayClientAuth.cjs} +2 -2
  99. package/src/main/lib/{remoteClientSessions.cjs → feedback-remote/remoteClientSessions.cjs} +1 -1
  100. package/src/main/lib/{gitWorktree.cjs → git/gitWorktree.cjs} +7 -7
  101. package/src/main/lib/historyRollup.cjs +1 -1
  102. package/src/main/lib/importReferences.cjs +1 -1
  103. package/src/main/lib/machineProfile.cjs +1 -1
  104. package/src/main/lib/openExternalApp.cjs +1 -1
  105. package/src/main/lib/opsOwnership.cjs +3 -3
  106. package/src/main/lib/{agentPersonaSchema.cjs → persona-macro/agentPersonaSchema.cjs} +2 -2
  107. package/src/main/lib/{macroLibrary.cjs → persona-macro/macroLibrary.cjs} +5 -5
  108. package/src/main/lib/{personaMerge.cjs → persona-macro/personaMerge.cjs} +4 -4
  109. package/src/main/lib/{prdAdminRoutes.cjs → prd/prdAdminRoutes.cjs} +7 -7
  110. package/src/main/lib/{prdAgentType.cjs → prd/prdAgentType.cjs} +3 -3
  111. package/src/main/lib/{prdCreate.cjs → prd/prdCreate.cjs} +12 -12
  112. package/src/main/lib/{prdDeclaredPaths.cjs → prd/prdDeclaredPaths.cjs} +1 -1
  113. package/src/main/lib/{prdGateFiles.cjs → prd/prdGateFiles.cjs} +1 -1
  114. package/src/main/lib/{prdLocations.cjs → prd/prdLocations.cjs} +2 -2
  115. package/src/main/lib/{prdMigration.cjs → prd/prdMigration.cjs} +3 -3
  116. package/src/main/lib/prereqs.cjs +2 -2
  117. package/src/main/lib/{instanceLock.cjs → process/instanceLock.cjs} +4 -11
  118. package/src/main/lib/{pidAlive.cjs → process/pidAlive.cjs} +1 -1
  119. package/src/main/lib/{procName.cjs → process/procName.cjs} +2 -2
  120. package/src/main/lib/{timeoutShim.cjs → process/timeoutShim.cjs} +1 -1
  121. package/src/main/lib/projectRootResolve.cjs +1 -1
  122. package/src/main/lib/promptSessionSchema.cjs +1 -1
  123. package/src/main/lib/{definitionOfDone.cjs → scheduler/definitionOfDone.cjs} +6 -12
  124. package/src/main/lib/{fixChainDepth.cjs → scheduler/fixChainDepth.cjs} +1 -1
  125. package/src/main/lib/{jobDirtFilter.cjs → scheduler/jobDirtFilter.cjs} +1 -1
  126. package/src/main/lib/{jobSupervisorRecord.cjs → scheduler/jobSupervisorRecord.cjs} +1 -1
  127. package/src/main/lib/{launchFailure.cjs → scheduler/launchFailure.cjs} +2 -2
  128. package/src/main/lib/{queueStore.cjs → scheduler/queueStore.cjs} +6 -6
  129. package/src/main/lib/{quietMachineLease.cjs → scheduler/quietMachineLease.cjs} +1 -1
  130. package/src/main/lib/{rcaReport.cjs → scheduler/rcaReport.cjs} +4 -4
  131. package/src/main/lib/{reaperHelpers.cjs → scheduler/reaperHelpers.cjs} +3 -3
  132. package/src/main/lib/{reservationExpiry.cjs → scheduler/reservationExpiry.cjs} +4 -11
  133. package/src/main/lib/{reviewNotice.cjs → scheduler/reviewNotice.cjs} +1 -1
  134. package/src/main/lib/{scheduleJobTransitions.cjs → scheduler/scheduleJobTransitions.cjs} +3 -3
  135. package/src/main/lib/{schedulerBatch.cjs → scheduler/schedulerBatch.cjs} +2 -2
  136. package/src/main/lib/{schedulerConfig.cjs → scheduler/schedulerConfig.cjs} +1 -1
  137. package/src/main/lib/{schedulerRuntimeState.cjs → scheduler/schedulerRuntimeState.cjs} +1 -1
  138. package/src/main/lib/{sessionSlots.cjs → scheduler/sessionSlots.cjs} +2 -2
  139. package/src/main/lib/{upgradeDrain.cjs → scheduler/upgradeDrain.cjs} +8 -11
  140. package/src/main/lib/{watchdogHelpers.cjs → scheduler/watchdogHelpers.cjs} +5 -19
  141. package/src/main/lib/{auditLog.cjs → telemetry/auditLog.cjs} +1 -1
  142. package/src/main/lib/{opsErrorLog.cjs → telemetry/opsErrorLog.cjs} +4 -4
  143. package/src/main/lib/{telemetryBacklog.cjs → telemetry/telemetryBacklog.cjs} +4 -4
  144. package/src/main/lib/{telemetryBoot.cjs → telemetry/telemetryBoot.cjs} +1 -1
  145. package/src/main/lib/{telemetryClient.cjs → telemetry/telemetryClient.cjs} +4 -4
  146. package/src/main/lib/{telemetrySettings.cjs → telemetry/telemetrySettings.cjs} +1 -1
  147. package/src/main/logs.cjs +1 -1
  148. package/src/main/mcpStatus.cjs +2 -2
  149. package/src/main/memoryAggregate.cjs +3 -3
  150. package/src/main/memoryTool.cjs +1 -1
  151. package/src/main/otel.cjs +2 -2
  152. package/src/main/pluginInstall.cjs +29 -3
  153. package/src/main/projectBrief.cjs +6 -6
  154. package/src/main/projectPages.cjs +1 -1
  155. package/src/main/promptSessionEvents.cjs +2 -2
  156. package/src/main/pty.cjs +7 -7
  157. package/src/main/queueOps.cjs +6 -6
  158. package/src/main/remoteClient.cjs +4 -4
  159. package/src/main/remoteClientIpc.cjs +1 -1
  160. package/src/main/runVerify.cjs +1 -1
  161. package/src/main/scheduler/CLAUDE.md +26 -0
  162. package/src/main/scheduler/budget.cjs +576 -0
  163. package/src/main/scheduler/gitTreeGuard.cjs +672 -0
  164. package/src/main/scheduler/prdParser.cjs +4 -4
  165. package/src/main/scheduler/prdPaths.cjs +487 -0
  166. package/src/main/scheduler/reviewLadder.cjs +1011 -0
  167. package/src/main/scheduler/starvation.cjs +390 -0
  168. package/src/main/scheduler.cjs +289 -3311
  169. package/src/main/seedAgentPersonas.cjs +5 -5
  170. package/src/main/seedDevPlugin.cjs +70 -3
  171. package/src/main/seedSchedulerMcp.cjs +2 -2
  172. package/src/main/supervisor.cjs +5 -5
  173. package/src/main/telemetryIpc.cjs +51 -0
  174. package/src/main/transcripts.cjs +3 -3
  175. package/src/main/updaterIpc.cjs +1 -1
  176. package/src/main/usage.cjs +1 -1
  177. package/src/main/watchers.cjs +1 -1
  178. package/src/main/webRemote.cjs +7 -7
  179. package/src/preload/api.d.ts +8 -8
  180. package/src/preload/index.cjs +3 -3
  181. package/dist/assets/RemoteMachines-CWnKbFHc.js +0 -3
  182. package/dist/assets/SystemPrompt-DxeNofL9.js +0 -1
  183. package/dist/assets/TagLibrary-QOAs7ISO.js +0 -1
  184. package/dist/assets/Toggle-Ba_f7q_m.js +0 -1
  185. package/plugins/session-manager-dev/skills/blog-for-project-feature/0-select/SKILL.md +0 -39
  186. package/plugins/session-manager-dev/skills/blog-for-project-feature/1-research/SKILL.md +0 -37
  187. package/plugins/session-manager-dev/skills/blog-for-project-feature/2-storyboard/SKILL.md +0 -64
  188. package/plugins/session-manager-dev/skills/blog-for-project-feature/3-capture/SKILL.md +0 -52
  189. package/plugins/session-manager-dev/skills/blog-for-project-feature/4-draft-copy/SKILL.md +0 -37
  190. package/plugins/session-manager-dev/skills/blog-for-project-feature/5-illustrate/SKILL.md +0 -50
  191. package/plugins/session-manager-dev/skills/blog-for-project-feature/6-compose/SKILL.md +0 -51
  192. package/plugins/session-manager-dev/skills/blog-for-project-feature/7-review/SKILL.md +0 -54
  193. package/plugins/session-manager-dev/skills/blog-for-project-feature/SKILL.md +0 -144
  194. package/plugins/session-manager-dev/skills/blog-for-project-feature/feature.config.yaml +0 -90
  195. package/plugins/session-manager-dev/skills/discover-features/SKILL.md +0 -95
  196. package/plugins/session-manager-dev/skills/explain-to-me/SKILL.md +0 -205
  197. package/plugins/session-manager-dev/skills/explain-to-me/assets/style-reference.html +0 -163
  198. package/plugins/session-manager-dev/skills/find-opportunity/SKILL.md +0 -83
  199. package/plugins/session-manager-dev/skills/issue-address/0-select/0-fetch-pool/SKILL.md +0 -38
  200. package/plugins/session-manager-dev/skills/issue-address/0-select/1-reject-fixed/SKILL.md +0 -64
  201. package/plugins/session-manager-dev/skills/issue-address/0-select/2-reject-claimed/SKILL.md +0 -71
  202. package/plugins/session-manager-dev/skills/issue-address/0-select/3-rank-impact/SKILL.md +0 -39
  203. package/plugins/session-manager-dev/skills/issue-address/0-select/SKILL.md +0 -75
  204. package/plugins/session-manager-dev/skills/issue-address/1-confirm-open/SKILL.md +0 -39
  205. package/plugins/session-manager-dev/skills/issue-address/2-claim/SKILL.md +0 -57
  206. package/plugins/session-manager-dev/skills/issue-address/3-reproduce/SKILL.md +0 -40
  207. package/plugins/session-manager-dev/skills/issue-address/4-fix/SKILL.md +0 -33
  208. package/plugins/session-manager-dev/skills/issue-address/5-verify/SKILL.md +0 -40
  209. package/plugins/session-manager-dev/skills/issue-address/SKILL.md +0 -120
  210. package/plugins/session-manager-dev/skills/local-project-health/SKILL.md +0 -58
  211. package/plugins/session-manager-dev/skills/memory-sanitation/SKILL.md +0 -147
  212. package/plugins/session-manager-dev/skills/ops-sweep/SKILL.md +0 -137
  213. package/plugins/session-manager-dev/skills/pr-review-sweep/0-fetch/SKILL.md +0 -41
  214. package/plugins/session-manager-dev/skills/pr-review-sweep/1-classify/SKILL.md +0 -56
  215. package/plugins/session-manager-dev/skills/pr-review-sweep/2-check-fixed/SKILL.md +0 -49
  216. package/plugins/session-manager-dev/skills/pr-review-sweep/3-queue/SKILL.md +0 -57
  217. package/plugins/session-manager-dev/skills/pr-review-sweep/4-land-and-resolve/SKILL.md +0 -60
  218. package/plugins/session-manager-dev/skills/pr-review-sweep/SKILL.md +0 -124
  219. package/plugins/session-manager-dev/skills/pr-signal/SKILL.md +0 -67
  220. package/plugins/session-manager-dev/skills/project-status/SKILL.md +0 -243
  221. package/plugins/session-manager-dev/skills/requesting-code-review/SKILL.md +0 -98
  222. package/plugins/session-manager-dev/skills/requesting-code-review/code-reviewer.md +0 -146
  223. /package/src/main/lib/{agentEffortResolve.cjs → claude/agentEffortResolve.cjs} +0 -0
  224. /package/src/main/lib/{claudeCliCaps.cjs → claude/claudeCliCaps.cjs} +0 -0
  225. /package/src/main/lib/{usageCircuit.cjs → claude/usageCircuit.cjs} +0 -0
  226. /package/src/main/lib/{epicSpawnCwd.cjs → epic/epicSpawnCwd.cjs} +0 -0
  227. /package/src/main/lib/{customerFeedbackAdminRoutes.cjs → feedback-remote/customerFeedbackAdminRoutes.cjs} +0 -0
  228. /package/src/main/lib/{remoteE2eCrypto.cjs → feedback-remote/remoteE2eCrypto.cjs} +0 -0
  229. /package/src/main/lib/{branchSweep.cjs → git/branchSweep.cjs} +0 -0
  230. /package/src/main/lib/{gitExec.cjs → git/gitExec.cjs} +0 -0
  231. /package/src/main/lib/{jobWorktree.cjs → git/jobWorktree.cjs} +0 -0
  232. /package/src/main/lib/{jobWorktreeBootLive.cjs → git/jobWorktreeBootLive.cjs} +0 -0
  233. /package/src/main/lib/{jobWorktreeTerminalOrphanLive.cjs → git/jobWorktreeTerminalOrphanLive.cjs} +0 -0
  234. /package/src/main/lib/{builtinMacros.cjs → persona-macro/builtinMacros.cjs} +0 -0
  235. /package/src/main/lib/{personaImportHealth.cjs → persona-macro/personaImportHealth.cjs} +0 -0
  236. /package/src/main/lib/{shippedPersonaSeeds.cjs → persona-macro/shippedPersonaSeeds.cjs} +0 -0
  237. /package/src/main/lib/{depSlugResolve.cjs → prd/depSlugResolve.cjs} +0 -0
  238. /package/src/main/lib/{planValidator.cjs → prd/planValidator.cjs} +0 -0
  239. /package/src/main/lib/{prdAuthoringSeed.cjs → prd/prdAuthoringSeed.cjs} +0 -0
  240. /package/src/main/lib/{prdDisposition.cjs → prd/prdDisposition.cjs} +0 -0
  241. /package/src/main/lib/{prdFrontmatter.cjs → prd/prdFrontmatter.cjs} +0 -0
  242. /package/src/main/lib/{prdSizing.cjs → prd/prdSizing.cjs} +0 -0
  243. /package/src/main/lib/{workTypeLibrary.cjs → prd/workTypeLibrary.cjs} +0 -0
  244. /package/src/main/lib/{childWithLog.cjs → process/childWithLog.cjs} +0 -0
  245. /package/src/main/lib/{cleanEnv.cjs → process/cleanEnv.cjs} +0 -0
  246. /package/src/main/lib/{defaultShell.cjs → process/defaultShell.cjs} +0 -0
  247. /package/src/main/lib/{killTree.cjs → process/killTree.cjs} +0 -0
  248. /package/src/main/lib/{loginShellPath.cjs → process/loginShellPath.cjs} +0 -0
  249. /package/src/main/lib/{procIdentity.cjs → process/procIdentity.cjs} +0 -0
  250. /package/src/main/lib/{singleInstanceGuard.cjs → process/singleInstanceGuard.cjs} +0 -0
  251. /package/src/main/lib/{smProcNames.cjs → process/smProcNames.cjs} +0 -0
  252. /package/src/main/lib/{timeoutShimScript.cjs → process/timeoutShimScript.cjs} +0 -0
  253. /package/src/main/lib/{winSpawn.cjs → process/winSpawn.cjs} +0 -0
  254. /package/src/main/lib/{adoptedRunSupervisor.cjs → scheduler/adoptedRunSupervisor.cjs} +0 -0
  255. /package/src/main/lib/{dispatchLoop.cjs → scheduler/dispatchLoop.cjs} +0 -0
  256. /package/src/main/lib/{dodDrainHook.cjs → scheduler/dodDrainHook.cjs} +0 -0
  257. /package/src/main/lib/{fixPlanSlug.cjs → scheduler/fixPlanSlug.cjs} +0 -0
  258. /package/src/main/lib/{gateAuthority.cjs → scheduler/gateAuthority.cjs} +0 -0
  259. /package/src/main/lib/{landedSinceRun.cjs → scheduler/landedSinceRun.cjs} +0 -0
  260. /package/src/main/lib/{loadGate.cjs → scheduler/loadGate.cjs} +0 -0
  261. /package/src/main/lib/{needsReviewLedger.cjs → scheduler/needsReviewLedger.cjs} +0 -0
  262. /package/src/main/lib/{queueHealth.cjs → scheduler/queueHealth.cjs} +0 -0
  263. /package/src/main/lib/{queueHistory.cjs → scheduler/queueHistory.cjs} +0 -0
  264. /package/src/main/lib/{runLogRetention.cjs → scheduler/runLogRetention.cjs} +0 -0
  265. /package/src/main/lib/{scheduleJobSchema.cjs → scheduler/scheduleJobSchema.cjs} +0 -0
  266. /package/src/main/lib/{schedulerPaths.cjs → scheduler/schedulerPaths.cjs} +0 -0
  267. /package/src/main/lib/{terminalRunOutcome.cjs → scheduler/terminalRunOutcome.cjs} +0 -0
  268. /package/src/main/lib/{validationSentinels.cjs → scheduler/validationSentinels.cjs} +0 -0
  269. /package/src/main/lib/{crashTelemetry.cjs → telemetry/crashTelemetry.cjs} +0 -0
  270. /package/src/main/lib/{otlpTraceExporter.cjs → telemetry/otlpTraceExporter.cjs} +0 -0
  271. /package/src/main/lib/{telemetryConsent.cjs → telemetry/telemetryConsent.cjs} +0 -0
  272. /package/src/main/lib/{telemetryCounters.cjs → telemetry/telemetryCounters.cjs} +0 -0
  273. /package/src/main/lib/{atomicFs.cjs → util/atomicFs.cjs} +0 -0
  274. /package/src/main/lib/{encodeCwd.cjs → util/encodeCwd.cjs} +0 -0
  275. /package/src/main/lib/{expandHome.cjs → util/expandHome.cjs} +0 -0
  276. /package/src/main/lib/{extractJson.cjs → util/extractJson.cjs} +0 -0
  277. /package/src/main/lib/{fileTail.cjs → util/fileTail.cjs} +0 -0
  278. /package/src/main/lib/{headTailBuffer.cjs → util/headTailBuffer.cjs} +0 -0
  279. /package/src/main/lib/{kebabCase.cjs → util/kebabCase.cjs} +0 -0
  280. /package/src/main/lib/{loopDelay.cjs → util/loopDelay.cjs} +0 -0
  281. /package/src/main/lib/{lruCache.cjs → util/lruCache.cjs} +0 -0
  282. /package/src/main/lib/{withTimeout.cjs → util/withTimeout.cjs} +0 -0
@@ -17,9 +17,9 @@ const fs = require('node:fs');
17
17
  const fsp = require('node:fs/promises');
18
18
  const os = require('node:os');
19
19
  const path = require('node:path');
20
- const { splitFrontmatter } = require('../lib/prdFrontmatter.cjs');
21
- const { deriveEpicIdFromPrdPath } = require('../lib/prdLocations.cjs');
22
- const { reportDanglingAgentTypeOnce } = require('../lib/prdAgentType.cjs');
20
+ const { splitFrontmatter } = require('../lib/prd/prdFrontmatter.cjs');
21
+ const { deriveEpicIdFromPrdPath } = require('../lib/prd/prdLocations.cjs');
22
+ const { reportDanglingAgentTypeOnce } = require('../lib/prd/prdAgentType.cjs');
23
23
 
24
24
  /**
25
25
  * Expand a PRD `cwd` value to an absolute path.
@@ -112,7 +112,7 @@ async function parsePrdRaw(filePath) {
112
112
  // buildInvestigationPrompt). Only a literal `true` opts in, matching
113
113
  // quietMachine's parse semantics below; reconcile() is the sole reader
114
114
  // that turns this (plus createdVia's absence) into a fix-plan verdict —
115
- // see lib/fixPlanSlug.cjs's classifyDiscoveredFixPlan.
115
+ // see lib/scheduler/fixPlanSlug.cjs's classifyDiscoveredFixPlan.
116
116
  isFixPlan: fm.isFixPlan === 'true',
117
117
  // WHO executes this PRD (persona name) — distinct from `tag` (work
118
118
  // type). Absent on every PRD authored before this field existed; the
@@ -0,0 +1,487 @@
1
+ /**
2
+ * scheduler/prdPaths.cjs — per-project PRD dir resolution (PRD 808): candidate/
3
+ * archived dir lookup, slug containment, archive-on-complete, boot migration and
4
+ * queue-backup sweep. Extracted verbatim from scheduler.cjs; internal calls go
5
+ * through module.exports (spy seam).
6
+ */
7
+ 'use strict';
8
+
9
+ const fs = require('node:fs');
10
+ const fsp = require('node:fs/promises');
11
+ const path = require('node:path');
12
+ const schedulerPaths = require('../lib/scheduler/schedulerPaths.cjs');
13
+ const logs = require('../logs.cjs');
14
+ const { SCHEDULE_SLUG_RE } = require('../ipcSchemas.cjs');
15
+ const { DEFAULT_PROJECT_CWD } = require('../lib/scheduler/schedulerBatch.cjs');
16
+ const { resolvePrdsDirs, resolveArchivedPrdsDirs, resolvePrdWriteDir, listEpicPrdDirs, listArchivedPrdDirs, ancestorEpicPrdDirs, ancestorEpicArchivedPrdDirs } = require('../lib/prd/prdLocations.cjs');
17
+ const { migratePrds, consolidateFlatPrds, legacyAdoptExistingPrds } = require('../lib/prd/prdMigration.cjs');
18
+ const { allProjectCwds } = require('../lib/activeSessions.cjs');
19
+
20
+ /**
21
+ * Every PRD-source directory currently in play: the legacy global dir (still
22
+ * searched so a not-yet-migrated file is never invisible) plus every active
23
+ * project's own PRDs dir. Used by scans that enumerate PRDs across projects
24
+ * (reconcile, list-prds, lint, rescan).
25
+ */
26
+ function candidatePrdsDirs() {
27
+ return [schedulerPaths.prdsRoot(), ...resolvePrdsDirs()];
28
+ }
29
+
30
+ /**
31
+ * Every `prds-archived/` dir across every project (the sibling-of-source
32
+ * layout archiveCompletedPrd writes into — see that function's comment).
33
+ * Used only by list-prds so the PRDs/Runs tabs can show an Epic's REAL
34
+ * historical PRD count, not just its currently-pending one. Deliberately
35
+ * excludes the flat legacy PRDS_ARCHIVE_DIR (manual `schedule:archive-prd`
36
+ * timestamped-subdir layout) — that path is for a different, unused-by-
37
+ * any-renderer manual archive flow, not Epic-scoped completion archiving.
38
+ */
39
+ function candidateArchivedPrdsDirs() {
40
+ return resolveArchivedPrdsDirs();
41
+ }
42
+
43
+ /**
44
+ * Resolve an archived PRD's real terminal outcome for `schedule:list-prds`:
45
+ * its live queue.json row (if the job hasn't aged out yet), else its
46
+ * history.jsonl row, else 'completed' — archiveCompletedPrd only ever
47
+ * archives a job whose effective status is 'completed' (a 'failed' job's
48
+ * PRD source stays in the live prds/ dir), so 'completed' is a safe
49
+ * default, not a guess; the live/history lookups are defensive in case
50
+ * that archiving invariant ever changes. Exported as a pure function (no
51
+ * IPC/fs) so this fallback chain is unit-testable without an Electron
52
+ * harness.
53
+ */
54
+ function resolveArchivedPrdStatus(slug, liveStatusBySlug, histBySlug) {
55
+ const resolved = liveStatusBySlug.get(slug) ?? histBySlug.get(slug)?.status ?? 'completed';
56
+ return resolved === 'failed' ? 'failed' : 'completed';
57
+ }
58
+
59
+ /** The PRD-source directory for a given job cwd (falls back to DEFAULT_PROJECT_CWD). */
60
+ function prdDirForCwd(cwd) {
61
+ return resolvePrdWriteDir(cwd || DEFAULT_PROJECT_CWD);
62
+ }
63
+
64
+ /**
65
+ * Absolute path to `<job's project PRDs dir>/<job.slug>.md`. Resolves to
66
+ * `resolvePrdWriteDir(cwd)` — the RETIRED flat
67
+ * `<cwd>/session-manager-operations/scheduler/prds/` dir, which holds only
68
+ * zero-byte `.reserved-NNN` stubs and no PRD source for any Epic-scoped PRD
69
+ * (see resolveNotifyPrd's and resolveVerifyPrdPath's doc comments, PRD 985
70
+ * and 991). Do NOT reach for this in new code — use `findPrdDir(slug)` (via
71
+ * `resolveVerifyPrdPath`/`resolveNotifyPrd`, or directly for a fresh path)
72
+ * instead. The one legitimate remaining caller is executeJob's `prdPath`
73
+ * (~line 2235), which only falls back to this AFTER `findPrdDir` already
74
+ * came up empty — a real miss, not a resolution shortcut — matching this
75
+ * function's genuinely-legacy semantics rather than a bug. (PRD 991 also
76
+ * removed the two verify call sites and buildInvestigationPrompt's
77
+ * originalBody read from this list — all three now go through
78
+ * resolveVerifyPrdPath instead.)
79
+ */
80
+ function prdPathForJob(job) {
81
+ return path.join(module.exports.prdDirForCwd(job && job.cwd), `${job && job.slug}.md`);
82
+ }
83
+
84
+ /**
85
+ * Absolute path to a job's archived-twin `<slug>.md`. Resolves to the first
86
+ * archive dir (across the flat legacy layout and every Epic's own sibling
87
+ * archive — see listArchivedPrdDirs) that actually contains the slug, falling
88
+ * back to the flat `prds-archived/<slug>.md` path when none do (this fallback
89
+ * is only ever used for its string value — logging/note text in
90
+ * prdArchivedSkipResult — never as an existence check).
91
+ */
92
+ function archivedPrdPathForJob(job) {
93
+ const slug = job && job.slug;
94
+ const flatPath = path.join(module.exports.prdDirForCwd(job && job.cwd), '..', 'prds-archived', `${slug}.md`);
95
+ for (const dir of listArchivedPrdDirs((job && job.cwd) || DEFAULT_PROJECT_CWD)) {
96
+ const candidate = module.exports.safeSlugPathIn(dir, slug);
97
+ if (candidate && fs.existsSync(candidate)) return candidate;
98
+ }
99
+ return flatPath;
100
+ }
101
+
102
+ /**
103
+ * True if a job's PRD has already been archived — in the flat legacy
104
+ * `prds-archived/` dir OR in the Epic-scoped sibling archive dir every new
105
+ * PRD actually lands in (archiveCompletedPrd archives into the source PRD's
106
+ * OWN parent's prds-archived/, which for an Epic PRD is inside that Epic).
107
+ * A queue entry whose PRD moved to either is stale — the work already
108
+ * shipped — not a genuine missing-PRD failure.
109
+ */
110
+ async function archivedTwinExists(job) {
111
+ const slug = job && job.slug;
112
+ const dirs = [...listArchivedPrdDirs((job && job.cwd) || DEFAULT_PROJECT_CWD)];
113
+ // Epic-in-non-git-parent case (see prdLocations.ancestorEpicDirs' header
114
+ // comment): job.cwd's own project never sees the ancestor Epic's archive
115
+ // dir via listArchivedPrdDirs, so also walk up from job.cwd for this
116
+ // job's EXACT epicId.
117
+ if (job && job.cwd && job.epicId) {
118
+ dirs.push(...ancestorEpicArchivedPrdDirs(job.cwd, job.epicId));
119
+ }
120
+ for (const dir of dirs) {
121
+ const candidate = module.exports.safeSlugPathIn(dir, slug);
122
+ if (!candidate) continue;
123
+ try {
124
+ await fsp.access(candidate);
125
+ return true;
126
+ } catch { /* not here — try the next archive dir */ }
127
+ }
128
+ return false;
129
+ }
130
+
131
+ /**
132
+ * Search every candidate PRD dir for `<slug>.md` (legacy dir first, then
133
+ * each active project's dir). Returns the containing dir, or null if the
134
+ * slug isn't found anywhere. Used by slug-only callers (IPC handlers) that
135
+ * don't have a job's cwd on hand.
136
+ */
137
+ async function findPrdDir(slug) {
138
+ for (const dir of module.exports.candidatePrdsDirs()) {
139
+ try {
140
+ await fsp.access(path.join(dir, `${slug}.md`));
141
+ return dir;
142
+ } catch { /* not here — try the next candidate dir */ }
143
+ }
144
+ return null;
145
+ }
146
+
147
+ /**
148
+ * Job-aware widening of findPrdDir: tries the ordinary global candidate
149
+ * search first (unchanged), then a stored absolute PRD path on the row (if
150
+ * the queue schema ever carries one — checked defensively), then walks up
151
+ * to 3 ancestor directories above `job.cwd` for that EXACT `job.epicId`'s
152
+ * `prds` dir (prdLocations.ancestorEpicPrdDirs).
153
+ *
154
+ * Fixes the incident where an Epic lives in a non-git PARENT folder P
155
+ * (P/session-manager-operations/scheduler/epics/<epicId>/prds/*.md) but its
156
+ * PRDs' frontmatter `cwd` is the sub-repo P/repo — P is never itself a
157
+ * tracked project cwd, so findPrdDir's candidatePrdsDirs() never visits it
158
+ * and the job was wrongly retired as `prd-missing`. epicId is matched
159
+ * exactly (never a glob); every path is built with path.join, never string
160
+ * concatenation with user input.
161
+ */
162
+ async function findPrdDirForJob(job) {
163
+ const viaGlobalSearch = await module.exports.findPrdDir(job && job.slug);
164
+ if (viaGlobalSearch) return viaGlobalSearch;
165
+ if (job && typeof job.prdPath === 'string' && job.prdPath) {
166
+ try {
167
+ await fsp.access(job.prdPath);
168
+ return path.dirname(job.prdPath);
169
+ } catch { /* stored path stale — fall through to the ancestor walk */ }
170
+ }
171
+ if (job && job.cwd && job.epicId) {
172
+ for (const dir of ancestorEpicPrdDirs(job.cwd, job.epicId)) {
173
+ try {
174
+ await fsp.access(path.join(dir, `${job.slug}.md`));
175
+ return dir;
176
+ } catch { /* not here — try the next ancestor */ }
177
+ }
178
+ }
179
+ return null;
180
+ }
181
+
182
+ /**
183
+ * Resolve `<dir>/<slug>.md` for a directory already known to contain (or be
184
+ * about to receive) the slug, and enforce path containment. Returns the
185
+ * absolute path on success, null on slug-escape attempts. The zod schema
186
+ * for slugs already blocks `..` because the SLUG_RE excludes `/`, but
187
+ * defense-in-depth: a second containment check after path.resolve costs
188
+ * nothing and catches future regex laxity.
189
+ */
190
+ function safeSlugPathIn(dir, slug) {
191
+ const resolved = path.resolve(path.join(dir, `${slug}.md`));
192
+ if (!resolved.startsWith(dir + path.sep)) return null;
193
+ return resolved;
194
+ }
195
+
196
+ /**
197
+ * Realpath of a PRDs dir for the symlink-containment re-checks below, or null
198
+ * when `dir` or any component between it and its project cwd (the parent of
199
+ * `session-manager-operations`) is a symlink. Why: comparing a file's realpath
200
+ * against the RAW dir rejected every PRD under a symlinked path (macOS tmpdir
201
+ * /var → /private/var), but trusting realpath(dir) alone lets a rogue job swap
202
+ * `epics/<id>/prds` for a symlink to e.g. ~/.claude and write there. Symlinks
203
+ * ABOVE the project cwd (a symlinked home or project folder) stay allowed.
204
+ */
205
+ async function realPrdsDir(dir) {
206
+ const marker = `${path.sep}session-manager-operations${path.sep}`;
207
+ const idx = (dir + path.sep).lastIndexOf(marker);
208
+ const stop = idx === -1 ? path.dirname(dir) : dir.slice(0, idx);
209
+ for (let p = dir; p.length > stop.length; p = path.dirname(p)) {
210
+ const st = await fsp.lstat(p).catch(() => null);
211
+ if (st && st.isSymbolicLink()) return null;
212
+ }
213
+ try { return await fsp.realpath(dir); } catch { return null; }
214
+ }
215
+
216
+ /**
217
+ * Locate an EXISTING `<slug>.md` across every candidate PRD dir and return
218
+ * its safe, containment-checked absolute path — or null if the slug isn't
219
+ * found anywhere (or would escape its containing dir).
220
+ */
221
+ async function safeSlugPath(slug) {
222
+ const dir = await module.exports.findPrdDir(slug);
223
+ if (!dir) return null;
224
+ return module.exports.safeSlugPathIn(dir, slug);
225
+ }
226
+
227
+ /**
228
+ * The two distinct failure modes safeSlugPath collapses into one nullable
229
+ * return (the defect this fixes — see the PRD that added this helper's
230
+ * Goal): a slug that fails SCHEDULE_SLUG_RE is a caller mistake ("invalid
231
+ * slug"), while a well-formed slug that exists in no candidate PRD dir is a
232
+ * lookup miss ("unknown slug") — an agent retrying the first as if it were
233
+ * the second (or vice versa) burns a turn on the wrong fix. Returns
234
+ * `{ ok: true, path }` or `{ ok: false, reason: 'invalid-slug' | 'not-found' }`.
235
+ * `cwd`, if given, narrows the search to that one project's own PRD dirs
236
+ * (prdDirForCwd + its Epic-scoped dirs — same pattern as getPrdParsed);
237
+ * omitted, it searches every candidate dir machine-wide via findPrdDir.
238
+ */
239
+ async function resolveSlugOrReason(slug, cwd) {
240
+ if (!SCHEDULE_SLUG_RE.test(slug)) return { ok: false, reason: 'invalid-slug' };
241
+ if (cwd) {
242
+ for (const dir of [module.exports.prdDirForCwd(cwd), ...listEpicPrdDirs(cwd)]) {
243
+ const p = module.exports.safeSlugPathIn(dir, slug);
244
+ if (!p) continue;
245
+ try {
246
+ await fsp.access(p);
247
+ return { ok: true, path: p };
248
+ } catch { /* not in this dir — try the next candidate */ }
249
+ }
250
+ return { ok: false, reason: 'not-found' };
251
+ }
252
+ const dir = await module.exports.findPrdDir(slug);
253
+ if (!dir) return { ok: false, reason: 'not-found' };
254
+ const p = module.exports.safeSlugPathIn(dir, slug);
255
+ if (!p) return { ok: false, reason: 'not-found' };
256
+ return { ok: true, path: p };
257
+ }
258
+
259
+ /**
260
+ * Shared row-match predicate for a reset-job request: slugs carry no cwd
261
+ * salt, so two different projects can independently produce the identical
262
+ * slug. Both the renderer-facing `schedule:reset-job` IPC handler and
263
+ * remote.resetJob (admin/MCP) go through this one predicate so a cwd-bearing
264
+ * caller always resets THAT project's job, never just any row matching the
265
+ * string.
266
+ */
267
+ function jobMatchesSlugAndCwd(j, slug, cwd) {
268
+ return j.slug === slug && (!cwd || j.cwd === cwd);
269
+ }
270
+
271
+ /** Actionable message for `resolveSlugOrReason`'s 'not-found' reason. */
272
+ function unknownSlugMessage(slug) {
273
+ return `unknown slug "${slug}": no PRD file with that name in any known project — call scheduler_list_prds (optionally with cwd) to see what exists`;
274
+ }
275
+
276
+ /**
277
+ * Move a completed job's `<slug>.md` out of its PRD dir into that dir's
278
+ * sibling `prds-archived/`, so a finished slug can't be re-fired by the
279
+ * scheduler. Sibling-of-source (not the hard-coded legacy PRDS_ARCHIVE_DIR)
280
+ * so a per-project PRD (`<cwd>/session-manager-operations/scheduler/prds/`)
281
+ * archives into that SAME project's `prds-archived/`, not the global legacy
282
+ * one — PRDS_ARCHIVE_DIR only happens to coincide with this for the legacy
283
+ * PRDS_DIR. Mirrors the `schedule:clear-queue` archive logic (same
284
+ * containment check). Non-throwing: a missing source file (already archived
285
+ * or already gone) is a silent no-op, and any other error is logged as a
286
+ * warning — an archive failure must never break job-completion bookkeeping.
287
+ */
288
+ async function archiveCompletedPrd(slug, cwd) {
289
+ try {
290
+ const srcDir = (await module.exports.findPrdDir(slug)) ?? module.exports.prdDirForCwd(cwd);
291
+ const src = module.exports.safeSlugPathIn(srcDir, slug);
292
+ if (!src) return;
293
+ const archiveDir = path.join(srcDir, '..', 'prds-archived');
294
+ await fsp.mkdir(archiveDir, { recursive: true });
295
+ const dst = path.join(archiveDir, `${slug}.md`);
296
+ await fsp.rename(src, dst);
297
+ } catch (e) {
298
+ if (e?.code !== 'ENOENT') {
299
+ logs.writeLine({ level: 'warn', scope: 'scheduler', message: 'archiveCompletedPrd: rename failed', meta: { slug, error: e?.message } });
300
+ }
301
+ }
302
+ }
303
+
304
+ /**
305
+ * One-time, idempotent migration (PRD 808): move every PRD source .md file
306
+ * still sitting in the legacy global PRDS_DIR into its own project's
307
+ * `<cwd>/session-manager-operations/scheduler/prds/`, based on that PRD's
308
+ * own frontmatter `cwd`. Runs at every boot — a no-op fs.readdir once
309
+ * nothing is left to move. Files migratePrds couldn't resolve (missing/
310
+ * unparseable cwd, cwd not on disk) are left in place and logged as a
311
+ * warning — never silently dropped — so a human can fix the frontmatter.
312
+ */
313
+ /**
314
+ * consolidateAllFlatPrds(cwds) — run consolidateFlatPrds() over every given
315
+ * project cwd, logging outcomes. Called from TWO places: once at boot (over
316
+ * every historical project, via runPrdMigration below) AND at the top of
317
+ * every reconcile() call (over every project reconcile itself would
318
+ * otherwise scan), BEFORE reconcile scans the flat dir for PRD sources. The
319
+ * reconcile()-level call is what makes "anything written to the retired flat
320
+ * prds/ dir is swept into prds-archived/ without being executed" actually
321
+ * true regardless of which of reconcile's several callers (tickQueue's poll,
322
+ * job completion, the schedule:rescan/schedule:adopt-prd IPC handlers,
323
+ * broadcast()'s coalescer, rescheduleTimer) triggers the pass — schedule:state
324
+ * no longer reconciles on read. A PRD dropped in the flat dir has no
325
+ * queue row yet at that point, so it is never in LIVE_JOB_STATUSES and this
326
+ * sweep archives it before reconcile can ever turn it into a pending job.
327
+ */
328
+ async function consolidateAllFlatPrds(cwds, skipCwds) {
329
+ for (const cwd of cwds) {
330
+ if (skipCwds?.has(cwd)) continue; // torn shard: its PRDs are not ours to touch this pass
331
+ try {
332
+ const c = await consolidateFlatPrds(cwd);
333
+ if (c.moved > 0) {
334
+ console.log(`[scheduler] flat-PRD consolidation: archived ${c.moved} file(s) in ${cwd}`);
335
+ }
336
+ for (const f of c.failed) {
337
+ logs.writeLine({
338
+ level: 'warn', scope: 'scheduler',
339
+ message: `flat-PRD consolidation: could not archive ${f.file}`,
340
+ meta: { cwd, reason: f.reason },
341
+ });
342
+ }
343
+ // Deliberately left behind because a live job still points at them
344
+ // (PRD 992). Logged so a permanently-stuck flat PRD is visible rather
345
+ // than looking like a clean consolidation.
346
+ for (const s of c.skipped ?? []) {
347
+ logs.writeLine({
348
+ level: 'info', scope: 'scheduler',
349
+ message: `flat-PRD consolidation: left ${s.file} in place`,
350
+ meta: { cwd, reason: s.reason },
351
+ });
352
+ }
353
+ } catch (e) {
354
+ logs.writeLine({ level: 'warn', scope: 'scheduler', message: 'flat-PRD consolidation failed', meta: { cwd, error: e?.message } });
355
+ }
356
+ }
357
+ }
358
+
359
+ async function runPrdMigration() {
360
+ let result;
361
+ try {
362
+ result = await migratePrds(schedulerPaths.prdsRoot());
363
+ } catch (e) {
364
+ logs.writeLine({ level: 'error', scope: 'scheduler', message: 'PRD migration failed', meta: { error: e?.message } });
365
+ return null;
366
+ }
367
+ console.log(`[scheduler] PRD migration: moved ${result.moved}, skipped ${result.skipped}`);
368
+ if (result.unresolved.length > 0) {
369
+ logs.writeLine({
370
+ level: 'warn',
371
+ scope: 'scheduler',
372
+ message: `PRD migration: ${result.unresolved.length} file(s) left in legacy dir`,
373
+ meta: { legacyDir: schedulerPaths.prdsRoot(), unresolved: result.unresolved },
374
+ });
375
+ for (const u of result.unresolved) {
376
+ console.warn(`[scheduler] PRD migration: left ${u.file} in legacy dir (${u.reason})`);
377
+ }
378
+ }
379
+
380
+ // Phase 2 (2026-07-31 domain-model decision): the flat per-project
381
+ // `scheduler/prds/` dir is retired — new PRDs are epic-scoped, and anything
382
+ // still sitting flat consolidates into `prds-archived/` for later special
383
+ // processing. Queue rows for moved files are reaped by the archived-twin
384
+ // retirement. Idempotent per project; failures are logged, never fatal.
385
+ // (This boot-time pass is redundant with the one reconcile() now also runs
386
+ // on every pass, but stays here so a fresh boot's very first log line
387
+ // still reports the initial sweep — see consolidateAllFlatPrds's own
388
+ // comment for why reconcile() is the load-bearing call site.)
389
+ //
390
+ // Deferred off scheduler.init()'s synchronous critical path: allProjectCwds()
391
+ // is a synchronous ~270ms directory scan, and awaiting it inline here
392
+ // competed with the renderer's first IPC round trips (schedule.state,
393
+ // billing.fetch, teams.list) for the event loop during boot. Dropping the
394
+ // await doesn't weaken the consolidation guarantee — consolidateAllFlatPrds
395
+ // also runs at the top of every reconcile() (see its own comment above),
396
+ // and a flat PRD can only ever execute via tickQueue, which always
397
+ // reconciles first, so nothing dropped in the flat dir can run before a
398
+ // reconcile() pass sweeps it regardless of whether this boot-time pass has
399
+ // finished yet.
400
+ setImmediate(() => {
401
+ module.exports.consolidateAllFlatPrds(allProjectCwds()).catch((e) => {
402
+ logs.writeLine({ level: 'warn', scope: 'scheduler', message: 'deferred flat-PRD consolidation failed', meta: { error: e?.message } });
403
+ });
404
+ });
405
+
406
+ // Rollout migration for the PRD-authoring-lockdown feature: stamp every
407
+ // pre-existing PRD as legacy-adopted BEFORE reconcile() ever runs its
408
+ // provenance gate against it. Must run every boot (idempotent, cheap
409
+ // scan-and-skip) rather than once — a project opened for the first time
410
+ // after this shipped still has pre-existing unstamped PRDs the very first
411
+ // time reconcile() sees them.
412
+ try {
413
+ const adopted = await legacyAdoptExistingPrds();
414
+ if (adopted.stamped > 0) {
415
+ console.log(`[scheduler] legacy-adopt migration: stamped ${adopted.stamped} pre-existing PRD(s) as createdVia=legacy-adopted`);
416
+ }
417
+ for (const f of adopted.failed) {
418
+ logs.writeLine({ level: 'warn', scope: 'scheduler', message: 'legacy-adopt migration: could not stamp PRD', meta: f });
419
+ }
420
+ } catch (e) {
421
+ logs.writeLine({ level: 'error', scope: 'scheduler', message: 'legacy-adopt migration failed', meta: { error: e?.message } });
422
+ }
423
+
424
+ return result;
425
+ }
426
+
427
+ // Matches only numbered timestamp backups (queue.json.bak-<epoch>), not the
428
+ // bare `queue.json.bak` single-shot copy some older code paths left behind.
429
+ const QUEUE_BAK_RE = /^queue\.json\.bak-\d+$/;
430
+ const QUEUE_BAK_KEEP = 5;
431
+
432
+ /**
433
+ * Sweeps accumulated queue.json.bak-<epoch> files down to the newest
434
+ * QUEUE_BAK_KEEP, deleting the rest. Runs at boot. `entries` comes from
435
+ * fs.readdir(ROOT), so every candidate path is already contained in ROOT.
436
+ */
437
+ async function sweepQueueBackups() {
438
+ let entries;
439
+ try {
440
+ entries = await fsp.readdir(schedulerPaths.scheduledPlansRoot());
441
+ } catch {
442
+ return;
443
+ }
444
+ const baks = entries.filter((f) => QUEUE_BAK_RE.test(f));
445
+ if (baks.length <= QUEUE_BAK_KEEP) return;
446
+
447
+ baks.sort((a, b) => {
448
+ const ta = Number(a.slice('queue.json.bak-'.length));
449
+ const tb = Number(b.slice('queue.json.bak-'.length));
450
+ return tb - ta; // newest (largest epoch) first
451
+ });
452
+ const toDelete = baks.slice(QUEUE_BAK_KEEP);
453
+ let removed = 0;
454
+ for (const f of toDelete) {
455
+ try {
456
+ await fsp.unlink(path.join(schedulerPaths.scheduledPlansRoot(), f));
457
+ removed++;
458
+ } catch (e) {
459
+ console.warn('[scheduler] backup sweep: unlink failed', f, e?.message);
460
+ }
461
+ }
462
+ if (removed > 0) {
463
+ console.log(`[scheduler] backup sweep: removed ${removed} old queue.json.bak-* file(s), kept ${QUEUE_BAK_KEEP}`);
464
+ }
465
+ }
466
+
467
+ module.exports = {
468
+ candidatePrdsDirs,
469
+ candidateArchivedPrdsDirs,
470
+ resolveArchivedPrdStatus,
471
+ prdDirForCwd,
472
+ prdPathForJob,
473
+ archivedPrdPathForJob,
474
+ archivedTwinExists,
475
+ findPrdDir,
476
+ findPrdDirForJob,
477
+ safeSlugPathIn,
478
+ realPrdsDir,
479
+ safeSlugPath,
480
+ resolveSlugOrReason,
481
+ jobMatchesSlugAndCwd,
482
+ unknownSlugMessage,
483
+ archiveCompletedPrd,
484
+ consolidateAllFlatPrds,
485
+ runPrdMigration,
486
+ sweepQueueBackups,
487
+ };