feat(generator): SpecsIntact .SEC output renderer#207
Conversation
Add `generateSec(tree, refs?)` — the inverse of the SEC parser. Renders the canonical CSI AST to SpecsIntact .SEC XML so firms can deliver into SpecsIntact-mandated workflows. Faithfulness is defined as AST round-trip: parse → generate → re-parse yields the same tree. The parser is deliberately lossy (it drops MTA/HDR/BRK chrome and SRF inline section numbers), so the renderer emits a clean canonical .SEC rather than reproducing original bytes. Each node type inverts its parser mapping; the tier offset from a parent SPT chooses LST (+1) vs ITM (+2) for leaves, while any node carrying children or a standard ref becomes a nested SPT. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds ChangesSEC XML Generator
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/generator/sec/tier.ts (1)
6-13: 💤 Low valueConsider a more precise type for
TIERto improve type safety.
Readonly<Record<string, number>>allows indexing with any string and types the result asnumber, but at runtimeTIER[unknownType]returnsundefined. SincetierOfcorrectly handles this with?? null, the code is safe, but a more precise type likeReadonly<Partial<Record<NodeType, number>>>would make the optional nature explicit and catch potential misuse at compile time.♻️ Suggested type refinement
-const TIER: Readonly<Record<string, number>> = { +const TIER: Readonly<Partial<Record<NodeType, number>>> = { article: 0,🤖 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/generator/sec/tier.ts` around lines 6 - 13, The TIER constant uses a too-permissive type annotation that accepts any string key but returns undefined at runtime for unknown keys, creating a type safety gap. Change the type of the TIER constant from Readonly<Record<string, number>> to Readonly<Partial<Record<NodeType, number>>> to explicitly express that it only contains specific NodeType keys and that accessing unknown keys may return undefined, which will catch potential misuse at compile time rather than runtime.
🤖 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/generator/sec/index.ts`:
- Line 1: The import statement for SpecNode, SpecTree, and SecRef is importing
directly from the internal types.js file instead of using the barrel export.
Update the import path from ../../ast/types.js to ../../ast/index.js. The three
types are already re-exported from the barrel, so only the import path needs to
change while keeping the imported type names unchanged. This follows the coding
guideline of importing from a sibling module's barrel (index.ts) rather than its
internal files.
In `@src/generator/sec/tier.ts`:
- Line 1: Update the import statement for NodeType to use the barrel export from
the ast module instead of the internal types file. In src/generator/sec/tier.ts,
change the import path from '../../ast/types.js' to '../../ast/index.js' to
follow the module boundary contract. Additionally, apply the same fix to
src/generator/sec/index.ts where SpecNode, SpecTree, and SecRef should also be
imported from their respective barrel files instead of internal implementation
files.
---
Nitpick comments:
In `@src/generator/sec/tier.ts`:
- Around line 6-13: The TIER constant uses a too-permissive type annotation that
accepts any string key but returns undefined at runtime for unknown keys,
creating a type safety gap. Change the type of the TIER constant from
Readonly<Record<string, number>> to Readonly<Partial<Record<NodeType, number>>>
to explicitly express that it only contains specific NodeType keys and that
accessing unknown keys may return undefined, which will catch potential misuse
at compile time rather than runtime.
🪄 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: f74ca854-8918-4166-b9e7-d2cb08252f6b
📒 Files selected for processing (6)
src/generator/index.tssrc/generator/sec/entities.test.tssrc/generator/sec/entities.tssrc/generator/sec/index.test.tssrc/generator/sec/index.tssrc/generator/sec/tier.ts
The SEC renderer imported SpecNode/SpecTree/SecRef (sec/index.ts) and NodeType (sec/tier.ts) directly from ../../ast/types.js, crossing the hard module boundary. Both are re-exported from the ast barrel — switch to ../../ast/index.js per the module-boundary contract. Also tighten TIER from Readonly<Record<string, number>> to Readonly<Partial<Record<NodeType, number>>> so unknown-key access is typed undefined at compile time (tierOf already guards with ?? null). Addresses CodeRabbit review on #207. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
CodeRabbit nitpick addressed (sec/tier.ts TIER type): fixed in 3d58724 — |
Why
SpecR parses SpecsIntact
.SECbut could not generate it. Firms delivering into SpecsIntact-mandated workflows need the inverse path. Kept on backlog per ADR-019 (explicitly not declined). This adds the AST →.SECrenderer.What
New
generateSec(tree, refs?)insrc/generator/sec/— the inverse ofsrc/parser/sec/, exported fromsrc/generator/index.ts.tree→<SEC><SCN>SECTION …</SCN><STL>…</STL>{parts}</SEC>part→<PRT><TTL>PART N …</TTL>…</PRT>(re-adds thePART Nprefix the parser strips)article/pr*→<SPT><TTL>…</TTL>{refs}{children}</SPT>note→<NTE><NPR>…</NPR></NTE>,continuation→<TXT>…</TXT><LST>, +2 →<ITM>; any node with children or a standard ref → nested<SPT><REF><RID>code</RID><RTL>tail</RTL></REF>anchored to their source nodeencodeXmlEntities(insec/entities.ts) is the exact inverse of the parser'sdecodeXmlEntities— the five XML metacharacters,&first so introduced entities aren't double-escaped.Faithfulness = AST round-trip, not byte-identity. The parser is deliberately lossy (drops
MTA/HDR/BRKchrome,URL/SCPinline wrappers, andSRFinline section numbers), so the renderer emits a clean canonical.SECthat re-parses to the same tree.Scope is rendering only — no scheduling/CLI wiring (out of scope per the issue, on backlog).
Design decisions
(section, title, and each node's type/text/vanish)— since UUIDs are regenerated on every parse. The decisive tests run on the real UFGS fixtures27_41_00.SECand27_10_00.SEC.SPTis always parent_tier+1, whileLSTis +1 andITMis +2 as leaves. The renderer inverts this by computing each leaf child's tier offset from its parent SPT.SPT. Only anSPTcan hold child content or a<REF>; a<LST>/<ITM>leaf cannot. A node whose only "child" is a ref (e.g. anSPTcontaining just a<REF>) parses to a childless AST node, so the ref index is consulted to keep it anSPT.SPTcollapses the gap to +1. This shape never occurs in the UFGS corpus (every tier-gap node is a childlessITM), so it's rendered best-effort as a nestedSPTand documented rather than silently picked.Testing
pnpm test— 953 passed, incl. 14 new insrc/generator/sec/)pnpm lint)27_41_00.SEC,27_10_00.SEC): parse → generate → re-parse yields an identical tree(code, text)🤖 Co-authored by Claude Opus 4.8. Closes #108.
Summary by CodeRabbit
generateSecto produce canonical SpecsIntact.SECXML with nested sections, parts, articles, and list/item structures.generateSecfrom the generator module’s public API.