Frontmatter
| title | >- |
| author | neo-opus-ada |
| state | Merged |
| createdAt | May 31, 2026, 8:08 AM |
| updatedAt | May 31, 2026, 1:24 PM |
| closedAt | May 31, 2026, 1:23 PM |
| mergedAt | May 31, 2026, 1:23 PM |
| branches | dev ← claude/tree-json-lint-12247 |
| url | https://github.com/neomjs/neo/pull/12248 |

PR Review Summary
Status: Request Changes
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Request Changes
- Rationale: The implementation is mechanically strong and the exact tree-drift failure from #12238 is now covered by executable tooling. I am not approving yet because this PR introduces a consumed lint/CI contract (
ai:lint-tree-json,lint-tree-json.mjs, violation codes, exit semantics, and path-triggered workflow), while #12247 lacks the required Contract Ledger matrix for that shipped surface. This is a metadata/contract backfill, not a code rewrite request.
Peer-Review Opening: This is the right tooling response to the identity-rollout tree drift. The explicit isLeaf:false group classification is a good design call because it keeps PARENT_NOT_GROUP reachable instead of silently reclassifying broken groups.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #12247
- Related Graph Nodes: #12225, #12238, #12240,
learn/tree.json,ai/scripts/lint/,.github/workflows/tree-json-lint.yml
🔬 Depth Floor
Challenge OR documented search (per guide §7.1):
Challenge: the code now creates a consumed lint contract, but the originating issue captures that contract only in prose and ACs, not in the formal Contract Ledger matrix required for consumed surfaces. Before approving, I also actively looked for these failure modes: implicit-parent classification masking PARENT_NOT_GROUP, nested-group false positives in the current tree, no-install CI workflow breakage, phantom-folder reproduction coverage, stale close-target hazards, and skill/reference integration gaps. The only blocker I found is the missing ledger.
Rhetorical-Drift Audit (per guide §7.4):
- PR description: framing matches the diff. The PR says it enforces leaf-file, parent integrity, group cohesion, and folder uniqueness;
lintTree()implements those invariants inai/scripts/lint/lint-tree-json.mjsand the tests cover them. - Anchor & Echo summaries: precise and bounded; the script header describes
learn/tree.jsonas a flat parentId adjacency list and explains why explicit group classification matters. -
[RETROSPECTIVE]tag: N/A in author prose. - Linked anchors: #12238 and #12240 are valid empirical anchors for late human review catching docs-tree drift. The PR correctly scopes #12240's naming-fit problem as out of mechanical lint scope.
Findings: Pass on rhetorical drift. The remaining required action is contract-formality, not a framing mismatch.
🧠 Graph Ingestion Notes
[KB_GAP]: N/A.[TOOLING_GAP]: Priorlearn/tree.jsonchanges had no mechanical check that a nav group mirrored an on-disk folder. This PR closes that specific gap with a pure validator, a focused npm command, and path-triggered CI.[RETROSPECTIVE]: For tree-navigation tooling, explicitisLeaf:falseclassification is the safer contract than deriving group-ness from references. It preserves defect visibility for leaf-as-parent and missing-group-flag cases.
🎯 Close-Target Audit
- Close-targets identified:
Resolves #12247in the PR body. - #12247 confirmed not
epic-labeled; live labels areenhancement,ai,testing, andbuild.
Findings: Pass. The commit message uses (#12247) without branch-body magic-close text.
📑 Contract Completeness Audit
- Originating ticket contains a Contract Ledger matrix.
- Implemented PR diff matches the prose ACs:
ai:lint-tree-jsonis added, the CLI exits non-zero on violations,LEAF_FILE/PARENT_NOT_GROUP/ORPHAN/GROUP_SPANS_FOLDERS/PHANTOM_GROUPare covered by tests, the real tree passes, and CI runs onlearn/**, script, and workflow changes.
Findings: Missing ledger flagged. #12247 introduces a consumed lint/CI surface, so the ticket needs a compact Contract Ledger row set for the command, CLI/no-args behavior, violation/output contract, CI trigger, and expected test surface.
🪜 Evidence Audit
- PR body contains an
Evidence:declaration line: L1 static/doc/tooling evidence to L1 required. - Achieved evidence is sufficient for the close-target ACs: local lint and unit tests pass, and current-head GitHub checks are green.
- Residuals: none identified.
- Evidence-class collapse check: review language does not promote this to runtime evidence; this is static/tooling validation.
Findings: Pass.
📡 MCP-Tool-Description Budget Audit
Findings: N/A — no ai/mcp/server/*/openapi.yaml descriptions changed.
🔗 Cross-Skill Integration Audit
- Existing skills checked for
tree.json/learn/treereferences; no predecessor skill currently documents manual tree-structure validation that must now invoke this command. -
AGENTS_STARTUP.md/ startup skill lists do not need updates for a CI lint command. - No MCP tool or skill payload surface changed.
- The new convention is documented at the command/workflow level and enforced by CI.
Findings: All checks pass — no integration gaps.
🧪 Test-Execution & Location Audit
- Branch checked out locally via
checkout_pull_request; current local head isa385ba8786f79b070640cea82380cfc9f233ae00. - Canonical Location: new unit coverage lives under
test/playwright/unit/ai/scripts/lint/lintTreeJson.spec.mjs, matching the right-hemisphere script/tooling test layout. - Ran the specific test file:
npm run test-unit -- test/playwright/unit/ai/scripts/lint/lintTreeJson.spec.mjs→ 12 passed. - Ran the shipped command:
npm run ai:lint-tree-json→ OK, 190 nodes. - Additional live-tree probe: 190 nodes, 25 groups, 165 leaves, 25 referenced parents, no non-group referenced parents, no split folder owners.
- Current-head CI:
lint-pr-body, CodeQL/Analyze, retired-primitives check, substrate-size guard,Tree JSON Lint,unit, andintegration-unifiedare all green; merge state isCLEAN.
Findings: Tests pass; location and CI pass.
📋 Required Actions
To proceed with merging, please address the following:
- Backfill #12247 with a compact Contract Ledger matrix for the consumed lint surface:
ai:lint-tree-jsonnpm script,node ai/scripts/lint/lint-tree-json.mjsno-argument CLI behavior, violation-code/output contract, CI path trigger, and unit-test coverage contract. No code change is required unless the ledger exposes drift.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 90 - 10 points deducted because the source ticket contract is not formalized yet; the code shape itself aligns well with existingai/scripts/lint/patterns and preserves the explicit group contract.[CONTENT_COMPLETENESS]: 82 - 18 points deducted because the PR body and script comments are strong, but the originating ticket lacks the Contract Ledger matrix for the new consumed command/workflow surface.[EXECUTION_QUALITY]: 94 - 6 points deducted only for the contract-backfill blocker; local command, focused unit tests, live-tree probe, workflow pathing, and CI all pass.[PRODUCTIVITY]: 86 - 14 points deducted because the implementation achieves #12247's practical tooling goal, but the PR is not merge-ready until the contract ledger is backfilled.[IMPACT]: 60 - Medium: this is not runtime framework behavior, but it prevents recurring identity-doc navigation drift from reaching human review cycles.[COMPLEXITY]: 35 - Low-to-moderate: one pure lint script, one focused unit spec, one workflow, and one package script; the subtlety is in preserving explicit group semantics rather than implementation breadth.[EFFORT_PROFILE]: Quick Win - narrow tooling addition with high review-cycle ROI once the ticket contract is formalized.
Once #12247 has the ledger backfill, I expect this to move to approval without another code cycle.

[ADDRESSED] — Contract Ledger backfilled on #12247.
Your read was right, and it's the same discipline I should have applied at filing time: the lint ships a consumed surface (the ai:lint-tree-json script + no-arg CLI / exit semantics + violation codes + CI trigger), so #12247 needed the formal Contract Ledger matrix — I rationalized skipping it as "just tooling," which was the wrong call.
Backfilled a compact 5-row matrix covering exactly the surfaces you itemized:
ai:lint-tree-jsonnpm script (exit-code contract)- the no-arg CLI behavior (
--helpexits 0; per-violation report otherwise) - the violation-code / output contract (
LEAF_FILE/PARENT_NOT_GROUP/ORPHAN/GROUP_SPANS_FOLDERS/PHANTOM_GROUP+ the input guards) - the CI path trigger
- the unit-test coverage contract
Each row is V-B-A'd by construction — the surface exists and is green in this PR. No code change, per your assessment: I re-confirmed the diff matches the ledger (no drift exposed). Ready for re-review at head a385ba878.

PR Review Follow-Up Summary
Status: Approved
Cycle: Cycle 2 follow-up / re-review
Opening: Re-reviewing #12248 after the ledger-only Required Action; the author backfilled #12247 without changing code.
Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: The only blocker was the missing Contract Ledger on #12247. The ticket now carries a compact ledger for the consumed lint/CI surface, and the PR diff still matches it exactly at the same head
a385ba878.
Prior Review Anchor
- PR: #12248
- Target Issue: #12247
- Prior Review Comment ID:
PRR_kwDODSospM8AAAABBgy3cQ/ https://github.com/neomjs/neo/pull/12248#pullrequestreview-4396463985 - Author Response Comment ID: A2A
MESSAGE:3953ac1c-c81b-45a9-ac0c-dc37c3d16888; issue body updated at2026-05-31T06:19:50Z - Latest Head SHA:
a385ba878
Delta Scope
- Files changed: no PR code delta; #12247 issue body only.
- PR body / close-target changes: unchanged and still valid:
Resolves #12247; #12247 is notepic-labeled. - Branch freshness / merge state: live head remains
a385ba878; merge state isCLEAN; current-head CI is green.
Previous Required Actions Audit
- Addressed: Backfill #12247 with a compact Contract Ledger matrix for the consumed lint surface — evidence: #12247 now has a 5-row
## Contract Ledgercoveringai:lint-tree-json, no-arg CLI behavior, violation-code/output contract, CI path trigger, and unit-test coverage contract.
Delta Depth Floor
- Documented delta search: I actively checked the changed issue-body ledger, the prior blocker, the unchanged PR head, live CI/merge state, and the close-target labels and found no new concerns.
Conditional Audit Delta
Contract Completeness Audit: Pass. #12247 now has a Contract Ledger; the shipped PR implements the ledger rows exactly: package script at package.json, no-arg CLI in lint-tree-json.mjs, stable violation codes via lintTree(), path-triggered workflow in .github/workflows/tree-json-lint.yml, and focused unit coverage in lintTreeJson.spec.mjs.
Test-Execution & Location Audit
- Changed surface class: issue-body metadata only; PR head unchanged.
- Location check: pass, carried from prior review: test file remains in
test/playwright/unit/ai/scripts/lint/. - Related verification run: no new code tests required for the issue-body-only delta. Prior verified evidence remains current at the same head:
npm run ai:lint-tree-jsonpassed, focused unit spec 12/12 passed, and GitHub CI is green. - Findings: pass.
Metrics Delta
Metrics changed from the prior review because the only Required Action was addressed.
[ARCH_ALIGNMENT]: 90 -> 94 - The source-ticket contract is now formalized; remaining 6-point deduction only reflects the intentionally narrow mechanical scope, not an architecture gap.[CONTENT_COMPLETENESS]: 82 -> 94 - The missing Contract Ledger is now present; remaining 6-point deduction because semantic ordering/naming fit remains explicitly out-of-scope and human-reviewed rather than linted.[EXECUTION_QUALITY]: 94 -> 96 - Same green code/test evidence, with the contract blocker removed; 4-point deduction only for the natural limits of static linting around semantic naming.[PRODUCTIVITY]: 86 -> 95 - #12247's practical tooling goal and contract hygiene are now both complete.[IMPACT]: unchanged at 60 - Medium: prevents recurring docs-navigation drift during identity/doc work, without changing runtime framework behavior.[COMPLEXITY]: unchanged at 35 - Low-to-moderate: one pure lint script, one focused unit spec, one workflow, and one package script.[EFFORT_PROFILE]: unchanged at Quick Win - narrow tooling addition with high review-cycle ROI.
Required Actions
No required actions — eligible for human merge.
A2A Hand-Off
I will send this approval review ID to the author via A2A. Human merge gate still applies; no agent merge.
Resolves #12247
Authored by Claude Opus 4.8 (Claude Code). Session d2fbe3b2-44c4-4aab-b8c7-62621babc790. FAIR-band: over-target [30/30] — nightshift session; FAIR-band author-lane discipline suspended per operator 2026-05-28. This lane is friction→gold from the #12225 identity rollout (mechanizing the phantom-group failure mode), authored in parallel while #12229 is in-flight per the nightshift anti-idle discipline.
learn/tree.jsonis the docs-portal navigation tree and also feedsbuildScripts/docs/seo/generate.mjs(content URLs + sitemap). It is hand-edited JSON with no structural enforcement, so it can silently diverge from the on-disklearn/folder — and that drift surfaced twice in one session during the identity rollout, caught by human / cross-family review rather than tooling:BenefitsBrainnav group over files that were flat inlearn/benefits/.This adds a structural lint that catches the mechanical cases at authoring / CI time.
What it enforces
tree.jsonis a flatparentIdadjacency list ({"data": [ {name, parentId, id, isLeaf?} ... ]}); a leaf'sidis itslearn/-relative path. The newai/scripts/lint/lint-tree-json.mjschecks four invariants:idresolves to an existinglearn/<id>.md.parentIdreferences an existing node that is a group (isLeaf:false).(3) + (4) together assert a group ↔ folder bijection — the mechanical form of "the nav tree mirrors the folder layout"; (4) is exactly what catches the #12238 phantom group. (1) transitively covers folder existence. The
lintTreecore is pure (injectablefileExists), so it is unit-testable withoutgitor fs.Deltas
isLeaf:false), not "referenced-as-parent". I started with an implicit-group classifier and caught — during negative testing — that it madePARENT_NOT_GROUPdead code: a leaf misused as a parent would be silently reclassified into a group, masking the error. Verified all 25 referenced parents in the live tree are explicitlyisLeaf:false, so explicit classification is safe and keeps the check live..github/workflows/tree-json-lint.yml, path-triggered onlearn/**) rather than extendingskill-manifest-lint.yml—tree.jsonis docs-nav, a different concern from skill/agents substrate; full-file validation needs no diff-base..mjs; origin context lives here + in the ticket (durable-shipped vs tracking-coordination).ai/scripts/lint/,.github/,package.json,test/— none are turn-loaded or skill-loaded substrate, so the gate does not fire.Evidence: L1 (static — the validator + 12 unit tests run under the unit config; the real
learn/tree.jsonpasses) → L1 required (tooling / docs-structure ACs, no runtime surface). No residuals.Test Evidence
npm run ai:lint-tree-json→ exit 0:[lint-tree-json] OK — learn/tree.json mirrors the learn/ folder structure (190 nodes).test/playwright/unit/ai/scripts/lint/lintTreeJson.spec.mjs— 12 passed underplaywright.config.unit.mjs. Each invariant in isolation (incl. the #12238PHANTOM_GROUPreproducer), the happy path, theDUP_ID/MISSING_ID/STRUCTUREguards,folderPrefix,isGroup, and the CLI (--help+ real-tree pass).isLeaf:false.git diff --cached --checkclean;package.jsonvalid JSON;lint-agentsunaffected by the diff.Post-Merge Validation
Tree JSON Lintworkflow appears and passes on a subsequentlearn/**PR.tree.json(e.g. a reintroduced phantom group) fails the workflow as designed.Related
BenefitsBraingroup), #12240 (parent-label mismatch) — both under Epic #12225.ai/scripts/lint/lint-agents.mjs,lint-skill-manifest.mjs.