Skip to content

feat(parser): hierarchy-inference confidence — provenance jsonb + read-time scorer - #418

Merged
thewrz merged 16 commits into
mainfrom
feat/hierarchy-confidence
Jul 8, 2026
Merged

feat(parser): hierarchy-inference confidence — provenance jsonb + read-time scorer#418
thewrz merged 16 commits into
mainfrom
feat/hierarchy-confidence

Conversation

@thewrz

@thewrz thewrz commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Why

The 5-signal DOCX inference engine records disagreement (meta.conflicts) but nothing records how strongly the winning classification was supported — a paragraph classified by the indentation fallback alone looked identical to one nailed by numbering.xml with full corroboration. Editability classification and style consensus already expose 0–1 confidence in the API contract; hierarchy inference did not, so human review triage (onboarding report, future review canvas #143) had nothing to rank by. The blind spot of unanimous-but-weak wins is the point.

Implements the approved design docs/superpowers/specs/2026-07-07-hierarchy-confidence-design.md (ADR-055 — the design doc's "ADR-054" was taken by first-class-clients in the interim).

What

  • Persist facts, derive the score at read time: nullable paragraphs.signal_provenance jsonb (migration 041) records { signalUsed, agreed }agreed computed against the final post-correctMisalignedArticle resolution. NULL = honestly unscored, never a fake number. The formula can improve without migration or reparse.
  • Pure scorer src/parser/docx/hierarchy-confidence.ts: base = winner reliability tier (0.95/0.85/0.6/0.6/0.35), bounded corroboration bonus, severity-weighted conflict penalty (ilvl-distance scaled), clamp [0,1]. Evidence strings name signals, never vendors.
  • Surfaces (contract lockstep): meta.inference on every paragraph read (GET /specs/{id}, paragraph write responses, MCP get_paragraph/get_spec) and a hierarchy section in the onboarding report (REST job + MCP get_onboarding_report), review threshold 0.6, worst-first triage list. SEC-sourced specs read as "explicit structure", never suspect; unscored always carries its reason.
  • Frozen revision snapshots retain inference (regression-pinned); project clones copy provenance; conflicts untouched ("persisted, never dropped").

Invariants proven

  • Zero classification drift: fixture A/B over the 703-file corpus — 0/703 fixtures changed, snapshots byte-identical.
  • Null in → null out, score ∈ [0,1], monotonic in corroboration, antitonic in disagreement — table-driven tests.
  • Evidence never names a vendor — test-enforced regex.

Testing

  • Unit tests pass (1463/1463)
  • Integration tests pass (1143 passed / 0 failed / 2 skipped; residual full-suite reds are pre-existing environmental — upload rate-limiter 429s under full-suite volume + leftover fixture rows in the shared dev DB)
  • Migration 041 reversibility proven live (down/up)
  • Fixture corpus A/B: 0/703 changed
  • Contract gate green (openapi + MCP INV-1/2/3)
  • CI green
  • Manual verification: import a DOCX master → report hierarchy section + meta.inference on tree reads

🤖 Co-authored by Claude Fable 5 (planned via 5-reader understand workflow; implemented by 8 sequential task agents each adversarially verified; 3-lens adversarial review, 6 findings fixed). Closes #412.

Summary by CodeRabbit

  • New Features
    • Added hierarchy-inference confidence to onboarding reports (scored/unscored/below-threshold) with worst-first low-confidence review items.
    • Exposed per-structural node inference metadata (confidence, agreed/winning signals, evidence) in API and MCP responses, including revision snapshot preservation.
  • Bug Fixes
    • Inference is omitted for unscored/non-structural content, with consistent unscored behavior.
  • Documentation
    • Added ADR-055 and updated architecture/design docs to describe winner provenance and threshold triage behavior.
  • Tests
    • Expanded unit/integration and contract coverage for scoring, onboarding report fields, and snapshot retention.

thewrz and others added 11 commits July 7, 2026 12:01
…e jsonb + read-time scorer

Approved brainstorm output: persist {signalUsed, agreed} per paragraph,
derive 0-1 confidence + evidence at read time, surface in onboarding
report triage + paragraph meta. ADR-054 to follow with implementation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closes-tracking: #412

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Migration 041 adds paragraphs.signal_provenance (jsonb, NULL = honestly
unscored). Write path threads {signalUsed, agreed} through flattenDfs/
insertTree and cloneParagraphs; read paths (getSpecTree, ancestors,
subtree) derive meta.inference at read time via the shared
deriveInference helper, so the scoring formula can improve without
migration or reparse (ADR-055).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tion

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… contract prose

Address six verified adversarial-review findings on the hierarchy-confidence
branch (ADR-055):

1. snapshotMemberTrees now SELECTs signal_provenance, so frozen revision trees
   retain meta.inference instead of silently unscoring every node.
2. buildNodeTree validates node_type via NodeTypeSchema (loud DatabaseError on
   mismatch) once per row, feeding the validated NodeType to both the type field
   and deriveInference — removing the unchecked `as NodeType` casts. Fixture rows
   that used the non-enum 'paragraph' placeholder are corrected to 'pr1'.
3. get_spec MCP tool description documents meta.inference on structural nodes.
4. hierarchy-summary's unscored reason is honest about all causes (pre-provenance
   parse, non-DOCX source, or manually inserted) while keeping the DOCX re-import
   upgrade path; openapi unscoredReason prose updated to match.
5. openapi SpecNodeInference absence list drops the incorrect "hidden" case and
   adds manually inserted paragraphs.
6. openapi importLibraryMaster pipeline prose names the hierarchy-inference
   summary step.

Pinned with a revisions integration regression test asserting a structural node
in a frozen snapshot carries meta.inference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d912a351-3d6b-4114-a2b9-dc62b197d614

📥 Commits

Reviewing files that changed from the base of the PR and between 2ca0e6e and 2a1159a.

📒 Files selected for processing (2)
  • openapi.yaml
  • src/db/queries/specs.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/db/queries/specs.ts
  • openapi.yaml

📝 Walkthrough

Walkthrough

This PR adds persisted hierarchy provenance, computes read-time inference confidence, and exposes a hierarchy summary in onboarding and MCP responses. It also updates schemas, database reads/writes, and tests to carry the new inference fields.

Changes

Hierarchy-Inference Confidence Feature

Layer / File(s) Summary
Design docs and architecture updates
docs/adr/055-..., docs/superpowers/plans/..., docs/superpowers/specs/..., ARCHITECTURE.md
Adds ADR-055, the implementation plan, the design spec, and architecture text for provenance persistence and read-time hierarchy scoring.
AST contracts and normalized-ilvl mapping
src/ast/error.ts, src/ast/types.ts, src/ast/schemas.ts, src/ast/inference-schemas.ts, src/ast/normalized-ilvl.ts, src/ast/index.ts, src/ast/*.test.ts
Defines hierarchy-inference types and schemas, adds the normalized-ilvl mapping, and updates AST exports and tests.
DOCX inference scoring and parse-time meta.inference
src/parser/docx/types.ts, src/parser/docx/hierarchy-confidence.ts, src/parser/docx/inference.ts, src/parser/docx/index.ts, src/parser/index.ts, src/parser/docx/*.test.ts
Adds SignalId/agreed, computes confidence, attaches meta.inference during classification, and extends parser exports and tests.
DB persistence and read-time derivation
src/db/migrations/041_*.ts, src/db/queries/*.ts, src/db/index.ts, src/db/queries/*.test.ts
Adds signal_provenance, persists it through inserts and clones, validates node types, and derives inference on read.
Onboarding hierarchy summary wiring
src/lib/hierarchy-summary.ts, src/lib/jobs.ts, src/api/onboarding.ts, src/mcp/onboarding-handlers.ts, src/mcp/onboarding-tools.ts, src/mcp/tools.ts, src/lib/*.test.ts, openapi.yaml, src/api/*.integration.test.ts, src/mcp/*.integration.test.ts
Adds summarizeHierarchy, extends onboarding report types, and wires hierarchy summaries into REST onboarding jobs, MCP report handlers, and response schemas.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant classifyOne
  participant scoreHierarchyConfidence
  participant paragraphs
  participant buildNodeTree
  participant summarizeHierarchy

  classifyOne->>scoreHierarchyConfidence: signalUsed, agreed, conflicts, nodeType
  scoreHierarchyConfidence-->>classifyOne: SpecNodeInference
  classifyOne->>paragraphs: insert signal_provenance
  buildNodeTree->>paragraphs: select signal_provenance, conflicts
  buildNodeTree->>scoreHierarchyConfidence: deriveInference(provenance, conflicts, nodeType)
  scoreHierarchyConfidence-->>buildNodeTree: meta.inference
  buildNodeTree->>summarizeHierarchy: tree, source
  summarizeHierarchy-->>buildNodeTree: HierarchySummary
Loading

Possibly related PRs

  • wrzonance/SpecR#21: Introduces the DOCX hierarchy inference pipeline that this PR extends with provenance and confidence scoring.
  • wrzonance/SpecR#160: Touches the same paragraph/spec-tree metadata path and conflict-backed inference model.
  • wrzonance/SpecR#166: Updates the copy-on-derive cloning path that now also carries signal_provenance.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: hierarchy-inference confidence with persisted provenance and read-time scoring.
Linked Issues check ✅ Passed The PR implements the linked requirements: nullable signal provenance, read-time scoring, surfaced inference metadata, onboarding hierarchy summary, and signal-based evidence.
Out of Scope Changes check ✅ Passed The doc and test additions are aligned with the stated ADR/design/implementation plan and supporting the hierarchy-confidence feature.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/hierarchy-confidence

Comment @coderabbitai help to get the list of available commands.

@thewrz
thewrz marked this pull request as ready for review July 8, 2026 00:13

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
src/ast/normalized-ilvl.ts (1)

31-33: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Silent fallback to 0 for unmapped node types.

nodeTypeToNormalizedIlvl returns 0 (same as 'part') for any NodeType not present in NODE_TYPE_TO_NORMALIZED_ILVL, since the map is Partial. If a future NodeType variant is added without updating this map, the scorer's CONFLICT_ILVL_STEP distance calculation (src/parser/docx/hierarchy-confidence.ts) would silently compute a wrong penalty instead of failing loudly, undermining the "single source of truth" comment's intent.

Consider throwing (or asserting exhaustively) on an unmapped type instead of silently defaulting to 0, so a future missing mapping surfaces immediately rather than corrupting confidence scores.

♻️ Proposed fix
 export function nodeTypeToNormalizedIlvl(nodeType: NodeType): number {
-  return NODE_TYPE_TO_NORMALIZED_ILVL[nodeType] ?? 0;
+  const ilvl = NODE_TYPE_TO_NORMALIZED_ILVL[nodeType];
+  if (ilvl === undefined) {
+    throw new Error(`nodeTypeToNormalizedIlvl: no mapping for node type "${nodeType}"`);
+  }
+  return ilvl;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ast/normalized-ilvl.ts` around lines 31 - 33, `nodeTypeToNormalizedIlvl`
currently hides missing `NodeType` mappings by defaulting to 0, which can
silently corrupt hierarchy scoring. Update the `NODE_TYPE_TO_NORMALIZED_ILVL`
lookup in `nodeTypeToNormalizedIlvl` to fail loudly for unmapped types instead
of returning a fallback, using an exhaustive assertion or throwing an error when
the map has no entry. Keep the behavior aligned with the “single source of
truth” intent so future `NodeType` additions are forced to update this mapping
and do not affect `hierarchy-confidence` calculations silently.
src/lib/hierarchy-summary.ts (1)

82-99: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consider capping lowConfidence for pathological docs.

lowConfidence is unbounded — a badly-imported DOCX with many low-confidence paragraphs would return the full list in the onboarding report/MCP payload. Since this mirrors the existing editability pattern, it's likely an accepted tradeoff, but a .slice(0, N) cap could bound response size for large specs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/hierarchy-summary.ts` around lines 82 - 99, Cap the lowConfidence
list returned by summarizeHierarchy to avoid unbounded onboarding/MCP payloads
for pathological documents. Keep the existing sort in summarizeHierarchy, then
apply a fixed upper limit before returning lowConfidence, following the same
bounded-response pattern used elsewhere so the counts remain unchanged while the
payload stays small.
src/parser/docx/inference.test.ts (1)

453-468: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider consolidating duplicated ClassifiedParagraph fixture builders.

makeClassified here mirrors near-identical builders in src/parser/docx/consensus-stats.test.ts (para), src/parser/docx/derive-template.test.ts (para), and src/parser/docx/numbering-profile-apply.test.ts (cp) — all four had to be edited in lockstep just to add the new agreed: [] field in this PR. Extracting one shared builder (e.g. in a test-utils module) would prevent this repeated multi-file churn the next time ClassifiedParagraph gains/changes a field.

♻️ Sketch of a shared helper
// src/parser/docx/test-fixtures.ts
export function makeClassifiedParagraph(
  overrides: Partial<ClassifiedParagraph> & Pick<ClassifiedParagraph, 'nodeType' | 'resolvedIlvl'>
): ClassifiedParagraph {
  return {
    paragraph: { text: '', isVanish: false },
    signalUsed: 1,
    conflicts: [],
    agreed: [],
    isVanish: false,
    ...overrides,
  };
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/parser/docx/inference.test.ts` around lines 453 - 468, The test fixture
builder for ClassifiedParagraph is duplicated across multiple docx tests, so
future field changes require repeated edits. Consolidate makeClassified and the
equivalent para/cp helpers into a shared test utility (for example, a docx
test-fixtures helper) and update the tests to use that single builder, keeping
the agreed field and other defaults centralized.
src/db/queries/specs.ts (1)

157-170: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

parseNodeType is duplicated verbatim in paragraphs.ts.

Both copies guard the same DB↔AST node_type invariant and even carry mirrored comments. Extracting a single shared helper (e.g. under src/db/queries/) avoids future drift between the two guards.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/db/queries/specs.ts` around lines 157 - 170, The parseNodeType helper is
duplicated in both specs.ts and paragraphs.ts, so extract the shared DB-to-AST
node_type validation into a single reusable helper under src/db/queries and have
both call sites use it. Keep the current behavior and error handling intact by
moving the NodeTypeSchema safeParse logic and DatabaseError path into the shared
function, then update the existing parseNodeType usages in the relevant query
modules to reference that common helper instead of maintaining two mirrored
copies.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/ast/normalized-ilvl.ts`:
- Around line 31-33: `nodeTypeToNormalizedIlvl` currently hides missing
`NodeType` mappings by defaulting to 0, which can silently corrupt hierarchy
scoring. Update the `NODE_TYPE_TO_NORMALIZED_ILVL` lookup in
`nodeTypeToNormalizedIlvl` to fail loudly for unmapped types instead of
returning a fallback, using an exhaustive assertion or throwing an error when
the map has no entry. Keep the behavior aligned with the “single source of
truth” intent so future `NodeType` additions are forced to update this mapping
and do not affect `hierarchy-confidence` calculations silently.

In `@src/db/queries/specs.ts`:
- Around line 157-170: The parseNodeType helper is duplicated in both specs.ts
and paragraphs.ts, so extract the shared DB-to-AST node_type validation into a
single reusable helper under src/db/queries and have both call sites use it.
Keep the current behavior and error handling intact by moving the NodeTypeSchema
safeParse logic and DatabaseError path into the shared function, then update the
existing parseNodeType usages in the relevant query modules to reference that
common helper instead of maintaining two mirrored copies.

In `@src/lib/hierarchy-summary.ts`:
- Around line 82-99: Cap the lowConfidence list returned by summarizeHierarchy
to avoid unbounded onboarding/MCP payloads for pathological documents. Keep the
existing sort in summarizeHierarchy, then apply a fixed upper limit before
returning lowConfidence, following the same bounded-response pattern used
elsewhere so the counts remain unchanged while the payload stays small.

In `@src/parser/docx/inference.test.ts`:
- Around line 453-468: The test fixture builder for ClassifiedParagraph is
duplicated across multiple docx tests, so future field changes require repeated
edits. Consolidate makeClassified and the equivalent para/cp helpers into a
shared test utility (for example, a docx test-fixtures helper) and update the
tests to use that single builder, keeping the agreed field and other defaults
centralized.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: dc0fda11-a394-4e89-aaac-4ed31cf9b070

📥 Commits

Reviewing files that changed from the base of the PR and between cae7521 and c649afe.

📒 Files selected for processing (40)
  • ARCHITECTURE.md
  • docs/adr/055-hierarchy-inference-confidence.md
  • docs/superpowers/plans/2026-07-07-hierarchy-confidence.md
  • docs/superpowers/specs/2026-07-07-hierarchy-confidence-design.md
  • openapi.yaml
  • src/api/onboarding.integration.test.ts
  • src/api/onboarding.ts
  • src/api/revisions.integration.test.ts
  • src/ast/index.ts
  • src/ast/inference-schemas.ts
  • src/ast/normalized-ilvl.ts
  • src/ast/schemas.ts
  • src/ast/types.ts
  • src/db/index.ts
  • src/db/migrations/041_paragraphs_signal_provenance.ts
  • src/db/queries/associations.integration.test.ts
  • src/db/queries/derive.ts
  • src/db/queries/inference-meta.ts
  • src/db/queries/paragraphs.ts
  • src/db/queries/revisions.ts
  • src/db/queries/specs.test.ts
  • src/db/queries/specs.ts
  • src/lib/hierarchy-summary.test.ts
  • src/lib/hierarchy-summary.ts
  • src/lib/jobs.test.ts
  • src/lib/jobs.ts
  • src/mcp/onboarding-handlers.ts
  • src/mcp/onboarding-tools.ts
  • src/mcp/onboarding.integration.test.ts
  • src/mcp/tools.ts
  • src/parser/docx/consensus-stats.test.ts
  • src/parser/docx/derive-template.test.ts
  • src/parser/docx/hierarchy-confidence.test.ts
  • src/parser/docx/hierarchy-confidence.ts
  • src/parser/docx/index.ts
  • src/parser/docx/inference.test.ts
  • src/parser/docx/inference.ts
  • src/parser/docx/numbering-profile-apply.test.ts
  • src/parser/docx/types.ts
  • src/parser/index.ts

CodeRabbit nitpicks 1+4: nodeTypeToNormalizedIlvl no longer aliases unmapped
types onto part/ilvl-0 (a future NodeType addition now surfaces immediately
instead of silently corrupting conflict-distance penalties), and the verbatim-
duplicated parseNodeType guard is extracted to db/queries/node-type.ts with a
context-named error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thewrz

thewrz commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai Addressed your review in a64aecb — per-finding resolution:

  1. normalized-ilvl silent ?? 0 fallbackFixed. nodeTypeToNormalizedIlvl now throws on an unmapped node type instead of aliasing it onto part/ilvl 0, so a future NodeType addition surfaces immediately rather than silently corrupting conflict-distance penalties. Pinned with unit tests (structural round-trip + throw on note/continuation).
  2. Cap lowConfidenceDeclined. The section deliberately mirrors the editability summary's contract, which is also unbounded; a unilateral cap would silently truncate triage data (against the report's 'never folded into another bucket' honesty rule) and diverge the two sections. If payload size becomes real, bounding belongs to a shared, indicated (e.g. truncated: true) decision across both report sections — happy to file a follow-up if it bites.
  3. Shared ClassifiedParagraph test builderDeclined for this PR. Repo convention keeps test fixture builders inline per test file (src/test-utils/ intentionally holds only the contract harness). Worth revisiting if ClassifiedParagraph grows another field, as a standalone chore.
  4. parseNodeType duplicated verbatimFixed. Extracted to src/db/queries/node-type.ts with a context parameter so the loud error still names the calling mapper (buildNodeTree/buildSubtree).

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
git log --oneline -5 a64aecb 2>/dev/null || echo "commit not found locally"
git show a64aecb --stat 2>/dev/null | head -50

Length of output: 1621


🏁 Script executed:

#!/bin/bash
git show a64aecb -- src/ast/normalized-ilvl.ts
echo "-----"
git show a64aecb -- src/db/queries/node-type.ts

Length of output: 3483


Both fixes look solid — confirmed in commit a64aecb.

@thewrz Confirmed both fixes look good:

  1. nodeTypeToNormalizedIlvl — now throws on an unmapped NodeType instead of silently defaulting to 0/part, with the rationale documented inline and pinned by unit tests. This closes the silent-corruption risk on conflict-distance penalties.
  2. parseNodeType — cleanly extracted to src/db/queries/node-type.ts with a context parameter threaded through from buildNodeTree and buildSubtree, so error messages still identify the calling mapper. Good dedup.

On the two declined items:

  • lowConfidence cap — reasonable rationale, mirroring the unbounded editability summary contract is a fair consistency argument. Agreed a shared truncated flag decision across both sections is the right place to revisit this if payload size becomes a real concern.
  • Shared ClassifiedParagraph test builder — fine to keep inline per repo convention; agreed it's worth a standalone chore if the type grows further.

Nothing further from me on these four items.

[resolve]

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/ast/normalized-ilvl.ts (1)

1-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Two parallel structures risk silent drift.

NODE_TYPE_TO_NORMALIZED_ILVL and NODE_TYPES_BY_NORMALIZED_ILVL encode the same ordering in two independently-maintained literals, despite the comment calling this a "single source of truth." If a future node type (e.g. pr8) is added to only one of them, nothing here enforces consistency, and the round-trip test only exercises entries present in the array — a map-only addition would go unnoticed.

Derive one from the other to guarantee they can't diverge.

♻️ Proposed fix to derive the map from the array
-export const NODE_TYPE_TO_NORMALIZED_ILVL: Partial<Record<NodeType, number>> = {
-  part: 0,
-  article: 1,
-  pr1: 2,
-  pr2: 3,
-  pr3: 4,
-  pr4: 5,
-  pr5: 6,
-  pr6: 7,
-  pr7: 8,
-};
-
 export const NODE_TYPES_BY_NORMALIZED_ILVL: readonly NodeType[] = [
   'part',
   'article',
   'pr1',
   'pr2',
   'pr3',
   'pr4',
   'pr5',
   'pr6',
   'pr7',
 ];
+
+export const NODE_TYPE_TO_NORMALIZED_ILVL: Partial<Record<NodeType, number>> =
+  Object.fromEntries(NODE_TYPES_BY_NORMALIZED_ILVL.map((nodeType, ilvl) => [nodeType, ilvl]));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ast/normalized-ilvl.ts` around lines 1 - 29, The normalized ILVL ordering
is duplicated in NODE_TYPE_TO_NORMALIZED_ILVL and NODE_TYPES_BY_NORMALIZED_ILVL,
so they can drift out of sync. Update the logic in normalized-ilvl.ts to derive
one structure from the other using the shared NodeType ordering, keeping the
canonical list in a single place. Make sure the exported constants stay
consistent for the inference engine, hierarchy-confidence scorer, and report
summarizer.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/ast/normalized-ilvl.ts`:
- Around line 31-40: Replace the generic Error thrown in
nodeTypeToNormalizedIlvl with a module-local SpecrError to match the typed-error
convention. Add an AST-specific error type in src/ast and use it for the missing
NODE_TYPE_TO_NORMALIZED_ILVL mapping failure path in nodeTypeToNormalizedIlvl.
Keep the existing guard and message semantics, but ensure the failure is raised
through the new SpecrError class so callers can discriminate this error type
consistently.

---

Nitpick comments:
In `@src/ast/normalized-ilvl.ts`:
- Around line 1-29: The normalized ILVL ordering is duplicated in
NODE_TYPE_TO_NORMALIZED_ILVL and NODE_TYPES_BY_NORMALIZED_ILVL, so they can
drift out of sync. Update the logic in normalized-ilvl.ts to derive one
structure from the other using the shared NodeType ordering, keeping the
canonical list in a single place. Make sure the exported constants stay
consistent for the inference engine, hierarchy-confidence scorer, and report
summarizer.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 93ae7c2f-eb16-4e43-a427-fee0b3462103

📥 Commits

Reviewing files that changed from the base of the PR and between c649afe and a64aecb.

📒 Files selected for processing (5)
  • src/ast/normalized-ilvl.test.ts
  • src/ast/normalized-ilvl.ts
  • src/db/queries/node-type.ts
  • src/db/queries/paragraphs.ts
  • src/db/queries/specs.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/db/queries/paragraphs.ts

Comment thread src/ast/normalized-ilvl.ts
thewrz and others added 3 commits July 7, 2026 18:07
CodeRabbit follow-up: nodeTypeToNormalizedIlvl threw a raw Error at a module
surface, against the SpecrError typed-error convention. Adds src/ast/error.ts
(AstError extends SpecrError, mirroring parser/error.ts) exported via the
barrel; test pins the type.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The "single source of truth" comment claimed one canonical ordering, but the
lookup map and the by-ilvl list were two independently-maintained literals that
could silently drift (a map-only node-type addition would slip past the
round-trip test). Derive NODE_TYPE_TO_NORMALIZED_ILVL from the authoritative
NODE_TYPES_BY_NORMALIZED_ILVL so the two can never diverge. Output-identical:
0/703 fixture-corpus classification drift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Soft-removal (PATCH .../removal) sets vanish on a single node with no cascade to
descendants, and the owner-facing renderers suppress the whole vanished subtree
(renderPrNode returns '' before recursing). The hierarchy-summary walker skipped
the vanished node itself but still recursed into its children, so
get_onboarding_report could count and flag paragraphs that no longer render —
telling a reviewer to inspect a low-confidence paragraph that was already
removed. Prune the entire subtree at a vanish node, matching reporting.ts's
"vanish ∪ descendants" exclusion. Regression test pins a scored, low-confidence
descendant under a removed parent staying out of the report.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thewrz

thewrz commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Review loop — remaining items handled

CodeRabbit body nitpick (2nd review): src/ast/normalized-ilvl.ts 1-29 — "two parallel structures risk silent drift"

Fixed in d01db39. NODE_TYPE_TO_NORMALIZED_ILVL is now derived from the authoritative NODE_TYPES_BY_NORMALIZED_ILVL list via Object.fromEntries, so the two can no longer diverge — the "single source of truth" comment is now literally true. Output-identical: 0/703 fixture-corpus classification drift; unit round-trip test still green.

Codex (GPT-5.5, xhigh) — additional eyes — 1 finding

  • [P2] Hidden descendants counted by the hierarchy summarysrc/lib/hierarchy-summary.ts. Confirmed valid, fixed in 2ca0e6e. Soft-removal (PATCH .../removal) sets vanish on a single node with no cascade, and the owner-facing renderers suppress the entire vanished subtree (renderPrNode returns '' before recursing). The summary walker skipped the vanished node itself but still recursed into its children, so get_onboarding_report could count and flag paragraphs that no longer render — telling a reviewer to inspect an already-removed low-confidence paragraph. The walker now prunes the whole subtree at a vanish node, matching reporting.ts's "vanish ∪ descendants" exclusion. Pinned with a regression test (scored low-confidence descendant under a removed parent stays out of the report).

Both changes verified locally: pnpm lint + full unit suite (1466 passed).

🤖 Handled by Claude Fable 5 in the review-remote-pr loop.

Resolves openapi.yaml import-description conflict as the union of #417's
re-import revival prose and #418's hierarchy-confidence prose.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thewrz
thewrz merged commit 95058d9 into main Jul 8, 2026
5 checks passed
@thewrz
thewrz deleted the feat/hierarchy-confidence branch July 8, 2026 05:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hierarchy-inference confidence: provenance jsonb + read-time scorer

1 participant