Authored by Opus 4.8 (Claude Code). Session de8830c3-8d1b-47f8-90db-bd236c8b9bd2.
Related: #12194
Related: #12186
The merged bucketing fixes (#12188/#12184) are forward-only: sealed-chunk + oldVersion-precedence pin the existing ~4,133 v8.1.0 issues, so a normal re-sync won't clear the tickets-view pile-up. This PR delivers the tool to clear it — a non-destructive, one-time re-bucket migration:
IssueSyncer.migrateArchiveBuckets(metadata, {dryRun}) — recomputes every issue's version from the full release history with oldVersion ignored (#planBuckets(..., {ignoreOldVersion:true})), relocates mis-bucketed .md files via a two-phase staged move (no collisions, no delete, no GitHub re-fetch), rewrites the issues _index.json, and prunes emptied dirs. Fails loud if no releases load.
SyncService.migrateArchiveBuckets({dryRun}) — facade (load → migrate → save), mirroring refetchIssuesByNumber.
ai/scripts/migrations/rebucketArchive.mjs — operator-run entry with a --dry-run preview.
Execution is operator-gated + deferred (large content diff; pair with the concurrency-capped regen + deploy). This PR is the reviewable, tested tool; #12194 closes after the operator runs it and verifies the result ACs.
FAIR-band: over-target — sole active implementer (@neo-gpt rate-limited, @neo-gemini benched) + operator-directed lane (epic #12186; operator chose approach B).
Evidence: L1 (unit: re-bucket moves a sealed-pinned issue to its true release with content preserved + source dir pruned + index updated; dryRun previews without moving; no-releases aborts — 3 new tests, 212 green) → L2 required for the AC that the LIVE archive clears (operator execution). Residual: live run + portal verification [#12194].
Deltas from ticket (if any)
- Approach B (non-destructive) per your call. The recompute hinges on a new
ignoreOldVersion option on #planBuckets — without it, oldVersion-precedence re-confirms v8.1.0 (the cached path is valid semver) and the corrected closedAt→release is never reached. Default-false keeps all existing call sites unchanged.
- Scope: issues only (the dominant pile-up = the tickets-view). Pulls/Discussions, if similarly mis-bucketed, are a sibling follow-up on their own syncers (same pattern).
- Two-phase staged move (stage → final) so files swapping chunks during the re-chunk cannot collide mid-move.
Test Evidence
npm run test-unit -- test/playwright/unit/ai/services/github-workflow/ → 212 passed (3 new, 0 regressions).
- New (
IssueSyncer.spec): core re-bucket (v8.1.0 → true release; content preserved; source dir pruned; _index.json version+chunk updated); dryRun (plan only — file + metadata untouched); no-releases guard (throws).
node --check on all touched files; pre-commit whitespace/shorthand hooks green.
Post-Merge Validation
Authored by Opus 4.8 (Claude Code). Session de8830c3-8d1b-47f8-90db-bd236c8b9bd2.
Related: #12194 Related: #12186
The merged bucketing fixes (#12188/#12184) are forward-only: sealed-chunk +
oldVersion-precedence pin the existing ~4,133 v8.1.0 issues, so a normal re-sync won't clear the tickets-view pile-up. This PR delivers the tool to clear it — a non-destructive, one-time re-bucket migration:IssueSyncer.migrateArchiveBuckets(metadata, {dryRun})— recomputes every issue's version from the full release history witholdVersionignored (#planBuckets(..., {ignoreOldVersion:true})), relocates mis-bucketed.mdfiles via a two-phase staged move (no collisions, no delete, no GitHub re-fetch), rewrites the issues_index.json, and prunes emptied dirs. Fails loud if no releases load.SyncService.migrateArchiveBuckets({dryRun})— facade (load → migrate → save), mirroringrefetchIssuesByNumber.ai/scripts/migrations/rebucketArchive.mjs— operator-run entry with a--dry-runpreview.Execution is operator-gated + deferred (large content diff; pair with the concurrency-capped regen + deploy). This PR is the reviewable, tested tool; #12194 closes after the operator runs it and verifies the result ACs.
FAIR-band: over-target — sole active implementer (@neo-gpt rate-limited, @neo-gemini benched) + operator-directed lane (epic #12186; operator chose approach B).
Evidence: L1 (unit: re-bucket moves a sealed-pinned issue to its true release with content preserved + source dir pruned + index updated; dryRun previews without moving; no-releases aborts — 3 new tests, 212 green) → L2 required for the AC that the LIVE archive clears (operator execution). Residual: live run + portal verification [#12194].
Deltas from ticket (if any)
ignoreOldVersionoption on#planBuckets— without it,oldVersion-precedence re-confirms v8.1.0 (the cached path is valid semver) and the correctedclosedAt→releaseis never reached. Default-false keeps all existing call sites unchanged.Test Evidence
npm run test-unit -- test/playwright/unit/ai/services/github-workflow/→ 212 passed (3 new, 0 regressions).IssueSyncer.spec): core re-bucket (v8.1.0 → true release; content preserved; source dir pruned;_index.jsonversion+chunk updated); dryRun (plan only — file + metadata untouched); no-releases guard (throws).node --checkon all touched files; pre-commit whitespace/shorthand hooks green.Post-Merge Validation
node ai/scripts/migrations/rebucketArchive.mjs --dry-runagainst the real metadata — review the distribution before executing.resources/content/archive/issues/has no v8.1.0 catch-all and no data loss.