Skip to content

feat(lib): section-number module — expanded-shape validator + normalizer#114

Merged
thewrz merged 7 commits into
mainfrom
feat/section-number-lib
Jun 6, 2026
Merged

feat(lib): section-number module — expanded-shape validator + normalizer#114
thewrz merged 7 commits into
mainfrom
feat/section-number-lib

Conversation

@thewrz
Copy link
Copy Markdown
Contributor

@thewrz thewrz commented Jun 6, 2026

Summary

  • New pure module 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.
  • ADR-020: expanded shape as opaque normalized string, exact-match linking only; structured-type alternative rejected. Registered in CLAUDE.md.
  • Design spec + implementation plan for the full lift (docs/superpowers/).
  • One pinned // KNOWN AMBIGUITY: in free prose, Section 26 00 13.10 20 mm reads 20 as an agency suffix. Tagged .SEC <SRF> refs are immune.

Why: 36% of the UFGS reference corpus (239/665 files) carries a suffix; 01 33 23 and 01 33 23.33 are 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 semantics
  • pnpm 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-parsersfeat/section-number-apifeat/section-number-db).

Summary by CodeRabbit

  • New Features

    • Standardized section-number parsing, normalization, validation, and extraction across the app.
  • Documentation

    • Added ADR, design spec, and implementation plan documenting the section-number expansion standard.
  • Tests

    • Added unit and integration tests covering parsing, normalization, extraction, and schema validation.
  • Chores

    • CI updated to run checks for all pull requests (no base-branch filter).

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 6, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Adds ADR-020, a design and multi-PR plan, implements src/lib/section-number.ts with normalized canonical regex/fragment, corresponding Vitest tests, inserts the ADR into CLAUDE.md, and relaxes the CI pull_request trigger to run on all PRs.

Changes

Section-Number Expansion Initiative

Layer / File(s) Summary
Architectural Decision and ADR-020
CLAUDE.md, docs/adr/020-section-number-expanded-shape.md
ADR-020 records representing expanded section numbers as opaque normalized strings with exact-match-only linking semantics and specifies DB CHECK constraint locations and known ambiguity notes.
Design Specification and Consumer Architecture
docs/superpowers/specs/2026-06-05-section-number-expansion-design.md
Comprehensive design: problem statement, audit, locked decisions, module contract (canonical/tolerant regexes, fragment, normalization, Zod schema), consumer migration sites, DB migration plan (normalize with regexp_replace and add CHECKs), test scope, and four-PR delivery plan.
Section-Number Grammar Module and Test Suite
src/lib/section-number.ts, src/lib/section-number.test.ts
Core exports: SECTION_NUMBER_RE, sectionNumberFragment(), normalizeSectionNumber(), findSectionNumbers(), SectionMatch, and SectionNumberSchema (Zod). Tests validate regex acceptance/rejection, normalization, fragment capture behavior, offset-aware extraction, and schema enforcement.
Multi-PR Implementation Roadmap
docs/superpowers/plans/2026-06-05-section-number-expansion.md
Stepwise PR plan (PR1–PR4) covering module introduction, reference/inference updates, schema and filename changes, DB normalization and constraints, integration tests, and verification checklist.
CI trigger update
.github/workflows/ci.yml
pull_request trigger now runs on all PRs (removed branches: ["main"] restriction) to support stacked sub-MVP PRs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I nibble at regex, tidy each space,
Dots and suffixes kept in their place.
One module to parse, tests to ensure,
Four PRs will steady the rollout for sure. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main contribution: a new section-number module with validator and normalizer functionality for expanded CSI/UFGS section numbers.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/section-number-lib

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/lib/section-number.ts (1)

54-59: ⚡ Quick win

Line 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

📥 Commits

Reviewing files that changed from the base of the PR and between ba99b64 and e3fa3c9.

📒 Files selected for processing (6)
  • CLAUDE.md
  • docs/adr/020-section-number-expanded-shape.md
  • docs/superpowers/plans/2026-06-05-section-number-expansion.md
  • docs/superpowers/specs/2026-06-05-section-number-expansion-design.md
  • src/lib/section-number.test.ts
  • src/lib/section-number.ts

Comment thread docs/superpowers/plans/2026-06-05-section-number-expansion.md Outdated
Comment thread docs/superpowers/plans/2026-06-05-section-number-expansion.md Outdated
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.

1 participant