claude-code-session-manager 0.110.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.
- package/.claude-plugin/marketplace.json +2 -2
- package/dist/assets/{AgentLibrary-Cm6MrOnF.js → AgentLibrary-RwPvEgqm.js} +2 -2
- package/dist/assets/{Button-CGVncJ6a.js → Button-36j3PzYv.js} +1 -1
- package/dist/assets/{DataModel-C3hlMoa8.js → DataModel-DUhANo4l.js} +1 -1
- package/dist/assets/{History-CTyfGNKn.js → History-BeZzNjmv.js} +2 -2
- package/dist/assets/{Hooks-BXxCBibx.js → Hooks-iQ8845Hp.js} +3 -3
- package/dist/assets/{Library-aCs7zxAg.js → Library-DPE1h-1L.js} +6 -6
- package/dist/assets/{ListDetail-B9Hqk1_f.js → ListDetail-PStOuQAo.js} +1 -1
- package/dist/assets/{MarkdownEditor-Djfgp32k.js → MarkdownEditor-DPw-N2-m.js} +1 -1
- package/dist/assets/{McpServers-8HQC6QgR.js → McpServers-CUyRTNep.js} +2 -2
- package/dist/assets/{Memory-c4SVE3l2.js → Memory-bZ2xjBGy.js} +3 -3
- package/dist/assets/{Panel-BEQTwE4z.js → Panel-_VZ_uXNg.js} +1 -1
- package/dist/assets/{Permissions-BaYzgZtc.js → Permissions-PPRmfNbK.js} +3 -3
- package/dist/assets/{Plugins-DnXsVau7.js → Plugins-BFcvfvlx.js} +2 -2
- package/dist/assets/{ProvenanceBadge-CT2vWps1.js → ProvenanceBadge-B1WUknak.js} +1 -1
- package/dist/assets/RemoteMachines-D5V0lzgW.js +3 -0
- package/dist/assets/{SaveBar-DwcHt3iR.js → SaveBar-Dw64FVdM.js} +1 -1
- package/dist/assets/{Scheduler-D6Gcuo0M.js → Scheduler-C8s_pJ3I.js} +9 -9
- package/dist/assets/{ScopeSwitcher-CgvWDc9Y.js → ScopeSwitcher-D-nKDMAE.js} +1 -1
- package/dist/assets/{Settings-CJoogVAB.js → Settings-CQSrR4mO.js} +4 -4
- package/dist/assets/{SkillReferenceGraph-4fq01kxq.js → SkillReferenceGraph-BbZFMHim.js} +1 -1
- package/dist/assets/{Skills-BU4ZX4h4.js → Skills-Bzyrh6EQ.js} +1 -1
- package/dist/assets/SystemPrompt-DP6i3Dx0.js +1 -0
- package/dist/assets/TagLibrary-iip0C6Bl.js +1 -0
- package/dist/assets/{TiptapBody-DVyBsfsT.js → TiptapBody-Cr-hxX8L.js} +1 -1
- package/dist/assets/Toggle-7WpPzcZH.js +1 -0
- package/dist/assets/{ViewTabs-BLHjKUv-.js → ViewTabs-9lQCRwWJ.js} +1 -1
- package/dist/assets/{historyTotals-BXDd3EzP.js → historyTotals-BrFxw5J1.js} +1 -1
- package/dist/assets/{index-CuiU3I-8.js → index-CTBpf-26.js} +250 -249
- package/dist/assets/{settingsSchema-DD4bc5cT.js → settingsSchema-Dh5Kl7VO.js} +1 -1
- package/dist/assets/{useAgentsDirWatch-DMQ_XZtr.js → useAgentsDirWatch-CMpx0d04.js} +1 -1
- package/dist/assets/{useRemotePeers-C9NfteSD.js → useRemotePeers-CI2axd-L.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +3 -2
- package/plugins/CLAUDE.md +5 -8
- package/plugins/session-manager-dev/.claude-plugin/plugin.json +3 -6
- package/plugins/session-manager-dev/skills/builder/1-classify-and-bump/SKILL.md +2 -2
- package/plugins/session-manager-dev/skills/builder/SKILL.md +2 -3
- package/plugins/session-manager-dev/skills/develop/SKILL.md +1 -1
- package/plugins/session-manager-dev/skills/develop/standards.md +3 -3
- package/scripts/README.md +3 -3
- package/scripts/audit-ops-hygiene.cjs +4 -4
- package/scripts/hooks/guard-destructive-git.cjs +4 -4
- package/scripts/hooks/guard-inline-implementation.cjs +1 -1
- package/scripts/hooks/guard-prd-writes.cjs +2 -2
- package/scripts/hooks/lib/guard-destructive-git-policy.cjs +1 -1
- package/scripts/mint-epic.cjs +2 -2
- package/scripts/scheduler-mcp-server.cjs +4 -4
- package/src/main/agentLibrary.cjs +5 -5
- package/src/main/agentsIpc.cjs +76 -0
- package/src/main/build-info.json +4 -4
- package/src/main/chatRunner.cjs +12 -12
- package/src/main/config.cjs +2 -2
- package/src/main/crashDiagnostics.cjs +2 -2
- package/src/main/docEdit.cjs +5 -5
- package/src/main/files.cjs +2 -2
- package/src/main/git.cjs +1 -1
- package/src/main/health.cjs +20 -18
- package/src/main/heapSnapshot.cjs +1 -1
- package/src/main/historyAggregator.cjs +2 -2
- package/src/main/index.cjs +44 -132
- package/src/main/ipcSchemas.cjs +10 -10
- package/src/main/lib/CLAUDE.md +48 -0
- package/src/main/lib/activeSessions.cjs +1 -1
- package/src/main/lib/classifyPromptTicket.cjs +2 -2
- package/src/main/lib/{agentModelResolve.cjs → claude/agentModelResolve.cjs} +7 -7
- package/src/main/lib/{claudeBin.cjs → claude/claudeBin.cjs} +4 -4
- package/src/main/lib/{effectiveModelInfo.cjs → claude/effectiveModelInfo.cjs} +9 -9
- package/src/main/lib/{modelCatalog.cjs → claude/modelCatalog.cjs} +3 -3
- package/src/main/lib/{rateLimitDetect.cjs → claude/rateLimitDetect.cjs} +1 -1
- package/src/main/lib/{rateLimitWindow.cjs → claude/rateLimitWindow.cjs} +1 -1
- package/src/main/lib/{runClaudeP.cjs → claude/runClaudeP.cjs} +1 -1
- package/src/main/lib/credentials.cjs +3 -3
- package/src/main/lib/cwdClassify.cjs +1 -1
- package/src/main/lib/{activeIndexMerge.cjs → epic/activeIndexMerge.cjs} +3 -3
- package/src/main/lib/{activeIndexRebuild.cjs → epic/activeIndexRebuild.cjs} +1 -1
- package/src/main/lib/{delegationReadiness.cjs → epic/delegationReadiness.cjs} +21 -29
- package/src/main/lib/{epicContextDigest.cjs → epic/epicContextDigest.cjs} +1 -1
- package/src/main/lib/{epicDelegationStats.cjs → epic/epicDelegationStats.cjs} +2 -2
- package/src/main/lib/{epicMint.cjs → epic/epicMint.cjs} +11 -11
- package/src/main/lib/{epicSpawnPlan.cjs → epic/epicSpawnPlan.cjs} +3 -3
- package/src/main/lib/{epicStatusMirror.cjs → epic/epicStatusMirror.cjs} +2 -2
- package/src/main/lib/{epicTranscriptDiagnostic.cjs → epic/epicTranscriptDiagnostic.cjs} +2 -2
- package/src/main/lib/{epicTranscriptPath.cjs → epic/epicTranscriptPath.cjs} +2 -2
- package/src/main/lib/{epicValidationHook.cjs → epic/epicValidationHook.cjs} +3 -3
- package/src/main/lib/{epicWorktreeBoot.cjs → epic/epicWorktreeBoot.cjs} +1 -1
- package/src/main/lib/{epicWorktreeMerge.cjs → epic/epicWorktreeMerge.cjs} +3 -3
- package/src/main/lib/{epicWorktreeMint.cjs → epic/epicWorktreeMint.cjs} +3 -3
- package/src/main/lib/{epicWorktreeProjectConfig.cjs → epic/epicWorktreeProjectConfig.cjs} +4 -4
- package/src/main/lib/{promptSessionsCreateEpic.cjs → epic/promptSessionsCreateEpic.cjs} +4 -4
- package/src/main/lib/{crossProjectFeedback.cjs → feedback-remote/crossProjectFeedback.cjs} +7 -7
- package/src/main/lib/{customerFeedbackClient.cjs → feedback-remote/customerFeedbackClient.cjs} +3 -3
- package/src/main/lib/{customerFeedbackInbox.cjs → feedback-remote/customerFeedbackInbox.cjs} +2 -2
- package/src/main/lib/{customerFeedbackIpc.cjs → feedback-remote/customerFeedbackIpc.cjs} +1 -1
- package/src/main/lib/{localAdminHttp.cjs → feedback-remote/localAdminHttp.cjs} +2 -2
- package/src/main/lib/{macroAdminRoutes.cjs → feedback-remote/macroAdminRoutes.cjs} +1 -1
- package/src/main/lib/{projectHomeAdminRoutes.cjs → feedback-remote/projectHomeAdminRoutes.cjs} +4 -4
- package/src/main/lib/{relayClientAuth.cjs → feedback-remote/relayClientAuth.cjs} +2 -2
- package/src/main/lib/{remoteClientSessions.cjs → feedback-remote/remoteClientSessions.cjs} +1 -1
- package/src/main/lib/{gitWorktree.cjs → git/gitWorktree.cjs} +7 -7
- package/src/main/lib/historyRollup.cjs +1 -1
- package/src/main/lib/importReferences.cjs +1 -1
- package/src/main/lib/machineProfile.cjs +1 -1
- package/src/main/lib/openExternalApp.cjs +1 -1
- package/src/main/lib/opsOwnership.cjs +3 -3
- package/src/main/lib/{agentPersonaSchema.cjs → persona-macro/agentPersonaSchema.cjs} +2 -2
- package/src/main/lib/{builtinMacros.cjs → persona-macro/builtinMacros.cjs} +2 -2
- package/src/main/lib/{macroLibrary.cjs → persona-macro/macroLibrary.cjs} +5 -5
- package/src/main/lib/{personaMerge.cjs → persona-macro/personaMerge.cjs} +4 -4
- package/src/main/lib/{shippedPersonaSeeds.cjs → persona-macro/shippedPersonaSeeds.cjs} +11 -0
- package/src/main/lib/{prdAdminRoutes.cjs → prd/prdAdminRoutes.cjs} +7 -7
- package/src/main/lib/{prdAgentType.cjs → prd/prdAgentType.cjs} +3 -3
- package/src/main/lib/{prdCreate.cjs → prd/prdCreate.cjs} +12 -12
- package/src/main/lib/{prdDeclaredPaths.cjs → prd/prdDeclaredPaths.cjs} +1 -1
- package/src/main/lib/{prdGateFiles.cjs → prd/prdGateFiles.cjs} +1 -1
- package/src/main/lib/{prdLocations.cjs → prd/prdLocations.cjs} +2 -2
- package/src/main/lib/{prdMigration.cjs → prd/prdMigration.cjs} +3 -3
- package/src/main/lib/prereqs.cjs +2 -2
- package/src/main/lib/{instanceLock.cjs → process/instanceLock.cjs} +4 -11
- package/src/main/lib/{pidAlive.cjs → process/pidAlive.cjs} +1 -1
- package/src/main/lib/{procName.cjs → process/procName.cjs} +2 -2
- package/src/main/lib/{timeoutShim.cjs → process/timeoutShim.cjs} +1 -1
- package/src/main/lib/projectRootResolve.cjs +1 -1
- package/src/main/lib/promptSessionSchema.cjs +1 -1
- package/src/main/lib/{definitionOfDone.cjs → scheduler/definitionOfDone.cjs} +6 -12
- package/src/main/lib/{fixChainDepth.cjs → scheduler/fixChainDepth.cjs} +1 -1
- package/src/main/lib/{jobDirtFilter.cjs → scheduler/jobDirtFilter.cjs} +1 -1
- package/src/main/lib/{jobSupervisorRecord.cjs → scheduler/jobSupervisorRecord.cjs} +1 -1
- package/src/main/lib/{launchFailure.cjs → scheduler/launchFailure.cjs} +2 -2
- package/src/main/lib/{queueStore.cjs → scheduler/queueStore.cjs} +6 -6
- package/src/main/lib/{quietMachineLease.cjs → scheduler/quietMachineLease.cjs} +1 -1
- package/src/main/lib/{rcaReport.cjs → scheduler/rcaReport.cjs} +4 -4
- package/src/main/lib/{reaperHelpers.cjs → scheduler/reaperHelpers.cjs} +3 -3
- package/src/main/lib/{reservationExpiry.cjs → scheduler/reservationExpiry.cjs} +4 -11
- package/src/main/lib/{reviewNotice.cjs → scheduler/reviewNotice.cjs} +1 -1
- package/src/main/lib/{scheduleJobTransitions.cjs → scheduler/scheduleJobTransitions.cjs} +3 -3
- package/src/main/lib/{schedulerBatch.cjs → scheduler/schedulerBatch.cjs} +2 -2
- package/src/main/lib/{schedulerConfig.cjs → scheduler/schedulerConfig.cjs} +1 -1
- package/src/main/lib/{schedulerRuntimeState.cjs → scheduler/schedulerRuntimeState.cjs} +1 -1
- package/src/main/lib/{sessionSlots.cjs → scheduler/sessionSlots.cjs} +2 -2
- package/src/main/lib/{upgradeDrain.cjs → scheduler/upgradeDrain.cjs} +8 -11
- package/src/main/lib/{watchdogHelpers.cjs → scheduler/watchdogHelpers.cjs} +5 -19
- package/src/main/lib/{auditLog.cjs → telemetry/auditLog.cjs} +1 -1
- package/src/main/lib/{opsErrorLog.cjs → telemetry/opsErrorLog.cjs} +4 -4
- package/src/main/lib/{telemetryBacklog.cjs → telemetry/telemetryBacklog.cjs} +4 -4
- package/src/main/lib/{telemetryBoot.cjs → telemetry/telemetryBoot.cjs} +1 -1
- package/src/main/lib/{telemetryClient.cjs → telemetry/telemetryClient.cjs} +4 -4
- package/src/main/lib/{telemetrySettings.cjs → telemetry/telemetrySettings.cjs} +1 -1
- package/src/main/logs.cjs +1 -1
- package/src/main/mcpStatus.cjs +2 -2
- package/src/main/memoryAggregate.cjs +3 -3
- package/src/main/memoryTool.cjs +1 -1
- package/src/main/otel.cjs +2 -2
- package/src/main/pluginInstall.cjs +29 -3
- package/src/main/projectBrief.cjs +6 -6
- package/src/main/projectPages.cjs +1 -1
- package/src/main/promptSessionEvents.cjs +2 -2
- package/src/main/pty.cjs +7 -7
- package/src/main/queueOps.cjs +6 -6
- package/src/main/remoteClient.cjs +4 -4
- package/src/main/remoteClientIpc.cjs +1 -1
- package/src/main/runVerify.cjs +1 -1
- package/src/main/scheduler/CLAUDE.md +26 -0
- package/src/main/scheduler/budget.cjs +576 -0
- package/src/main/scheduler/gitTreeGuard.cjs +672 -0
- package/src/main/scheduler/prdParser.cjs +4 -4
- package/src/main/scheduler/prdPaths.cjs +487 -0
- package/src/main/scheduler/reviewLadder.cjs +1011 -0
- package/src/main/scheduler/starvation.cjs +390 -0
- package/src/main/scheduler.cjs +289 -3311
- package/src/main/seedAgentPersonas.cjs +5 -5
- package/src/main/seedDevPlugin.cjs +70 -3
- package/src/main/seedSchedulerMcp.cjs +2 -2
- package/src/main/supervisor.cjs +5 -5
- package/src/main/telemetryIpc.cjs +51 -0
- package/src/main/transcripts.cjs +3 -3
- package/src/main/updaterIpc.cjs +1 -1
- package/src/main/usage.cjs +1 -1
- package/src/main/watchers.cjs +1 -1
- package/src/main/webRemote.cjs +7 -7
- package/src/preload/api.d.ts +8 -8
- package/src/preload/index.cjs +3 -3
- package/src/seed/agents/demo-video-builder.md +62 -19
- package/dist/assets/RemoteMachines-DHJZfdJC.js +0 -3
- package/dist/assets/SystemPrompt-BCEyMSEq.js +0 -1
- package/dist/assets/TagLibrary-m3It8Oal.js +0 -1
- package/dist/assets/Toggle-In_AExPV.js +0 -1
- package/plugins/session-manager-dev/skills/blog-for-project-feature/0-select/SKILL.md +0 -39
- package/plugins/session-manager-dev/skills/blog-for-project-feature/1-research/SKILL.md +0 -37
- package/plugins/session-manager-dev/skills/blog-for-project-feature/2-storyboard/SKILL.md +0 -64
- package/plugins/session-manager-dev/skills/blog-for-project-feature/3-capture/SKILL.md +0 -52
- package/plugins/session-manager-dev/skills/blog-for-project-feature/4-draft-copy/SKILL.md +0 -37
- package/plugins/session-manager-dev/skills/blog-for-project-feature/5-illustrate/SKILL.md +0 -50
- package/plugins/session-manager-dev/skills/blog-for-project-feature/6-compose/SKILL.md +0 -51
- package/plugins/session-manager-dev/skills/blog-for-project-feature/7-review/SKILL.md +0 -54
- package/plugins/session-manager-dev/skills/blog-for-project-feature/SKILL.md +0 -144
- package/plugins/session-manager-dev/skills/blog-for-project-feature/feature.config.yaml +0 -90
- package/plugins/session-manager-dev/skills/discover-features/SKILL.md +0 -95
- package/plugins/session-manager-dev/skills/explain-to-me/SKILL.md +0 -205
- package/plugins/session-manager-dev/skills/explain-to-me/assets/style-reference.html +0 -163
- package/plugins/session-manager-dev/skills/find-opportunity/SKILL.md +0 -83
- package/plugins/session-manager-dev/skills/issue-address/0-select/0-fetch-pool/SKILL.md +0 -38
- package/plugins/session-manager-dev/skills/issue-address/0-select/1-reject-fixed/SKILL.md +0 -64
- package/plugins/session-manager-dev/skills/issue-address/0-select/2-reject-claimed/SKILL.md +0 -71
- package/plugins/session-manager-dev/skills/issue-address/0-select/3-rank-impact/SKILL.md +0 -39
- package/plugins/session-manager-dev/skills/issue-address/0-select/SKILL.md +0 -75
- package/plugins/session-manager-dev/skills/issue-address/1-confirm-open/SKILL.md +0 -39
- package/plugins/session-manager-dev/skills/issue-address/2-claim/SKILL.md +0 -57
- package/plugins/session-manager-dev/skills/issue-address/3-reproduce/SKILL.md +0 -40
- package/plugins/session-manager-dev/skills/issue-address/4-fix/SKILL.md +0 -33
- package/plugins/session-manager-dev/skills/issue-address/5-verify/SKILL.md +0 -40
- package/plugins/session-manager-dev/skills/issue-address/SKILL.md +0 -120
- package/plugins/session-manager-dev/skills/local-project-health/SKILL.md +0 -58
- package/plugins/session-manager-dev/skills/memory-sanitation/SKILL.md +0 -147
- package/plugins/session-manager-dev/skills/ops-sweep/SKILL.md +0 -137
- package/plugins/session-manager-dev/skills/pr-review-sweep/0-fetch/SKILL.md +0 -41
- package/plugins/session-manager-dev/skills/pr-review-sweep/1-classify/SKILL.md +0 -56
- package/plugins/session-manager-dev/skills/pr-review-sweep/2-check-fixed/SKILL.md +0 -49
- package/plugins/session-manager-dev/skills/pr-review-sweep/3-queue/SKILL.md +0 -57
- package/plugins/session-manager-dev/skills/pr-review-sweep/4-land-and-resolve/SKILL.md +0 -60
- package/plugins/session-manager-dev/skills/pr-review-sweep/SKILL.md +0 -124
- package/plugins/session-manager-dev/skills/pr-signal/SKILL.md +0 -67
- package/plugins/session-manager-dev/skills/project-status/SKILL.md +0 -243
- package/plugins/session-manager-dev/skills/requesting-code-review/SKILL.md +0 -98
- package/plugins/session-manager-dev/skills/requesting-code-review/code-reviewer.md +0 -146
- /package/src/main/lib/{agentEffortResolve.cjs → claude/agentEffortResolve.cjs} +0 -0
- /package/src/main/lib/{claudeCliCaps.cjs → claude/claudeCliCaps.cjs} +0 -0
- /package/src/main/lib/{usageCircuit.cjs → claude/usageCircuit.cjs} +0 -0
- /package/src/main/lib/{epicSpawnCwd.cjs → epic/epicSpawnCwd.cjs} +0 -0
- /package/src/main/lib/{customerFeedbackAdminRoutes.cjs → feedback-remote/customerFeedbackAdminRoutes.cjs} +0 -0
- /package/src/main/lib/{remoteE2eCrypto.cjs → feedback-remote/remoteE2eCrypto.cjs} +0 -0
- /package/src/main/lib/{branchSweep.cjs → git/branchSweep.cjs} +0 -0
- /package/src/main/lib/{gitExec.cjs → git/gitExec.cjs} +0 -0
- /package/src/main/lib/{jobWorktree.cjs → git/jobWorktree.cjs} +0 -0
- /package/src/main/lib/{jobWorktreeBootLive.cjs → git/jobWorktreeBootLive.cjs} +0 -0
- /package/src/main/lib/{jobWorktreeTerminalOrphanLive.cjs → git/jobWorktreeTerminalOrphanLive.cjs} +0 -0
- /package/src/main/lib/{personaImportHealth.cjs → persona-macro/personaImportHealth.cjs} +0 -0
- /package/src/main/lib/{depSlugResolve.cjs → prd/depSlugResolve.cjs} +0 -0
- /package/src/main/lib/{planValidator.cjs → prd/planValidator.cjs} +0 -0
- /package/src/main/lib/{prdAuthoringSeed.cjs → prd/prdAuthoringSeed.cjs} +0 -0
- /package/src/main/lib/{prdDisposition.cjs → prd/prdDisposition.cjs} +0 -0
- /package/src/main/lib/{prdFrontmatter.cjs → prd/prdFrontmatter.cjs} +0 -0
- /package/src/main/lib/{prdSizing.cjs → prd/prdSizing.cjs} +0 -0
- /package/src/main/lib/{workTypeLibrary.cjs → prd/workTypeLibrary.cjs} +0 -0
- /package/src/main/lib/{childWithLog.cjs → process/childWithLog.cjs} +0 -0
- /package/src/main/lib/{cleanEnv.cjs → process/cleanEnv.cjs} +0 -0
- /package/src/main/lib/{defaultShell.cjs → process/defaultShell.cjs} +0 -0
- /package/src/main/lib/{killTree.cjs → process/killTree.cjs} +0 -0
- /package/src/main/lib/{loginShellPath.cjs → process/loginShellPath.cjs} +0 -0
- /package/src/main/lib/{procIdentity.cjs → process/procIdentity.cjs} +0 -0
- /package/src/main/lib/{singleInstanceGuard.cjs → process/singleInstanceGuard.cjs} +0 -0
- /package/src/main/lib/{smProcNames.cjs → process/smProcNames.cjs} +0 -0
- /package/src/main/lib/{timeoutShimScript.cjs → process/timeoutShimScript.cjs} +0 -0
- /package/src/main/lib/{winSpawn.cjs → process/winSpawn.cjs} +0 -0
- /package/src/main/lib/{adoptedRunSupervisor.cjs → scheduler/adoptedRunSupervisor.cjs} +0 -0
- /package/src/main/lib/{dispatchLoop.cjs → scheduler/dispatchLoop.cjs} +0 -0
- /package/src/main/lib/{dodDrainHook.cjs → scheduler/dodDrainHook.cjs} +0 -0
- /package/src/main/lib/{fixPlanSlug.cjs → scheduler/fixPlanSlug.cjs} +0 -0
- /package/src/main/lib/{gateAuthority.cjs → scheduler/gateAuthority.cjs} +0 -0
- /package/src/main/lib/{landedSinceRun.cjs → scheduler/landedSinceRun.cjs} +0 -0
- /package/src/main/lib/{loadGate.cjs → scheduler/loadGate.cjs} +0 -0
- /package/src/main/lib/{needsReviewLedger.cjs → scheduler/needsReviewLedger.cjs} +0 -0
- /package/src/main/lib/{queueHealth.cjs → scheduler/queueHealth.cjs} +0 -0
- /package/src/main/lib/{queueHistory.cjs → scheduler/queueHistory.cjs} +0 -0
- /package/src/main/lib/{runLogRetention.cjs → scheduler/runLogRetention.cjs} +0 -0
- /package/src/main/lib/{scheduleJobSchema.cjs → scheduler/scheduleJobSchema.cjs} +0 -0
- /package/src/main/lib/{schedulerPaths.cjs → scheduler/schedulerPaths.cjs} +0 -0
- /package/src/main/lib/{terminalRunOutcome.cjs → scheduler/terminalRunOutcome.cjs} +0 -0
- /package/src/main/lib/{validationSentinels.cjs → scheduler/validationSentinels.cjs} +0 -0
- /package/src/main/lib/{crashTelemetry.cjs → telemetry/crashTelemetry.cjs} +0 -0
- /package/src/main/lib/{otlpTraceExporter.cjs → telemetry/otlpTraceExporter.cjs} +0 -0
- /package/src/main/lib/{telemetryConsent.cjs → telemetry/telemetryConsent.cjs} +0 -0
- /package/src/main/lib/{telemetryCounters.cjs → telemetry/telemetryCounters.cjs} +0 -0
- /package/src/main/lib/{atomicFs.cjs → util/atomicFs.cjs} +0 -0
- /package/src/main/lib/{encodeCwd.cjs → util/encodeCwd.cjs} +0 -0
- /package/src/main/lib/{expandHome.cjs → util/expandHome.cjs} +0 -0
- /package/src/main/lib/{extractJson.cjs → util/extractJson.cjs} +0 -0
- /package/src/main/lib/{fileTail.cjs → util/fileTail.cjs} +0 -0
- /package/src/main/lib/{headTailBuffer.cjs → util/headTailBuffer.cjs} +0 -0
- /package/src/main/lib/{kebabCase.cjs → util/kebabCase.cjs} +0 -0
- /package/src/main/lib/{loopDelay.cjs → util/loopDelay.cjs} +0 -0
- /package/src/main/lib/{lruCache.cjs → util/lruCache.cjs} +0 -0
- /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
|
+
};
|