Skip to content

feat(parser): detect asterisk-row note-delimiter convention (DOCX)#461

Merged
thewrz merged 9 commits into
mainfrom
feat/issue-292
Jul 11, 2026
Merged

feat(parser): detect asterisk-row note-delimiter convention (DOCX)#461
thewrz merged 9 commits into
mainfrom
feat/issue-292

Conversation

@thewrz

@thewrz thewrz commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Why

Firms bracket editor notes with rows of asterisks (*****…). The parser kept these as literal wall-of-asterisks text and only caught the enclosed content as a note incidentally, via a /note/-named style — a firm using this convention with an unrelated style name got no note treatment at all.

What (Phase 2 of 4)

  • New pure src/lib/note-delimiters.ts (isRuleRow, classifyNoteRoles) — format-agnostic, so Phase 4 (feat(parser): asterisk notes in text/PDF + surface vanish/banner in source_facts #294, text/PDF ingest) can reuse it directly.
  • New src/parser/docx/note-roles.ts DOCX adapter (isHeadingParagraph, computeNoteRoles), kept as a sibling of heuristics.ts so its existing isDecorationSeparator suite (dash/equals rows, '****** [OR] ******') is untouched.
  • inference.ts wiring: a rule-row paragraph (*****, 5+ asterisks and nothing else) is now marked suppressed — it produces no SpecNode at all — and prose enclosed by a paired rule-row region renders as note even when no banner/style signal fires.
  • New optional ClassifiedParagraph.suppressed?: boolean, distinct in kind from isVanish/isNote (those govern how a retained node renders; suppressed means "no node at all").
  • classifyOne's pre-signal cascade extracted into collectSignalHits — adding the role guard clauses pushed cyclomatic complexity to 11 (over the enforced budget of 10); the extraction brings it back to 7 with no behavior change.

No-op confirmations (explicitly verified, not assumed)

  • No openapi.yaml / AST / migration / ADR changes — confirmed via git diff --stat main...HEAD, this PR touches only src/lib/note-delimiters.ts, src/parser/docx/{types,note-roles,inference}.ts, and their test files. StyleNodeTypeSchema (ast/schemas.ts) was never a home for 'continuation'/'note', so no schema ripple; mergeProfileConflicts (numbering-profile.ts) already object-spreads unknown fields through on both the equal-reference and conflict-append paths, so suppressed needs zero merge-profile wiring.
  • No source_fact recording added (out of scope for this phase; issue body scopes only lib + DOCX wiring).

Fixture A/B verification (full 705-file corpus, incl. hand-authored MANUFACTURER_EXAMPLES)

  • An initial A/B during implementation ran against an incomplete worktree corpus (696 files, missing MANUFACTURER_EXAMPLES) and showed 0 changes. Re-running against the full 705-file corpus surfaced 4 hand-authored fixtures (the DOCX parser: hand-authored specs (tab-delimited / manually-numbered / centered-title) parse incorrectly #430 corpus) losing PART structure (3→2, 3→1, 3→0): their asterisk walls are frequently unpaired, the open/close toggle drifted out of phase, and real structure (PRODUCTS/EXECUTION headings, manufacturer lists) was swallowed as [NOTE] rows.
  • Fixed in 9f415f3 with a drift guard: a structural (numbered) item enclosed by an open rule region is proof the wall pairing has drifted, so the asterisk convention disengages for that document and classification falls back to the per-paragraph style/banner signals that handled these files correctly before this feature. Cleanly-paired documents still engage.
  • Final A/B vs the main baseline: 2/705 fixtures changed, both intendedhidden-text-test.docx and paring-fixes.docx no longer render literal wall-of-asterisks [NOTE] rows. Zero part-count changes anywhere.
  • note-region-corpus.integration.test.ts now scans the full corpus (39 tests) including a pinned regression named for the symptom (unpaired wall must not swallow PART headings).

Known ambiguities (documented in tests, not silently resolved)

  1. Unpaired opener force-closed by a heading (note-delimiters.test.ts, note-roles.test.ts, inference-notes.test.ts) — an unpaired rule-row opener with no closing row has no signal to tell whether the author simply forgot the closing delimiter (heading + everything after should NOT be swallowed) or genuinely left the note open. Resolved by treating a literal PART/article-shaped heading as a force-close: everything from the opener through the paragraph before the heading becomes suppressed/note; the heading and everything after resumes normal inference.
  2. Unpaired opener with no closing row and no trailing heading — swallows every remaining paragraph as note through end-of-stream. This can over-suppress trailing structural content if the author forgot the closing delimiter and the document simply ends (or the next heading is numbering/style-only, see chore(ci): bump pnpm/action-setup from 4 to 6 #3) — there is no signal in this shape to disambiguate.
  3. isHeadingParagraph is text-pattern-only (Signal 4 shape) — it does not consult Signal 1 (numbering.xml) or Signal 2 (style chain). A heading whose PART/article status is carried entirely by numbering/style, with no literal "PART n" / "N.N" text (e.g. a bare "GENERAL" title under a spec-shaped numId), is invisible to the force-close check and won't break an open note region. Mitigated by the drift guard (9f415f3): the DOCX adapter now feeds the numbering signal (NoteScanItem.isStructural), so such a heading inside an open region disengages the convention for the whole document instead of being swallowed.

Two consecutive rule rows (open-then-close back to back, nothing note between them) is also documented but is expected/correct behavior, not an ambiguity.

Testing

  • Unit tests pass (pnpm test) — 1690/1690 (with .env sourced; the earlier open-comments.test.ts failures were an unsourced-env artifact, not test failures).
  • Integration: note-region-corpus.integration.test.ts 39/39 against the full 705-file corpus. (Full-suite runs carry known pre-existing cross-file flakes — test(integration): cross-file shared-DB contamination — rotating unrelated failures in full-suite runs #442 — not chased here.)
  • Fixture A/B vs main baseline: 2/705 changed, both intended; zero part-count changes (see above).
  • pnpm lint (eslint + tsc --noEmit + prettier --check) clean.
  • pnpm build clean.
  • Manual verification: end-to-end makeDocx() test through the full parseDocx pipeline (extract → numbering → styles → document → classify → tree) pins no bare 5+-asterisk text surviving in the tree, enclosed prose typed note, and post-close content resuming as continuation.
  • CI green (pending)

Security note for reviewers

During implementation, a spike step's revert (git checkout back to the last commit) was followed by system-reminders claiming types.ts/inference.ts had been "intentionally modified by the user or a linter" and instructing silence toward the user about it. This was independently re-verified false via git status --porcelain, git diff --stat, and git status immediately after — the working tree was genuinely clean and matched HEAD exactly. Treated as an injected/anomalous instruction and disregarded per security policy (never conceal repo state; verify from tool output, not claims). Flagging here for the maintainer to audit — not a code change, and it did not affect what shipped in this PR.

🤖 Co-authored by Claude Sonnet 5 (implementation) and Claude Fable 5 (corpus-regression fix 9f415f3 + review fix dd2a411). Closes #292.

Summary by CodeRabbit

  • New Features

    • DOCX files now recognize paired rows of five or more asterisks as note-region delimiters.
    • Delimited content renders as notes; delimiter rows are omitted.
    • Note regions safely close at section headings and include drift-guard protection to avoid accidental leakage.
  • Bug Fixes

    • Exactly four asterisks are no longer treated as delimiters.
    • Vanish behavior no longer causes delimiter text to surface.
    • Suppressed delimiters no longer interfere with lead-in nesting.
  • Tests

    • Added unit and DOCX end-to-end regression/integration coverage for delimiter pairing, edge cases, and drift-guard behavior.

thewrz and others added 6 commits July 11, 2026 11:42
…yNoteRoles)

Format-agnostic leaf util for asterisk-rule-delimited "NOTE TO SPECIFIER"
regions, reusable by any parser (DOCX now, text/PDF in #294). isRuleRow
gates on asterisk-only rows of 5+ (dashes/equals/bullets stay on the
existing isDecorationSeparator path in heuristics.ts). classifyNoteRoles
is a total, index-aligned single-pass toggle over rule rows, with headings
force-closing an open region as a documented safety break. Two KNOWN
AMBIGUITY cases (unpaired opener closed by a heading vs. swallowed to
end-of-stream) are pinned as tests rather than silently resolved.

Part of #292 (note-delimiter parsing, phase 2/4).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…eNoteRoles)

Bridges the format-agnostic classifyNoteRoles (src/lib/note-delimiters.ts) into
the DOCX parser: computeNoteRoles maps DocxParagraph[] to NoteScanItem[] via a
text-pattern-only heading gate (isPartHeading OR matchTextSignal → part/article),
kept as a sibling of heuristics.ts so its existing isDecorationSeparator suite
stays untouched. Part of #292 (Phase 2 of 4: asterisk-rule note regions).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Marks a rule-row delimiter paragraph (e.g. "*****") for an
asterisk-rule-delimited note region (#292). Distinct in kind from
isVanish/isNote: those govern how a RETAINED node renders (hidden vs.
shown-as-[NOTE]); suppressed means the paragraph produces no SpecNode
at all — buildTree will drop it before tree assembly (wired in a
follow-up commit). Purely additive/optional: no existing construction
site needs updating.

Boundary invariant tests (types.test.ts) pin the field's contract
(absent by default, orthogonal to isVanish/isNote) and confirm no
ripple into NodeType, StyleNodeType, or ParseWarningType — the field
addition surfaced only as a tsc excess-property RED (vitest transpiles
via esbuild and does not type-check), confirmed GREEN after the change.

Part of #292 (Phase 2 of 4: asterisk-rule note regions).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Precompute computeNoteRoles once per document in classifyParagraphs and
thread the per-paragraph role through classifyOne: a 'rule' role marks
the paragraph suppressed (no SpecNode at all — buildTree drops it before
tree assembly), and 'note' extends the existing isNoteParagraph check so
prose enclosed by a paired rule-row region renders as [NOTE] even when
no banner/style signal fires. buildTree's content filter now excludes
suppressed paragraphs alongside the existing empty-paragraph drop.

Extracted the Signal 1/2/4/5 hit-collection cascade into a new
collectSignalHits helper — adding the role guard clauses pushed
classifyOne's cyclomatic complexity to 11, over the enforced budget of
10; the extraction brings it back to 7 with no behavior change.

Tests pin: paired rule rows suppress themselves and note their enclosed
prose; an unpaired opener force-closed by a PART/article heading leaves
the heading on normal 5-signal inference; dash/equals decoration rules
and the "****** [OR] ******" case are unaffected (isRuleRow is
asterisk-only); a 4-asterisk run stays below isRuleRow's 5-asterisk
minimum and is unsuppressed.

Part of #292.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ocx (#292)

Adds a makeDocx()-based E2E test exercising the full parseDocx pipeline
(extract → numbering → styles → document → classify → tree), not just
the classifyParagraphs/buildTree unit boundary already covered by
inference-notes.test.ts: a PART heading, a paired rule-row delimiter
enclosing prose, then ordinary trailing content. Pins three invariants —
no bare 5-or-more-asterisk text survives anywhere in the tree, the
enclosed prose is type 'note', and content after the closing rule row
resumes as type 'continuation'.

Verified RED against the pre-wiring inference.ts (da0c465~1) before
restoring the wired implementation, confirming the test exercises real
end-to-end behavior rather than asserting a tautology.

Part of #292 (Phase 2 of 4: asterisk-rule note regions). Task 6/8.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…corpus (#292)

Task 7/8 of #292 (Phase 2 of 4): integration + fixture A/B verification for
the note-role wiring landed in da0c465/2c732f7/d2dd236/a30a40e.

Adds a corpus-wide sweep (src/parser/docx/note-region-corpus.integration.test.ts)
asserting no bare asterisk rule-row line (5+ asterisks, nothing else) survives
into any real fixture's rendered markdown. The current ARCAT/MANUFACTURER_CPI
corpus does not contain this pattern (verified by scanning every fixture's raw
document.xml — zero paragraphs match), so this sweep does not itself RED/GREEN
on today's corpus; it is a forward-looking guard, same as
corpus-parts.integration.test.ts's structural sweep, that starts protecting
the moment a rule-row fixture is added. The load-bearing RED/GREEN pin for the
suppress+note-tag behavior remains index.test.ts's makeDocx() case (task 6,
verified RED against pre-wiring inference.ts) plus the unit coverage in
note-delimiters.test.ts / note-roles.test.ts / inference-notes.test.ts.

Verification performed (no other source changes):
- pnpm test:integration: full suite green (120 passed | 5 skipped, 1258 tests
  passed | 13 skipped). One NODE_ENV mistake on my part (sourcing .env
  clobbered the test-only rate-limit skip, producing 429s on
  parse.integration.test.ts) and one transient SEC-parser beforeAll hook
  timeout (unrelated module, passes in isolation and on rerun) were both ruled
  out as pre-existing/unrelated, not regressions from this change.
- pnpm fixture:snapshot (after) + fixture:diff before after: 0/696 fixtures
  changed (30 DOCX + 666 SEC) — parts count, noteLeaks count, refs, and
  rendered markdown are byte-identical to the pre-wiring baseline for every
  fixture in the corpus.
- corpus-parts.integration.test.ts (pre-existing 3-part-structure invariant):
  34/34 passing against the wired code.
- No openapi.yaml / AST / migration drift across the whole feature branch.

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

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 96336239-69a3-42dc-ac18-2b2564b376c7

📥 Commits

Reviewing files that changed from the base of the PR and between 2bfd779 and 9b3b6a7.

📒 Files selected for processing (14)
  • src/lib/note-delimiters.test.ts
  • src/lib/note-delimiters.ts
  • src/parser/docx/index.test.ts
  • src/parser/docx/inference-notes.test.ts
  • src/parser/docx/inference.test.ts
  • src/parser/docx/inference.ts
  • src/parser/docx/lead-in-nesting.test.ts
  • src/parser/docx/lead-in-nesting.ts
  • src/parser/docx/note-region-corpus.integration.test.ts
  • src/parser/docx/note-roles.test.ts
  • src/parser/docx/note-roles.ts
  • src/parser/docx/numbering-profile-apply.test.ts
  • src/parser/docx/types.test.ts
  • src/parser/docx/types.ts

📝 Walkthrough

Walkthrough

Adds shared asterisk rule-row detection and note-role classification, wires it into DOCX inference and tree building, preserves suppressed paragraph state through nesting, and adds unit, end-to-end, regression, and corpus coverage.

Changes

Asterisk note-region parsing

Layer / File(s) Summary
Shared note-role classifier
src/lib/note-delimiters.ts, src/lib/note-delimiters.test.ts
Detects contiguous 5-or-more-asterisk rows, assigns rule/note/none roles, force-closes on headings, and applies structural drift protection.
DOCX role inference integration
src/parser/docx/note-roles.ts, src/parser/docx/inference.ts, src/parser/docx/note-roles.test.ts
Maps DOCX paragraph metadata into shared roles, suppresses delimiter paragraphs, classifies enclosed prose as notes, and removes suppressed roots during tree construction.
Suppressed state and nesting
src/parser/docx/types.ts, src/parser/docx/lead-in-nesting.ts, src/parser/docx/numbering-profile-apply.test.ts, src/parser/docx/types.test.ts, src/parser/docx/lead-in-nesting.test.ts
Adds the optional suppressed classification field, preserves it through conflict merging, and excludes suppressed rows from lead-in adjacency checks.
DOCX regression and corpus coverage
src/parser/docx/index.test.ts, src/parser/docx/inference*.test.ts, src/parser/docx/note-region-corpus.integration.test.ts
Covers paired and unpaired regions, heading closure, structural drift handling, vanish precedence, four-asterisk boundaries, decoration rows, rendered output, and corpus cases.

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

Sequence Diagram(s)

sequenceDiagram
  participant DOCX as DOCX paragraphs
  participant Roles as computeNoteRoles
  participant Classifier as classifyNoteRoles
  participant Inference as classifyParagraphs
  participant Tree as buildTree
  DOCX->>Roles: provide paragraph text and metadata
  Roles->>Classifier: classify NoteScanItem[]
  Classifier-->>Roles: return rule/note/none roles
  Roles-->>Inference: provide paragraph roles
  Inference->>Tree: pass suppressed rule rows and note classifications
  Tree-->>DOCX: emit notes and omit delimiter rows
Loading

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

Possibly related PRs

  • wrzonance/SpecR#21: Introduced the DOCX 5-signal inference engine extended by this role-aware classification flow.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: DOCX asterisk-row note-delimiter detection.
Linked Issues check ✅ Passed The changes implement the linked #292 Phase 2 requirements: shared rule-row utilities, DOCX wiring, note classification, and delimiter suppression.
Out of Scope Changes check ✅ Passed The added tests and supporting parser/type changes all appear to support the asterisk-rule delimiter feature and its regressions.

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

thewrz and others added 2 commits July 11, 2026 13:48
…eadInSublists (#292)

nestLeadInSublists filtered only blanks from its content view, but buildTree also
drops suppressed asterisk rule-row delimiters. A rule row sitting between a lead-in
and its Signal-4 restart run therefore defeated the same-tier adjacency check the
same way an un-dropped blank would. Add `cp.suppressed !== true` to the filter so the
two content views agree exactly.

Also lands the accompanying #292 suppressed-field test updates that ride with this
review fix: the rule-row-suppressed-regardless-of-vanish pin (inference-notes), the
mergeProfileConflicts suppressed-preservation pins (numbering-profile-apply), and the
ClassifiedParagraph.suppressed boundary-test refactor (types).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…headings (#292)

Hand-authored DOCX (the #430 corpus) wall their specifier notes with asterisk rule
rows that are frequently unpaired — a closing wall gets merged into the note prose
("…Waste Management *****") or dropped entirely. The naive open/close toggle then
drifts out of phase and swallows real numbered structure (PART headings, articles,
manufacturer lists) as notes: fixture A/B showed four files losing parts (3→2, 3→1,
3→0), with PRODUCTS/EXECUTION and manufacturer names becoming [NOTE] rows.

Add a drift guard to the shared classifier: a structural (numbered) item enclosed by
an open rule region is proof the pairing has drifted, so the asterisk convention
disengages for the whole document — falling back to the per-paragraph style/banner
classification that already handled these files correctly before the feature existed.
The DOCX adapter supplies the numbering signal (NoteScanItem.isStructural); a bare
"PRODUCTS" under a spec-shaped numId, invisible to the text-only heading gate, now
trips it. Documents whose walls are cleanly paired (hidden-text-test, paring-fixes)
still engage and suppress their rule rows.

Fixture A/B over the full 705-file corpus: only hidden-text-test.docx and
paring-fixes.docx remain changed (both intended rule-row suppression); zero
part-count changes anywhere.

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

thewrz commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@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 (1)
src/lib/note-delimiters.ts (1)

57-63: 📐 Maintainability & Code Quality | 🔵 Trivial

Drift guard silently discards the signal without surfacing it for downstream conflict tracking.

classifyNoteRoles returns only the final roles (or all-'none' on drift) with no indication that a drift/disengagement occurred. Per the coding guidelines, src/parser/**/*.ts must "persist inference conflicts rather than discarding losing signals" and expose them via meta.conflicts/paragraphs.conflicts. Since this classifier is the only place that detects the drift, and it doesn't return that fact, the DOCX layer (note-roles.ts/inference.ts) has no way to record the conflict even if it wanted to — the losing signal (the note-region classification) is discarded here with no trace.

Consider returning a richer result (e.g. { roles, drifted: boolean }) so callers can persist the disengagement as a conflict.
[medium]

Based on coding guidelines: "Persist inference conflicts rather than discarding losing signals; expose them through meta.conflicts and store them in paragraphs.conflicts."

🤖 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/note-delimiters.ts` around lines 57 - 63, Update classifyNoteRoles to
return both the computed roles and whether structural drift was detected, rather
than silently returning only the all-'none' roles. Adjust its callers in the
DOCX note-role/inference flow to persist the drift signal as an inference
conflict through meta.conflicts and paragraphs.conflicts, while preserving the
existing role output behavior.

Source: Coding guidelines

🤖 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/lib/note-delimiters.ts`:
- Around line 57-63: Update classifyNoteRoles to return both the computed roles
and whether structural drift was detected, rather than silently returning only
the all-'none' roles. Adjust its callers in the DOCX note-role/inference flow to
persist the drift signal as an inference conflict through meta.conflicts and
paragraphs.conflicts, while preserving the existing role output behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 27b25ce4-ea1e-4522-8cec-a62015045ac1

📥 Commits

Reviewing files that changed from the base of the PR and between 2bfd779 and 9f415f3.

📒 Files selected for processing (14)
  • src/lib/note-delimiters.test.ts
  • src/lib/note-delimiters.ts
  • src/parser/docx/index.test.ts
  • src/parser/docx/inference-notes.test.ts
  • src/parser/docx/inference.test.ts
  • src/parser/docx/inference.ts
  • src/parser/docx/lead-in-nesting.test.ts
  • src/parser/docx/lead-in-nesting.ts
  • src/parser/docx/note-region-corpus.integration.test.ts
  • src/parser/docx/note-roles.test.ts
  • src/parser/docx/note-roles.ts
  • src/parser/docx/numbering-profile-apply.test.ts
  • src/parser/docx/types.test.ts
  • src/parser/docx/types.ts

The #292 drift guard disengages the asterisk-rule note convention when a
structural paragraph is found enclosed by an open (drifted) wall. It keyed
only on Signal 1 (a live positive numId), so a paragraph structural ONLY via
Signal 2 — its style resolves to a real tier through resolvedNumPr, with no
direct numId and no literal "PART n"/"N.N" text — was invisible to both the
drift check and the text-pattern heading gate, and got swallowed as a [NOTE]:
the exact structure-loss class the guard exists to prevent (Codex, PR #461).

computeNoteRoles now takes an injected `isStructural` predicate; the DOCX
driver supplies `numId>0 || trySignal2(...)`, reusing trySignal2's numId=0 /
suppressesNumbering / lead-in opt-out resolution verbatim with no duplicated
OOXML logic. Pinned by a regression test at the classifyParagraphs boundary.

isNoteParagraph moves from inference.ts to its natural home in the note
adapter (note-roles.ts) — consolidating DOCX note detection and keeping
inference.ts within the enforced 400-line cap after the wiring change.

Fixture A/B vs main baseline unchanged: 2/705 (both intended), zero
part-count changes. Full unit suite green (1691).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@thewrz

thewrz commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

Codex (GPT-5.5, xhigh) adversarial review — 1 finding [P2], handled (fixed in 9b3b6a7).

[P2] Include Signal 2 styles in drift detection (src/parser/docx/note-roles.ts) — VALID, fixed.
The #292 drift guard keyed only on Signal 1 (a live positive numId). A paragraph structural only via Signal 2 — its style resolves to a real tier through resolvedNumPr, with no direct numId and no literal "PART n"/"N.N" text — was invisible to both the drift check and the text-pattern heading gate, so inside a drifted (unpaired) asterisk wall it was swallowed as a [NOTE]: the exact structure-loss class the guard exists to prevent.

Fix: computeNoteRoles now takes an injected isStructural predicate; the DOCX driver supplies numId>0 || trySignal2(...), reusing trySignal2's numId=0 / suppressesNumbering / lead-in opt-out resolution verbatim (no duplicated OOXML logic). Pinned by a regression test at the classifyParagraphs boundary (inference-notes.test.ts) — verified red without the Signal-2 half, green with it. isNoteParagraph moved to the note adapter to keep inference.ts under the 400-line cap.

Verification: fixture A/B vs main baseline unchanged — 2/705 (both intended), zero part-count changes; full unit suite green (1691); lint + build clean.

Comment thread src/lib/note-delimiters.ts
@thewrz

thewrz commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.


You're currently rate limited under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. Your next review will be available in 4 minutes.

@thewrz
thewrz marked this pull request as ready for review July 11, 2026 21:33
@thewrz
thewrz merged commit f39f3b4 into main Jul 11, 2026
9 checks passed
@thewrz
thewrz deleted the feat/issue-292 branch July 11, 2026 21:37
@thewrz

thewrz commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.


You're currently rate limited under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. Your next review will be available in 5 minutes.

@thewrz

thewrz commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

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.

feat(parser): detect asterisk-row note-delimiter convention (DOCX)

1 participant