Authored by Claude Opus 4.7 (Claude Code). Session 5572d9a5-558d-4bea-b416-e31496c289c4.
FAIR-band: in-band [verify @ merge-gate]
Resolves #11860 (Sub 17)
Partial #11864 (Sub 20 — additive half; destructive half folds into Sub 18 #11862)
Summary
Combined PR per @tobiu's CI-efficiency framing on PR #11865: 1-sub-per-PR is right for risky work, pure CI tax for near-identical mechanical extractions. 4 new scheduling/<task>.mjs pure-function modules + their specs land in one CI round instead of four (4×7min integration = 28min saved).
Additive only. Does NOT touch Orchestrator, does NOT delete the old class files, does NOT modify PrimaryRepoSyncService. The wire-up shift (Orchestrator imports + call sites + old-class deletions + PrimaryRepoSync trim) is Sub 18 #11862's atomic scope (registry + collectDueCandidates + pickNextCandidate + CadenceEngine correction). Keeping this PR additive reduces risk + matches Sub 18's planned wire-up scope.
Evidence: L1 (29/29 specs pass across all 6 scheduling/ files — 4 new + 2 from Subs 15/16) → L1 required for pure-function extraction (no behavioral change at orchestrator surface; new files not wired in this PR).
New files (4 source + 4 spec)
ai/daemons/orchestrator/scheduling/goldenPath.mjs (Sub 17 — pure interval projection)
ai/daemons/orchestrator/scheduling/summary.mjs (Sub 20 — buildSummaryTrigger + getDueTask with sunset-handover priority + onSuccess hook for mark-read)
ai/daemons/orchestrator/scheduling/backup.mjs (Sub 20 — buildBackupTrigger + getDueTask)
ai/daemons/orchestrator/scheduling/primaryDevSync.mjs (Sub 20 — buildPrimaryRepoSyncTrigger + getDueTask with enabled-gate)
Sub-15 / Sub-16 KISS lessons applied from the start
- Pure functions (no class extends Base, no singleton: true, no
Neo.setupClass, no Neo.gatekeep)
- One JSDoc block per function (no file-level + function-level split)
- No decay-prone references (no
v13-path.md:N, no section labels like D3.1, no neighboring-file method names)
- Specs don't import Neo bootstrap (no module-load gatekeep call requiring
globalThis.Neo)
Earned vs not-earned buildTrigger separate exports
summary.mjs IS the one earning the buildTrigger + getDueTask split — getDueTask does meaningful extra work (calls getUnreadSunsetHandoversFn(db) + attaches onSuccess mark-read closure). backup.mjs + primaryDevSync.mjs follow the same shape for consistency + to keep the test-seam pattern (overridable getUnreadSunsetHandoversFn / markNodesAsReadFn). goldenPath.mjs has only getDueTask (no extra work to warrant the split — matches dream.mjs precedent).
Test Evidence
npm run test-unit -- test/playwright/unit/ai/daemons/orchestrator/scheduling/ → 29/29 pass (691ms)
Per-file:
dream.spec.mjs (existing from Sub 15) — 4 tests
swarmHeartbeat.spec.mjs (existing from Sub 16) — 4 tests
goldenPath.spec.mjs (new) — 4 tests (interval-elapsed / not-elapsed / disabled / missing-state)
summary.spec.mjs (new) — 6 tests (handover priority / periodic-sweep / disabled / getDueTask wrap / sunset-handover with onSuccess)
backup.spec.mjs (new) — 5 tests (buildTrigger boundaries + getDueTask state mapping)
primaryDevSync.spec.mjs (new) — 6 tests (enabled gate + interval / state mapping by PRIMARY_DEV_SYNC_TASK_NAME)
Sub 18 #11862 scope expanded to include the destructive half
Sub 18 (Round-2 closer) now also owns:
- Delete
services/SummarizationCoordinatorService.mjs + services/BackupCoordinatorService.mjs
- Trim
services/PrimaryRepoSyncService.mjs (remove buildPrimaryRepoSyncTrigger + getDueTask method; keep runTask + sync logic + class — the 803-LOC service has earned its Service shape via substantive runTask state/I/O)
- Update Orchestrator imports + call sites to consume
scheduling/<task>.mjs directly
- Remove now-stale
this.summarizationCoordinator + this.backupCoordinator instance fields
Post-Merge Validation
Deltas from tickets
- Sub 20 #11864 originally planned to do BOTH the additive (new scheduling/ files) AND destructive (delete old + Orchestrator update) work in one PR. Splitting: this PR does additive; Sub 18 absorbs destructive. Atomic risk surface per sub.
- File location uses
scheduling/<task>.mjs (no Service/Coordinator suffix) per @tobiu's KISS feedback on PR #11863. Sub 17 / Sub 20 ticket titles still use ...CoordinatorService naming — title naming churn deferred.
Depends on
Epic #11831 (parent). Sub 15 #11858 + Sub 16 #11859 merged (established the scheduling/ pattern).
Unblocks
Sub 18 #11862 atomic wire-up.
Authority
Discussion #11857 Cycle-3.5 GPT [GRADUATION_APPROVED] + operator architectural ratify + operator KISS feedback chain on PRs #11863 and #11865.
Per @tobiu's "you and me" instruction for Epic #11831 lane: no peer-review broadcast.
Authored by Claude Opus 4.7 (Claude Code). Session 5572d9a5-558d-4bea-b416-e31496c289c4.
FAIR-band: in-band [verify @ merge-gate]
Resolves #11860 (Sub 17) Partial #11864 (Sub 20 — additive half; destructive half folds into Sub 18 #11862)
Summary
Combined PR per @tobiu's CI-efficiency framing on PR #11865: 1-sub-per-PR is right for risky work, pure CI tax for near-identical mechanical extractions. 4 new
scheduling/<task>.mjspure-function modules + their specs land in one CI round instead of four (4×7min integration = 28min saved).Additive only. Does NOT touch Orchestrator, does NOT delete the old class files, does NOT modify
PrimaryRepoSyncService. The wire-up shift (Orchestrator imports + call sites + old-class deletions + PrimaryRepoSync trim) is Sub 18 #11862's atomic scope (registry + collectDueCandidates + pickNextCandidate + CadenceEngine correction). Keeping this PR additive reduces risk + matches Sub 18's planned wire-up scope.Evidence: L1 (29/29 specs pass across all 6 scheduling/ files — 4 new + 2 from Subs 15/16) → L1 required for pure-function extraction (no behavioral change at orchestrator surface; new files not wired in this PR).
New files (4 source + 4 spec)
ai/daemons/orchestrator/scheduling/goldenPath.mjs(Sub 17 — pure interval projection)ai/daemons/orchestrator/scheduling/summary.mjs(Sub 20 —buildSummaryTrigger+getDueTaskwith sunset-handover priority +onSuccesshook for mark-read)ai/daemons/orchestrator/scheduling/backup.mjs(Sub 20 —buildBackupTrigger+getDueTask)ai/daemons/orchestrator/scheduling/primaryDevSync.mjs(Sub 20 —buildPrimaryRepoSyncTrigger+getDueTaskwith enabled-gate)Sub-15 / Sub-16 KISS lessons applied from the start
Neo.setupClass, noNeo.gatekeep)v13-path.md:N, no section labels likeD3.1, no neighboring-file method names)globalThis.Neo)Earned vs not-earned
buildTriggerseparate exportssummary.mjsIS the one earning thebuildTrigger+getDueTasksplit —getDueTaskdoes meaningful extra work (callsgetUnreadSunsetHandoversFn(db)+ attachesonSuccessmark-read closure).backup.mjs+primaryDevSync.mjsfollow the same shape for consistency + to keep the test-seam pattern (overridablegetUnreadSunsetHandoversFn/markNodesAsReadFn).goldenPath.mjshas onlygetDueTask(no extra work to warrant the split — matchesdream.mjsprecedent).Test Evidence
npm run test-unit -- test/playwright/unit/ai/daemons/orchestrator/scheduling/→ 29/29 pass (691ms)Per-file:
dream.spec.mjs(existing from Sub 15) — 4 testsswarmHeartbeat.spec.mjs(existing from Sub 16) — 4 testsgoldenPath.spec.mjs(new) — 4 tests (interval-elapsed / not-elapsed / disabled / missing-state)summary.spec.mjs(new) — 6 tests (handover priority / periodic-sweep / disabled / getDueTask wrap / sunset-handover with onSuccess)backup.spec.mjs(new) — 5 tests (buildTrigger boundaries + getDueTask state mapping)primaryDevSync.spec.mjs(new) — 6 tests (enabled gate + interval / state mapping by PRIMARY_DEV_SYNC_TASK_NAME)Sub 18 #11862 scope expanded to include the destructive half
Sub 18 (Round-2 closer) now also owns:
services/SummarizationCoordinatorService.mjs+services/BackupCoordinatorService.mjsservices/PrimaryRepoSyncService.mjs(removebuildPrimaryRepoSyncTrigger+getDueTaskmethod; keeprunTask+ sync logic + class — the 803-LOC service has earned its Service shape via substantiverunTaskstate/I/O)scheduling/<task>.mjsdirectlythis.summarizationCoordinator+this.backupCoordinatorinstance fieldsPost-Merge Validation
Deltas from tickets
scheduling/<task>.mjs(noService/Coordinatorsuffix) per @tobiu's KISS feedback on PR #11863. Sub 17 / Sub 20 ticket titles still use...CoordinatorServicenaming — title naming churn deferred.Depends on
Epic #11831 (parent). Sub 15 #11858 + Sub 16 #11859 merged (established the scheduling/ pattern).
Unblocks
Sub 18 #11862 atomic wire-up.
Authority
Discussion #11857 Cycle-3.5 GPT
[GRADUATION_APPROVED]+ operator architectural ratify + operator KISS feedback chain on PRs #11863 and #11865.Per @tobiu's "you and me" instruction for Epic #11831 lane: no peer-review broadcast.