Authored by Opus 4.8 (Claude Code). Session de8830c3-8d1b-47f8-90db-bd236c8b9bd2.
Resolves #12188
Resolves #12184
Related: #12186
Closed-item bucketing resolved closedAt → release over a sortedReleases list floored to syncStartDate (2025-01-01) and sorted ascending, so every pre-floor closed issue/PR collapsed into the oldest in-window release — a v8.1.0 catch-all holding 4,133 of 7,263 archived issues (56.9%). This fetches the full release history into the bucketing reference while keeping release-NOTES floored (the floor moves to syncNotes), so closed items bucket into the real release that shipped after they closed — with zero change to the on-disk notes set, its chunk layout, or the SSR route count. Separately, the milestone→version branch is now guarded by semver.valid, so descriptive milestones (#3286/#3287 carried titles like "neo.d.ts - Typescript definitions …") no longer become garbage version folders; they fall through to the same closedAt → release resolution.
FAIR-band: over-target — sole active implementer (@neo-gpt rate-limited, @neo-gemini benched per current project state) + operator-directed lane (epic #12186 P0).
Evidence: L1 (unit: full-history pagination, syncNotes floor, non-semver-milestone fall-through — 4 new tests, 206 green) → L2 required (AC: post-re-sync archive/issues/ has no v8.1.0 catch-all + no garbage folders). Residual: live re-sync + portal tickets-view verification [#12186].
Deltas from ticket (if any)
- Release-notes stay floored; only bucketing goes full.
sortedReleases (the bucketing reference) now spans the full history; this.releases is full too (so the fast-path cache reconstructs a full reference), but syncNotes floors writes to syncStartDate and indexes within the floored set. This keeps the portal Releases view + release-notes chunk layout byte-identical and avoids adding ~1,028 release SSR routes — a conscious scope boundary (full release-notes history is a separate, opt-in enhancement, not this fix).
maxReleases 1000 → 2000 (config.template) — the full history (~1,194 releases) exceeds the old cap; a cap below the total would silently drop the oldest releases. A loud WARN now fires if the cap is hit before history is exhausted (no silent truncation).
- Updated one existing ReleaseNotesSyncer spec: its release dates were 2024 (pre-floor); with the floor now in
syncNotes, in-window dates are required to exercise the "notes written" path. Production behavior is unchanged — fetchAndCacheReleases already filtered those pre-floor releases out before the fix, so they never reached disk.
Config template change (per mcp-config-template-change-guide)
- Changed key:
issueSync.maxReleases (1000 → 2000).
- Local
config.mjs follow-up (required): each clone's gitignored config.mjs overrides maxReleases independently — bump it to 2000 after merge so the full-history fetch isn't truncated. (Not committed; gitignored.)
- Restart: unnecessary — the syncer reads config at run time; the next
sync-github-workflow picks up the new value.
- Peer notification: A2A sent (the change affects sync behavior in other clones).
Test Evidence
npm run test-unit -- test/playwright/unit/ai/services/github-workflow/ → 206 passed (4 new, 0 regressions).
- New tests:
ReleaseNotesSyncer.spec: cold full-history fetch (proves the early-exit and the filter were removed — pre-floor releases survive in sortedReleases, pagination does not stop at the floor); syncNotes floor (proves notes stay floored + index within the floored set while sortedReleases is full).
IssueSyncer.spec + PullRequestSyncer.spec: a non-semver milestone falls through to the closedAt → release fallback (v9.0.0), not a title-derived folder. Each fails without the guard.
node --check on all touched source files; pre-commit whitespace/shorthand hooks green.
Post-Merge Validation
Authored by Opus 4.8 (Claude Code). Session de8830c3-8d1b-47f8-90db-bd236c8b9bd2.
Resolves #12188 Resolves #12184 Related: #12186
Closed-item bucketing resolved
closedAt → releaseover asortedReleaseslist floored tosyncStartDate(2025-01-01) and sorted ascending, so every pre-floor closed issue/PR collapsed into the oldest in-window release — a v8.1.0 catch-all holding 4,133 of 7,263 archived issues (56.9%). This fetches the full release history into the bucketing reference while keeping release-NOTES floored (the floor moves tosyncNotes), so closed items bucket into the real release that shipped after they closed — with zero change to the on-disk notes set, its chunk layout, or the SSR route count. Separately, the milestone→version branch is now guarded bysemver.valid, so descriptive milestones (#3286/#3287 carried titles like "neo.d.ts - Typescript definitions …") no longer become garbage version folders; they fall through to the sameclosedAt → releaseresolution.FAIR-band: over-target — sole active implementer (@neo-gpt rate-limited, @neo-gemini benched per current project state) + operator-directed lane (epic #12186 P0).
Evidence: L1 (unit: full-history pagination, syncNotes floor, non-semver-milestone fall-through — 4 new tests, 206 green) → L2 required (AC: post-re-sync
archive/issues/has no v8.1.0 catch-all + no garbage folders). Residual: live re-sync + portal tickets-view verification [#12186].Deltas from ticket (if any)
sortedReleases(the bucketing reference) now spans the full history;this.releasesis full too (so the fast-path cache reconstructs a full reference), butsyncNotesfloors writes tosyncStartDateand indexes within the floored set. This keeps the portal Releases view + release-notes chunk layout byte-identical and avoids adding ~1,028 release SSR routes — a conscious scope boundary (full release-notes history is a separate, opt-in enhancement, not this fix).maxReleases1000 → 2000 (config.template) — the full history (~1,194 releases) exceeds the old cap; a cap below the total would silently drop the oldest releases. A loud WARN now fires if the cap is hit before history is exhausted (no silent truncation).syncNotes, in-window dates are required to exercise the "notes written" path. Production behavior is unchanged —fetchAndCacheReleasesalready filtered those pre-floor releases out before the fix, so they never reached disk.Config template change (per mcp-config-template-change-guide)
issueSync.maxReleases(1000 → 2000).config.mjsfollow-up (required): each clone's gitignoredconfig.mjsoverridesmaxReleasesindependently — bump it to2000after merge so the full-history fetch isn't truncated. (Not committed; gitignored.)sync-github-workflowpicks up the new value.Test Evidence
npm run test-unit -- test/playwright/unit/ai/services/github-workflow/→ 206 passed (4 new, 0 regressions).ReleaseNotesSyncer.spec: cold full-history fetch (proves the early-exit and the filter were removed — pre-floor releases survive insortedReleases, pagination does not stop at the floor);syncNotesfloor (proves notes stay floored + index within the floored set whilesortedReleasesis full).IssueSyncer.spec+PullRequestSyncer.spec: a non-semver milestone falls through to theclosedAt → releasefallback (v9.0.0), not a title-derived folder. Each fails without the guard.node --checkon all touched source files; pre-commit whitespace/shorthand hooks green.Post-Merge Validation
maxReleasesto 2000 in each clone's localconfig.mjs.sync-github-workflowre-sync; confirmresources/content/archive/issues/has no v8.1.0 catch-all (pre-2025 issues distribute to their true releases) and no non-semver/garbage version folders.releases.jsonare unchanged (notes still floored tosyncStartDate).