LearnNewsExamplesServices
Frontmatter
id11390
titleAdopt contentPath index map in GH workflow syncers
stateClosed
labels
enhancementairefactoringarchitecturebuild
assigneesneo-gpt
createdAtMay 15, 2026, 3:04 AM
updatedAtMay 15, 2026, 11:49 AM
githubUrlhttps://github.com/neomjs/neo/issues/11390
authorneo-gpt
commentsCount3
parentIssue11372
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 15, 2026, 11:49 AM

Adopt contentPath index map in GH workflow syncers

Closedenhancementairefactoringarchitecturebuild
neo-gpt
neo-gpt commented on May 15, 2026, 3:04 AM

Context

Epic #11372 tracks ADR 0004 Phase 1 for the Universal Ordinal-100 Content Architecture. PR #11381 has now been approved as Lane A: it adds contentPath.mjs, contentBucketDir(), chunkNumberFor(), and the validator primitives that downstream syncer and lookup code should consume.

This ticket is the narrow Lane B follow-up for ADR 0004 Phase 1 items 2, 3, and 5:

  • Index map: _index.json schema and maintenance in syncers.
  • LocalFileService rewrite: index-based lookup.
  • Syncer updates: IssueSyncer, PullRequestSyncer, and DiscussionSyncer consume contentPath.mjs and maintain _index.json.

Merge dependency: implementation should not start until PR #11381 is merged into dev, because this work consumes the helper introduced there.

The Problem

ADR 0004 retires ID-range folder derivation. Under the target ordinal-100 architecture, an item's chunk is based on its zero-based ordinal position inside a collection, not on the GitHub issue, PR, or discussion number. That means ID-keyed lookup cannot infer the path from the ID anymore.

The current implementation still relies on the old derivation model:

  • LocalFileService.mjs:3 imports chunkPath.
  • LocalFileService.mjs:78-89 derives active issue paths with chunkPath(normalizedId) and then recursively scans archive paths.
  • IssueSyncer.mjs:12-13 imports chunkPath and archivePath; IssueSyncer.mjs:370-410 uses chunkPath(issue.number) for active issues and archivePath() for archived issues.
  • PullRequestSyncer.mjs:11-12 imports chunkPath and archivePath; PullRequestSyncer.mjs:168-199 uses pr-${chunkPath(pr.number)} for active PRs and archivePath() for archived PRs.
  • DiscussionSyncer.mjs:149-178 still uses flat active discussion paths and archivePath() for archived discussions.

This is expected residual state after Lane A. It becomes wrong-shape as soon as Phase 1 moves past the additive helper and starts emitting the new resources/content/ structure.

The Architectural Reality

ADR 0004 §2.1 defines one target shape for active and archive tiers: every type lives under chunk-N/, including issues, pulls, discussions, release notes, and archive buckets.

ADR 0004 §3.2 defines the replacement for ID-derived lookup: syncers maintain resources/content/_index.json with entries containing at least {type, id, version, chunkNumber, path}. LocalFileService#getIssueById and sibling ID lookups use the index instead of deriving a folder from the numeric ID or recursively scanning the archive tree.

ADR 0004 §3.3 requires the syncers to compute itemCount and zero-based itemIndex for active and archive buckets, call contentPath(), and update the index alongside file writes.

This is a write-path plus lookup-path contract. It is not a clean-slate migration ticket; migration remains ADR 0004 Phase 1 item 10 after the emitters and consumers are ready.

The Fix

Implement the Lane B path/index substrate after PR #11381 lands:

  1. Add or extend a shared index-map primitive for resources/content/_index.json.
  2. Define a stable index schema for active and archived entries across issues, pulls, and discussions.
  3. Update IssueSyncer, PullRequestSyncer, and DiscussionSyncer so they:
    • compute active and archive ordinal positions,
    • call contentPath() for active and archive paths,
    • write files to the ADR 0004 chunk-N/ shape,
    • update/remove _index.json entries in the same sync pass that writes/removes markdown files.
  4. Update LocalFileService issue and discussion ID lookups to use _index.json as the primary lookup surface.
  5. Remove or quarantine old chunkPath() assumptions from the touched write/read paths.
  6. Add focused unit coverage for index maintenance and lookup behavior.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
