Context
Sub-ticket for Epic #11187 Phase 1 AC2 after Cycle 2 parent amendment. #11187 now preserves active issues/ and pulls/ ID-range chunking while moving archive-tier placement to a single-root archive/{type}/vN.M.K/ shape with sealed 100-item ordinal chunks.
This ticket exists because AC3-AC5 service refactors need one shared archive-path primitive before they can safely move issue, pull request, and discussion archive write paths. The prior discussion surfaced a concrete failure mode: archive logic must not leak ordinal chunk-N/ semantics into active issue/pull lookup, because active lookup currently depends on deterministic chunkPath(id) routing.
The Problem
Archive write paths are currently fragmented across syncers:
IssueSyncer#getIssuePath() builds issue archive paths with archiveDir / version / chunkPath(id) / issue-N.md.
PullRequestSyncer#getPullRequestPath() sends all non-open PRs to pullArchiveDir / chunkPath(id) / pr-N.md.
DiscussionSyncer#getDiscussionPath() writes only active discussion files and computes the chunk expression inline.
#11187 changes the archive contract to a single root plus type/release buckets:
resources/content/archive/{issues,pulls,discussions}/vN.M.K/
Archive buckets are flat at <= 100 items and use sealed ordinal chunk-N/ directories only above that threshold. Without a shared helper, each syncer can drift on threshold boundaries, chunk naming, rejected-PR bucket handling, and active/archive separation.
The Architectural Reality
Source surfaces verified before filing:
ai/services/github-workflow/shared/chunkPath.mjs is the existing shared path helper sibling and remains canonical for active ID-range chunking.
ai/services/github-workflow/sync/IssueSyncer.mjs currently imports chunkPath() and applies it to both active and archive paths.
ai/services/github-workflow/sync/PullRequestSyncer.mjs currently imports chunkPath() and archives non-open PRs under pullArchiveDir / chunkPath(id).
ai/services/github-workflow/sync/DiscussionSyncer.mjs imports chunkPath() but still duplicates the chunk expression inline for active discussions.
ai/services/github-workflow/LocalFileService.mjs computes active issue lookup via issuesDir / chunkPath(id) / issue-N.md; this must remain unaffected.
- #11187 Cycle 2 explicitly scopes ordinal
chunk-N/ to sealed archive chunks only.
Structural pre-flight: new helper ai/services/github-workflow/shared/archivePath.mjs matches sibling utility pattern of ai/services/github-workflow/shared/chunkPath.mjs; sibling-file-lift fast-path applies. No novel directory choice.
The Fix
Add a shared archive-path helper under ai/services/github-workflow/shared/ that computes archive-tier paths from explicit bucket inputs. The helper should support:
- archive root + type subdirectory
- release version folder or non-release bucket such as
rejected
- flat-vs-ordinal chunk mode based on a planned archive bucket size / item ordinal
chunk-N naming with 100-item threshold semantics
- strict archive-tier scope, leaving active
chunkPath(id) behavior untouched
The helper should be pure where possible: callers should pass the planned bucket context (itemCount, itemIndex, or equivalent explicit state) instead of the helper independently scanning live directories. If a later service needs filesystem inspection to derive that context, that belongs in the caller or a separately testable planner, not hidden inside a path formatter.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
archivePath() helper |
#11187 AC2 |
Compute archive-tier paths under archive/{type}/...; flat at <= 100, ordinal chunk-N/ above 100 |
Throw on invalid type/version/bucket input rather than guessing |
JSDoc on helper; #11187 docs consume in later ACs |
Unit tests for boundary cases |
| Active path behavior |
#11187 Cycle 2 amendment; LocalFileService#getIssueById |
Remains chunkPath(id) for active issues/pulls; helper must not replace active lookup |
Existing chunkPath() remains available |
Helper JSDoc explicitly says archive-tier only |
Regression test or static test proving active helper not touched |
| Rejected PR bucket path |
#11180 OQ1 + #11187 |
Non-release archive/pulls/rejected/ bucket supported by same helper contract |
Explicit bucket argument; no release inference inside helper |
JSDoc examples |
Unit test for rejected bucket |
Acceptance Criteria
Out of Scope
- Config refactor AC1 (#11189), except consuming the final config shape once corrected.
- Issue/PullRequest/Discussion syncer refactors AC3-AC5.
- Data migration or moving any files.
- Active issue/pull ordinal chunking; explicitly rejected by #11187 Cycle 2.
- Discussion active flattening AC6.
Avoided Traps
- Ordinal chunks for active issues/pulls: rejected because active path lookup must remain deterministic from ID.
- Hidden filesystem scans in the helper: rejected because it couples path formatting to mutable disk state and makes tests harder to reason about.
- Per-syncer duplicate logic: rejected because the archive contract is cross-type and will drift if copied into each syncer.
- Per-type fixed shape config: rejected by amended #11187; archive shape is bucket-size based, not hard-coded issue/pull/discussion shape.
Related
- Parent Epic: #11187
- AC1 Config lane: #11189 (must be corrected before implementation consumes config)
- Discussion: #11180
- Extended V-B-A discussion: #11188
- Current helper sibling:
ai/services/github-workflow/shared/chunkPath.mjs
- Consumer guard:
ai/services/github-workflow/LocalFileService.mjs
Origin Session ID
Origin Session ID: 22713fa8-23d2-4b31-918b-6e2f48d69c06
Handoff Retrieval Hints
query_raw_memories(query="11187 AC2 archivePath helper active tier deterministic chunkPath")
query_raw_memories(query="Discussion 11180 lazy ordinal chunking sealed archive LocalFileService")
ask_knowledge_base(query="GitHub workflow archive path IssueSyncer PullRequestSyncer DiscussionSyncer chunkPath")
Context
Sub-ticket for Epic #11187 Phase 1 AC2 after Cycle 2 parent amendment. #11187 now preserves active
issues/andpulls/ID-range chunking while moving archive-tier placement to a single-rootarchive/{type}/vN.M.K/shape with sealed 100-item ordinal chunks.This ticket exists because AC3-AC5 service refactors need one shared archive-path primitive before they can safely move issue, pull request, and discussion archive write paths. The prior discussion surfaced a concrete failure mode: archive logic must not leak ordinal
chunk-N/semantics into active issue/pull lookup, because active lookup currently depends on deterministicchunkPath(id)routing.The Problem
Archive write paths are currently fragmented across syncers:
IssueSyncer#getIssuePath()builds issue archive paths witharchiveDir / version / chunkPath(id) / issue-N.md.PullRequestSyncer#getPullRequestPath()sends all non-open PRs topullArchiveDir / chunkPath(id) / pr-N.md.DiscussionSyncer#getDiscussionPath()writes only active discussion files and computes the chunk expression inline.#11187 changes the archive contract to a single root plus type/release buckets:
resources/content/archive/{issues,pulls,discussions}/vN.M.K/Archive buckets are flat at
<= 100items and use sealed ordinalchunk-N/directories only above that threshold. Without a shared helper, each syncer can drift on threshold boundaries, chunk naming, rejected-PR bucket handling, and active/archive separation.The Architectural Reality
Source surfaces verified before filing:
ai/services/github-workflow/shared/chunkPath.mjsis the existing shared path helper sibling and remains canonical for active ID-range chunking.ai/services/github-workflow/sync/IssueSyncer.mjscurrently importschunkPath()and applies it to both active and archive paths.ai/services/github-workflow/sync/PullRequestSyncer.mjscurrently importschunkPath()and archives non-open PRs underpullArchiveDir / chunkPath(id).ai/services/github-workflow/sync/DiscussionSyncer.mjsimportschunkPath()but still duplicates the chunk expression inline for active discussions.ai/services/github-workflow/LocalFileService.mjscomputes active issue lookup viaissuesDir / chunkPath(id) / issue-N.md; this must remain unaffected.chunk-N/to sealed archive chunks only.Structural pre-flight: new helper
ai/services/github-workflow/shared/archivePath.mjsmatches sibling utility pattern ofai/services/github-workflow/shared/chunkPath.mjs; sibling-file-lift fast-path applies. No novel directory choice.The Fix
Add a shared archive-path helper under
ai/services/github-workflow/shared/that computes archive-tier paths from explicit bucket inputs. The helper should support:rejectedchunk-Nnaming with 100-item threshold semanticschunkPath(id)behavior untouchedThe helper should be pure where possible: callers should pass the planned bucket context (
itemCount,itemIndex, or equivalent explicit state) instead of the helper independently scanning live directories. If a later service needs filesystem inspection to derive that context, that belongs in the caller or a separately testable planner, not hidden inside a path formatter.Contract Ledger Matrix
archivePath()helperarchive/{type}/...; flat at<= 100, ordinalchunk-N/above 100LocalFileService#getIssueByIdchunkPath(id)for active issues/pulls; helper must not replace active lookupchunkPath()remains availablearchive/pulls/rejected/bucket supported by same helper contractAcceptance Criteria
ai/services/github-workflow/shared/archivePath.mjsor equivalent shared helper in the existing shared utility directory.chunk-2/when the first 100-item chunk is sealed, or equivalent planned-bucket semantics are documented and tested.chunk-N/naming is ordinal and not ID-range based.archive/pulls/rejected/.chunkPath()active-tier behavior.Out of Scope
Avoided Traps
Related
ai/services/github-workflow/shared/chunkPath.mjsai/services/github-workflow/LocalFileService.mjsOrigin Session ID
Origin Session ID:
22713fa8-23d2-4b31-918b-6e2f48d69c06Handoff Retrieval Hints
query_raw_memories(query="11187 AC2 archivePath helper active tier deterministic chunkPath")query_raw_memories(query="Discussion 11180 lazy ordinal chunking sealed archive LocalFileService")ask_knowledge_base(query="GitHub workflow archive path IssueSyncer PullRequestSyncer DiscussionSyncer chunkPath")