LearnNewsExamplesServices
Frontmatter
id11198
titleIssueService archive-write path refactor for lazy ordinal chunking (#11187 AC3)
stateClosed
labels
enhancementairefactoringai-generatedmodel-experience
assigneesneo-opus-4-7
createdAtMay 11, 2026, 11:20 AM
updatedAtMay 11, 2026, 4:11 PM
githubUrlhttps://github.com/neomjs/neo/issues/11198
authorneo-opus-4-7
commentsCount2
parentIssue11187
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[x] 11190 Add archivePath helper for sealed archive chunks, [x] 11189 Phase 1: Refactor Config for Archive Shape Management
blocking[]
closedAtMay 11, 2026, 4:11 PM

IssueService archive-write path refactor for lazy ordinal chunking (#11187 AC3)

Closedenhancementairefactoringai-generatedmodel-experience
neo-opus-4-7
neo-opus-4-7 commented on May 11, 2026, 11:20 AM

Context

Part of Epic #11187 Phase 1 execution. We are converging on the Option E''+S architecture for GitHub workflow data archiving: active tiers retain their deterministic ID-range chunking, while archive tiers move to a unified root with density-tuned lazy ordinal chunking.

Sibling sub-tickets: AC1 #11189 (config refactor; PR #11191 APPROVED at @tobiu merge gate), AC2 #11190 (archivePath helper; PR #11193 APPROVED at @tobiu merge gate), AC4 #11196 (PullRequestService — @neo-gemini-3-1-pro claimed), AC5 #11197 (DiscussionService — open).

The Problem

The current IssueSyncer#getIssuePath writes archive-tier issue data using archiveDir / vN.M.K / chunkPath(id) / filename (deterministic ID-range chunking under a version folder). Per Epic #11187 Cycle 2 amendment, archive-tier must switch to archive/issues/vN.M.K/{flat|chunk-N}/ with lazy ordinal chunking (sealed-chunk semantics via prevent-reopen.yml). Active-tier writes (issuesDir / chunkPath(id) /) MUST retain XXxx ID-range semantic so LocalFileService#getIssueById keeps O(1) deterministic lookup.

The Architectural Reality

V-B-A'd against current IssueSyncer.mjs:

  • ai/services/github-workflow/sync/IssueSyncer.mjs#getIssuePath (lines 267-314):
    • Line 283: OPEN → issuesDir / chunkPath(id) / filename (active; unchanged)
    • Line 293: CLOSED with milestone → archiveDir / milestoneDir / chunkPath(id) / filename (archive — REFACTOR)
    • Line 306: CLOSED without milestone but with subsequent release → archiveDir / releaseDir / chunkPath(id) / filename (archive — REFACTOR)
    • Line 310: CLOSED without subsequent release → issuesDir / chunkPath(id) / filename (active; unchanged)
  • ai/services/github-workflow/LocalFileService.mjsgetIssueById already uses chunkPath(id) for active + recursive search for archive (archive shape agnostic; no changes needed)
  • ai/services/github-workflow/IssueService.mjs — V-B-A grep'd: zero archive-path references; service layer doesn't directly compute paths (no changes needed)
  • New consumer: archivePath() helper from AC2 (PR #11193) — requires caller-owned itemCount + itemIndex per planner pre-pass.

The Fix

  1. Replace IssueSyncer#getIssuePath archive-write branches (lines 293, 306) with archivePath() helper from AC2.
  2. Add a release-bucket-planner pre-pass to compute itemCount + itemIndex for each archive bucket BEFORE iteration. The planner runs once per version-folder per sync cycle; per-issue path resolution then makes O(1) helper calls with the precomputed bucket state. This honors AC2's "caller-owned bucket state" contract (no hidden filesystem scans inside the helper).
  3. Active-tier write paths (lines 283, 310) retain chunkPath(id) semantic. Test explicitly verifies this with a closed-issue-without-subsequent-release case.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
IssueSyncer#getIssuePath archive branches Epic #11187 + AC2 (#11190) archivePath() contract Route closed-with-milestone + closed-with-later-release issues to archive/issues/vN.M.K/{flat|chunk-N}/ via archivePath() None — invalid milestone/release falls through to active tier Inline code comments + JSDoc updates Unit test verifying active vs archive pathing + planner pre-pass correctness
Active-tier chunkPath(id) preservation Epic #11187 Cycle 2 amendment OPEN + CLOSED-without-subsequent-release issues keep issuesDir / chunkPath(id) / filename None — fall-through equivalent to current behavior JSDoc on getIssuePath Explicit test: chunkPath(11190) returns '111xx' after refactor invocation
Release-bucket-planner pre-pass New (caller-owned bucket state per AC2) Computes itemCount + itemIndex per version-folder once per sync cycle; per-issue path resolution then O(1) No fallback — must run before per-issue iteration JSDoc on planner Unit test verifying planner produces correct bucket state across multi-version scenarios

Acceptance Criteria

  • AC1: IssueSyncer#getIssuePath archive branches (lines 293, 306) refactored to use archivePath() helper from AC2
  • AC2: Active-tier write paths (lines 283, 310) explicitly preserved with chunkPath(id); test verifies non-interference
  • AC3: Release-bucket-planner pre-pass added to caller (likely IssueSyncer#run or equivalent orchestration site); planner runs once per version-folder per sync cycle
  • AC4: Tests cover: flat archive ≤100, chunked archive >100, milestone-bucketed vs release-bucketed routing, active-tier non-interference, planner correctness across multi-version scenarios
  • AC5: ai/services/github-workflow/LocalFileService.mjs verified unchanged (no edits needed; archive recursive search is shape-agnostic) — capture as static-review confirmation
  • AC6: PR body documents the planner pre-pass design + cites AC2 (#11190) contract; sibling links to AC4 (#11196) + AC5 (#11197) for cross-family symmetry awareness

Out of Scope

  • Actually migrating existing issue-archive/ data (Phase 3 of Epic #11187)
  • Modifying PullRequestService (AC4 — @neo-gemini-3-1-pro's lane) or DiscussionService (AC5 — open for @neo-gpt)
  • Any modifications to the archivePath helper itself (AC2 / PR #11193)
  • Changing AC1 config (#11189 / PR #11191) — consumed as-is
  • Active-tier ordinal-chunking refactor — explicitly REJECTED per Epic #11187 Cycle 2 amendment

Avoided Traps

  • Active-tier ordinal chunking: rejected per Epic #11187 Cycle 2 amendment. Active-tier issues MUST retain chunkPath(id) for LocalFileService#getIssueById O(1) determinism.
  • Hidden filesystem scans inside helper consumption: rejected per AC2 (#11190) contract. Caller-owned bucket state via release-bucket-planner pre-pass.
  • Per-issue filesystem scan during iteration: rejected — would defeat the planner pre-pass + drag IO into per-call hot path. Planner runs ONCE per sync cycle per version-folder.
  • Modifying LocalFileService to handle ordinal chunks specially: rejected — recursive search is shape-agnostic; active path is chunkPath(id) deterministic; no edits needed.

Related

  • Parent Epic: #11187 Phase 1 AC3
  • AC2 (helper): #11190 / PR #11193 (APPROVED at @tobiu merge gate)
  • AC1 (config): #11189 / PR #11191 (APPROVED at @tobiu merge gate)
  • AC4 (sibling — PullRequestService): #11196 (@neo-gemini-3-1-pro)
  • AC5 (sibling — DiscussionService): #11197 (open)
  • Blocked-by: #11189 + #11190 (cannot open PR against dev until both land)

Origin Session ID

c2912891-b459-4a03-b2af-154d5e264df1

Handoff Retrieval Hints

  • query_raw_memories(query="Epic 11187 AC3 IssueService refactor archivePath planner pre-pass")
  • ask_knowledge_base(query="IssueSyncer getIssuePath archive write paths chunkPath")
  • File:line anchors: ai/services/github-workflow/sync/IssueSyncer.mjs:267-314 (specifically lines 293 + 306 for archive-write branches)