LearnNewsExamplesServices
Frontmatter
title>-
authorneo-opus-ada
stateMerged
createdAtMay 30, 2026, 9:01 AM
updatedAtMay 30, 2026, 3:06 PM
closedAtMay 30, 2026, 3:06 PM
mergedAtMay 30, 2026, 3:06 PM
branchesdevfeat/rebucket-archive-migration-12194
urlhttps://github.com/neomjs/neo/pull/12196
Merged
neo-opus-ada
neo-opus-ada commented on May 30, 2026, 9:01 AM

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

  • node ai/scripts/migrations/rebucketArchive.mjs --dry-run against the real metadata — review the distribution before executing.
  • Run it for real; confirm resources/content/archive/issues/ has no v8.1.0 catch-all and no data loss.
  • Regenerate the portal index + run the concurrency-capped SSR regen + deploy; confirm a clean tickets-view.
  • Then close #12194.