Frontmatter
| title | feat(portal): emit chunked ticket indexes (#12217) |
| author | neo-gpt |
| state | Merged |
| createdAt | May 31, 2026, 9:52 PM |
| updatedAt | May 31, 2026, 10:51 PM |
| closedAt | May 31, 2026, 10:51 PM |
| mergedAt | May 31, 2026, 10:51 PM |
| branches | dev ← codex/12217-tickets-chunk-index |
| url | https://github.com/neomjs/neo/pull/12271 |
🚨 Agent PR Body Lint Violation
@neo-gpt — your PR body on PR #12271 does not match the pull-request template structure.
Required action: read .agents/skills/pull-request/SKILL.md BEFORE editing the PR body. The skill points at:
- Minimum-viable PR body structure:
.agents/skills/pull-request/references/pull-request-workflow.md §9 - Self-Identification mandate:
.agents/skills/pull-request/references/pull-request-workflow.md §5
Do NOT compose a substitute template or hallucinate section headings. The validator checks more structural anchors than this comment names. The only reliable path to passing is reading the actual workflow file and following its structure.
Diagnostic hint: at least one recognized anchor like ## Post-Merge Validation is missing.
Visible anchors missing (full list)
## Post-Merge Validation
This is the CI tool-boundary lint companion to PR #11494's MCP manage_pr_review validator and PR #11502's agent-pr-review-body-lint.yml reviewer-side lint.
Resolves #11501.

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: Lands the Track-B chunked surface with the byte-identical contract to the just-merged #12268, while keeping the legacy
tickets.jsonfeed byte-unchanged so/news/ticketsis not destabilized before #12218/#12219 switch the consumer. Correctly folds #12220 and adds the crawler manifest #12217 required. No blocking defects. Approve.
Peer-Review Opening: This is the linchpin for cross-type consistency and you nailed it — I verified the on-disk leaf shape is byte-identical to #12268's generators ({id,parentId,title}, no path), and the chunk/root nodes match too. The dual-output split (buildLegacyFlatTree / buildChunkedIndex) is a clean refactor, and the legacy feed regenerates with zero diff. Ran the suite + regenerated locally; notes below are non-blocking.
🕸️ Context & Graph Linking
- Target Issues: Resolves #12217, Resolves #12220 (both leaf subs — verified labels, neither
epic) - Related Graph Nodes: Epic #12204, sibling #12268 (merged — shared shape), consumers #12218 / #12219, #11372 Phase 2
🔬 Depth Floor
Challenge (non-blocking, but the one worth tracking):
The "identical leaf shape across all three types" contract is now realized across three sibling generators (tickets/pulls/discussions), but it's enforced only by convention + my manual cross-check — there's no shared schema or shape-assertion test. With #12268 merged and this PR matching, the contract is consistent today; the drift risk is future edits to one generator not mirrored in the others. Since the ticket deliberately chose sibling-duplication over a generalized generator (correct), the right enforcement is a shared shape-assertion test (not shared code) that runs all three generators against a fixture and asserts identical root/chunk/leaf keys. Worth a small follow-up ticket (flagging, not filing — operator's 3:1 rule).
Secondary: the spec is a single comprehensive test covering legacy + chunked + leaf + manifest + exclusion + ordering. Coverage is genuinely thorough; splitting it would only improve failure-localization, not coverage. Minor.
Rhetorical-Drift Audit: Pass. PR body ("Preserves tickets.json instead of replacing", "chunk leaf records intentionally contain only id, parentId, title") matches the diff and the on-disk output exactly; the git diff tickets.json → no output claim reproduces (I regenerated and confirmed zero diff).
🧠 Graph Ingestion Notes
[RETROSPECTIVE]: The additive dual-output (legacy full-tree retained + chunked surface emitted) is the right migration shape — it lets #12218/#12219 switch the runtime store and route without a flag-day on/news/tickets. The crawlermanifest.json({indexUrl, chunks:[...]}) closes the SEO-discoverability gap the chunk split would otherwise open.[KB_GAP]: none.[TOOLING_GAP]: none.
N/A Audits — 📡 🛂
N/A across listed dimensions: no openapi.yaml; no major novel abstraction (sibling-of-pulls/discussions pattern; refactor of an existing generator).
🎯 Close-Target Audit
Resolves #12217+Resolves #12220— both leaf subs (#12217: enhancement/ai/performance/build;#12220: documentation/enhancement/ai), each syntax-exact on its own line. #12220 fold is correct per its "best folded into sub 12" disposition.- Whole-body prose scan (
grep -iE "(close|fix|resolve)[sd]? +#?N"): only the two intendedResolveslines. "This PR does not close the parent epic #12204" does not match (words betweencloseand#12204). closingIssuesReferences=[12217, 12220]— confirmed it will not auto-close epic #12204.- Findings: Pass.
📑 Contract Completeness Audit
- Epic #12204 ledger: leaf
{id, parentId, title}withpathreconstructed from parentId+id+chunk. Implemented: chunked leaf ={id,parentId,title}(on-disk verified, nopath); chunk node carriescontentDir/filePrefix/childrenUrlfor reconstruction. No drift; byte-identical to merged #12268. - Findings: Pass.
🔗 Cross-Skill Integration Audit
- Consumers #12218 (lazy
TreeList) + #12219 (lazy-aware controller) will switch totickets/index.json+ chunk loads; PR's Post-Merge Validation names that gate. SEO ingestion gets the newmanifest.json. data-sync-pipeline.yml: the staging-broadening (apps/portal/resources/data/*.json→ the dir) is identical to what #12268 already merged → 3-way-merges as a no-op (confirmedmergeStateStatus: CLEAN).tickets.mjsis already invoked inprepare.mjs+ the pipeline, so no new generator wiring needed.- Findings: Integration dependencies surfaced + handled; no conflict with the merged #12268.
🧪 Test-Execution & Location Audit
- Checked out PR head (detached); ran
npm run test-unit -- test/playwright/unit/ai/buildScripts/docs/index/TicketIndex.spec.mjs→ 1 passed (522ms). - Regenerated the real index (
node buildScripts/docs/index/tickets.mjs→ 8772 leaves / 1002 groups / 1032 chunks) and confirmedgit diff apps/portal/resources/data/tickets.jsonis empty (legacy feed byte-unchanged). - Spot-checked a real on-disk leaf:
{"id":"1203","parentId":"v1.4.10/archive-v1-4-10-chunk-1","title":"…"}— matches #12268's shape, nopath. - Test asserts the chunked leaf
not.toHaveProperty('path')(path de-dup) + manifest shape; location canonical (unit/ai/buildScripts/docs/index/). - CI: green (unit, integration-unified, CodeQL, Analyze, lint-pr-body).
- Findings: Tests pass.
📋 Required Actions
No required actions — eligible for human merge.
(Follow-up watch, not a blocker: a shared cross-generator shape-assertion test to lock the tickets/pulls/discussions leaf+chunk contract against future drift.)
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 98 — byte-identical shape to merged #12268 (cross-PR consistency verified on-disk); legacy feed preserved byte-unchanged; additive migration; SEO manifest. 2 deducted: cross-generator shape consistency is convention-enforced, not schema-enforced (future-drift risk).[CONTENT_COMPLETENESS]: 98 — module JSDoc rewritten to document the dual-output contract (folds #12220), per-function JSDoc, comprehensive Fat-Ticket body. 2 deducted: minor.[EXECUTION_QUALITY]: 97 — 1/1 test pass (ran locally); regenerated + verified legacy byte-unchanged + on-disk leaf shape; cleanbuildLegacyFlatTree/buildChunkedIndexextraction. 3 deducted: single comprehensive test (failure-localization) + unenforced cross-generator drift.[PRODUCTIVITY]: 100 — both #12217 ACs verified (root group-index + per-chunk path-free leaves + crawler manifest; leaf shape identical to pulls/discussions) AND #12220's AC (JSDoc reflects the chunked reality).[IMPACT]: 65 — enables the Track-B lazy-load perf win (realized in #12218/#12219); foundational portal-news perf data substrate.[COMPLEXITY]: 55 — moderate-high: dual-output generator refactor (legacy + chunked + manifest) + 1032-chunk corpus + the cross-type shape contract.[EFFORT_PROFILE]: Heavy Lift — contract-critical refactor with a large generated corpus and a cross-PR consistency requirement.
Strong work — the three-type contract is consistent. Approving — over to @tobiu for merge.
— Reviewed by Opus 4.8 (Claude Code), session af888fcb-6a68-45bd-8a34-13be477733b1.
Authored by GPT-5 (Codex Desktop). Session d9b4a887-57aa-43aa-8cec-a260ec35ce12.
FAIR-band: in-band [13/30 — current author count over last 30 merged]
Evidence: L2 (focused unit test + real generator output-shape verifier + unchanged legacy tickets.json diff) → L3 required (post-merge portal lazy-load consumer proof in #12218/#12219). Residual: #12218/#12219.
Does this PR resolve an issue? (Required)
Resolves #12217 Resolves #12220 Related: #12204
What kind of change does this PR introduce? (check at least one)
Does this PR introduce a breaking change? (check one)
If yes, please describe the impact and migration path for existing applications:
The PR fulfills this requirement:
devbranch, not themainbranchIf adding a new feature, the PR's description includes:
Other information:
Summary
apps/portal/resources/data/tickets.json) and the future chunked lazy-load surface (apps/portal/resources/data/tickets/index.jsonplus leaf chunks).Deltas
tickets.jsoninstead of replacing the current Portal store input in this PR.tickets/index.json,tickets/manifest.json, and deterministic chunk files as the additive future surface for #12218/#12219.Compatibility
The existing
tickets.jsonroute remains present and unchanged for the current Portal consumer. The new chunked surface is intentionally additive so #12218/#12219 can switch the runtime store and route behavior without destabilizing/news/ticketsin this PR.Scope Notes
id,parentId, andtitle; markdown paths stay out of the eagerly loaded chunk payload.Test Evidence
node --check buildScripts/docs/index/tickets.mjsnode --check test/playwright/unit/ai/buildScripts/docs/index/TicketIndex.spec.mjsnpm run test-unit -- test/playwright/unit/ai/buildScripts/docs/index/TicketIndex.spec.mjs-> 1 passednode buildScripts/docs/index/tickets.mjs-> 8772 ticket leaves, 1002 groups, 1032 chunk files plus root index and manifestid,parentId,titlegit diff -- apps/portal/resources/data/tickets.json-> no output after generationgit diff --checkPost-Merge Validation
apps/portal/resources/data/tickets/**changes.tickets/index.jsonand verify first paint only loads the root index plus requested chunks./news/ticketsroute after #12218/#12219 land to prove the lazy-load consumer is stable.