resources/content/_index.json ADR 0004 §3.2 Canonical ID-to-path map for content emitted by GH workflow syncers; entries include type, id, optional version/bucket, chunkNumber, and path Missing/malformed index should fail visibly for ID lookup; sync should regenerate during full sync JSDoc on the index helper plus ticket/PR body schema summary Unit tests for write/update/remove and malformed-index behavior
Syncer write paths ADR 0004 §3.1 and §3.3 IssueSyncer, PullRequestSyncer, and DiscussionSyncer compute itemIndex/itemCount and call contentPath() for active and archive tiers No legacy chunkPath() fallback for newly emitted content Updated method JSDoc where path behavior changes Unit tests proving active and archive output paths use chunk-N/
LocalFileService ID lookup ADR 0004 §3.2 and §3.5 Lookup reads _index.json and then opens the indexed file path If index entry is missing, return structured NOT_FOUND or a clearly documented regeneration hint; do not silently derive legacy paths as the primary path Updated class/method JSDoc explaining index-backed lookup Unit tests for active issue, archived issue, discussion, missing index entry, and stale path

Acceptance Criteria

  • resources/content/_index.json schema is implemented or generated by a dedicated helper with Anchor & Echo JSDoc.
  • IssueSyncer active and archive write paths consume contentPath() and maintain _index.json entries for created, moved, updated, dropped, and archived issues.
  • PullRequestSyncer active and archive write paths consume contentPath() and maintain _index.json entries for created, moved, updated, and archived PRs.
  • DiscussionSyncer active and archive write paths consume contentPath() and maintain _index.json entries for created, moved, updated, and archived discussions.
  • LocalFileService#getIssueById uses _index.json as the primary lookup mechanism and no longer derives active issue paths via chunkPath(issueNumber).
  • LocalFileService#getDiscussionById uses _index.json as the primary lookup mechanism and no longer relies on flat-path/recursive legacy active lookup as the primary path.
  • Old chunkPath() assumptions are removed or explicitly quarantined from the touched paths; any remaining references are listed in the PR body with a follow-up rationale.
  • Tests cover index write/update/remove behavior, active and archive lookup behavior, missing-entry behavior, and stale indexed-path behavior.
  • The PR body includes a dependency note that PR #11381 was merged before implementation began.

Out of Scope

  • Release-notes chunking and ReleaseNotesSyncer creation. That is ADR 0004 Phase 1 item 6.
  • TicketSource, PullRequestSource, DiscussionSource, and IssueIngestor consumer rewires. Those remain Lane C / #11361 territory.
  • publish.mjs release-cut review. That is ADR 0004 Phase 1 item 7.
  • Clean-slate migration or deletion of existing resources/content/ data. That is ADR 0004 Phase 1 item 10 and must wait until emitters and consumers are ready.
  • Phase 2 portal, SEO, and middleware work.

Avoided Traps

  • Do not reintroduce ID-range math as an optimization. ADR 0004 explicitly retires folder-name ID encoding.
  • Do not hide missing index data behind recursive scans as the normal path. Recursive traversal is a migration/debug fallback at most; the contract is index-backed lookup.
  • Do not begin implementation before PR #11381 lands on dev; this ticket consumes the helper shipped by that PR.
  • Do not bundle clean-slate migration into this PR. The value here is making the new syncer logic correct before any deletion/resync step.

Related

  • Parent epic: #11372
  • Dependency: PR #11381 / #11379
  • Downstream consumer lane: #11361
  • Authority: learn/agentos/decisions/0004-github-content-architecture.md
  • Prior related-but-superseded read-path fix: #11138

Handoff Retrieval Hint: ADR 0004 Lane B LocalFileService index lookup syncers contentPath _index.json #11372 #11381

tobiu referenced in commit 0e4c016 - "feat(github-workflow/shared): consolidate path primitives into universal contentPath.mjs (#11379) (#11381) on May 15, 2026, 10:19 AM
tobiu closed this issue on May 15, 2026, 11:49 AM
tobiu referenced in commit bed6713 - "feat(github-workflow): adopt content index map (#11390) (#11403) on May 15, 2026, 11:49 AM
tobiu referenced in commit ecadd27 - "feat(github-workflow): clean-slate purge of resources/content/ to enable ordinal-100 re-sync (#11451) (#11461) on May 16, 2026, 4:51 PM