feat(lib): section-number module — expanded-shape validator + normalizer#114
Conversation
… PRs, TDD task breakdown
📝 WalkthroughWalkthroughAdds ADR-020, a design and multi-PR plan, implements ChangesSection-Number Expansion Initiative
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/lib/section-number.ts (1)
54-59: ⚡ Quick winLine 54-59: avoid mutable accumulation to match the immutable-pattern rule.
out.push(...)mutates local state; the guideline here requires immutable construction.Proposed refactor
export function findSectionNumbers(text: string): readonly SectionMatch[] { const re = new RegExp(FRAGMENT, 'g'); - const out: SectionMatch[] = []; - for (const m of text.matchAll(re)) { - const value = normalizeSectionNumber(m[1] ?? ''); - // RegExpMatchArray.index is optional in TS lib typings — guard for strict mode - if (value !== null && typeof m.index === 'number') out.push({ value, index: m.index }); - } - return out; + return Array.from(text.matchAll(re)).flatMap((m) => { + const value = normalizeSectionNumber(m[1] ?? ''); + // RegExpMatchArray.index is optional in TS lib typings — guard for strict mode + return value !== null && typeof m.index === 'number' ? [{ value, index: m.index }] : []; + }); }As per coding guidelines, "
Use immutable patterns: create new objects, never mutate. Use spread operators, not property assignment".🤖 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/section-number.ts` around lines 54 - 59, The current loop in section-number.ts mutates local array out via out.push; change to an immutable construction by replacing the for...of push with a single expression that builds a new Array<SectionMatch> (e.g., use Array.from/text.matchAll + .reduce or [...text.matchAll(re)].flatMap/.filter/.map) and return that new array; preserve the same checks (call normalizeSectionNumber(m[1] ?? ''), test value !== null and typeof m.index === 'number') and produce objects with { value, index: m.index } instead of mutating with out.push.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.
Inline comments:
In `@docs/superpowers/plans/2026-06-05-section-number-expansion.md`:
- Line 14: Inline code spans contain leading/trailing spaces (e.g., the
backticked token `SECTION `) which triggers markdownlint MD038; remove those
internal spaces so the code span reads `SECTION` instead of `SECTION ` and
similarly trim any other backticked tokens with extra whitespace (the other
occurrence noted uses the same symbol). Locate the backtick-delimited instances
(search for the exact string `SECTION ` and any backticked tokens with
surrounding spaces) and replace them with the trimmed form, then re-run your
markdown linter to confirm MD038 is resolved.
- Around line 307-309: The fenced code block containing
"020-section-number-expanded-shape.md" is unlabeled and triggers MD040; update
the opening fence to include a language identifier (e.g., change ``` to ```text)
so the block becomes a labeled code fence and resolves the markdownlint warning
for the block that displays 020-section-number-expanded-shape.md.
---
Nitpick comments:
In `@src/lib/section-number.ts`:
- Around line 54-59: The current loop in section-number.ts mutates local array
out via out.push; change to an immutable construction by replacing the for...of
push with a single expression that builds a new Array<SectionMatch> (e.g., use
Array.from/text.matchAll + .reduce or
[...text.matchAll(re)].flatMap/.filter/.map) and return that new array; preserve
the same checks (call normalizeSectionNumber(m[1] ?? ''), test value !== null
and typeof m.index === 'number') and produce objects with { value, index:
m.index } instead of mutating with out.push.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 48cfcfc1-6c85-4769-8b5a-8ca90469a796
📒 Files selected for processing (6)
CLAUDE.mddocs/adr/020-section-number-expanded-shape.mddocs/superpowers/plans/2026-06-05-section-number-expansion.mddocs/superpowers/specs/2026-06-05-section-number-expansion-design.mdsrc/lib/section-number.test.tssrc/lib/section-number.ts
Summary
src/lib/section-number.ts— single source of truth for the expanded CSI/UFGS section-number grammar:NN NN NN|NN NN NN.NN(Level 4 dotted) |NN NN NN.NN NN(Level 5 agency: 10=Army, 20=NAVFAC, 30/40=NASA/AFCEC). Exports an anchored validator, a composable scanner fragment (exactly ONE capture group — consumer groups start at 2, pinned by test), a whitespace/NBSP normalizer, a free-text scanner, and a Zod schema.docs/superpowers/).// KNOWN AMBIGUITY: in free prose,Section 26 00 13.10 20 mmreads20as an agency suffix. Tagged.SEC <SRF>refs are immune.Why: 36% of the UFGS reference corpus (239/665 files) carries a suffix;
01 33 23and01 33 23.33are different sections. Downstream PRs fix the silent truncation that collided them.LOC note: the code delta is ~170 lines; the remainder of this PR is the design spec + plan documentation (
docs/superpowers/), which the LOC gate does not exclude.Test Plan
pnpm test src/lib/section-number.test.ts— 30 tests: accept/reject grammar table, normalization (incl. real NBSP bytes), fragment guards (no agency without dot, glued digits, year-after-suffix), capture-group contract pin, multiline separator semanticspnpm lint && pnpm build && pnpm test— full gate green (547 unit tests at this commit)Out of Scope
This PR does NOT change any consumer — parsers, schemas, API, and DB adoption land in the three stacked follow-up PRs (
feat/section-number-parsers→feat/section-number-api→feat/section-number-db).Summary by CodeRabbit
New Features
Documentation
Tests
Chores