| id | 2606022124 | |
|---|---|---|
| title | Section schema — unify entry shape under `heading:` discriminator | |
| status | ✅ | |
| model | opus | |
| depends-on |
|
|
| summary | Collapse the section-entry vocabulary to one discriminator (`heading:` — null, string, or mapping). Match with `regex:`, a CUE expression evaluated as a raw-interpolation string. Two helpers in scope: `digits` (named numeric capture) and `fmvar(name)` (frontmatter variable, regex-escaped). Quantify with `repeat: {min, max}`. `sequential:` survives as a sibling. Drop `aliases:`, `required:`, the `{unlisted: true}` mapping, scope-level `repeats:`/`min:`/`max:`, and `require.filename:` (which flattens to top-level `filename:`). Hard cutover; rewrite in-repo callers in the same PR. |
Make a section schema read as a quantified
regex over the document's heading sequence.
One discriminator. One matcher. One
cardinality field. The old shape had six
sibling fields plus three forms of heading:.
Plan 146 shipped the engine. The vocabulary
accreted around it: required:, aliases:,
{unlisted: true}, repeats:, sequential:,
scope-level min:/max:. Each addressed a
real need but read as ad-hoc knobs.
In-repo usage is small. Five inline kinds in
.mdsmith.yml lines 303–371 use heading: null plus heading: {unlisted: true} plus
required:. One fixture uses aliases:. The
repeat-pattern keys are unused outside
research files. Narrowing now beats narrowing
later.
- Migration tool. Hand-rewrite in this PR.
- Backwards-compat shim. Hard cutover.
content:redesign (plan 149).- Per-scope rule-override stacking.
- Renaming the
<?require filename: ?>directive in proto.md bodies.
Full specification: Section schema reference. The page is committed alongside this plan with an upcoming notice. The notice comes off when implementation lands.
Three axes describe each entry:
- Discriminator —
heading:value (null, string, or mapping). - Matcher —
regex:inside the mapping form. The YAML value is the body of a CUE raw-interpolation string (#"..."#). Backslashes pass through to RE2. Interpolation uses\#(expr). Two helpers in scope:digits(named numeric capture(?P<n>[0-9]+)) andfmvar(name)(frontmatter lookup + regex-escape). - Cardinality —
repeat: { min, max }inside the mapping form.
Plus three migration-only changes:
require.filename:flattens to top-levelfilename:.- Frontmatter-only kinds reject schema-level
closed:. - Every removed key
(
aliases:/required:/unlisted:/ scope-levelrepeats:/sequential:/min:/max:/require:) parse-errors with a "removed; see plan 2606022124" diagnostic naming the replacement.
Diagnostics follow the existing
parse_inline.go style: path prefix,
lowercase message, no trailing punctuation.
Final wording refined in code review.
Files rewritten as part of implementation:
.mdsmith.yml— 5 kinds, lines 303–371.- The runbook fixture under
internal/rules/MDS020-required-structure/good/(usesaliases:). docs/guides/schemas.md— rewrite to describe only the new shape.- The MDS020 README — same.
docs/reference/section-schema.md— remove the "upcoming" notice.- A "superseded" note on plan 146's entry-shape section.
- Update
Scopeininternal/schema/schema.go. DropAliases,Required,Repeats,Sequential(top-level),Min/Max,Wildcard. Add aMatchersub-struct (Regex,Repeat,Sequential). KeepPreambleas a derived flag. - Rewrite the heading-mapping parser in
internal/schema/parse_inline.goto accept{ regex, repeat?, sequential? }. Reject every removed key by name with a "removed; see plan 2606022124" diagnostic naming the replacement. - In the same parser, flatten
require.filename:to top-levelfilename:. Reject schema-levelclosed:on kinds withoutsections:. - Update
internal/schema/parse_file.goso## ?and## ...map to the newMatcher(regex: '.+', plusrepeat: { min: 0 }for...). Keep{n}and{field}token expansion in heading rows. (Scope note: MDS020's file- schema check still routes through its legacyparseSchema/parsedSchemapipeline so the{field}heading/body sync feature survives; this parser is exercised by the schema-package tests and prepares the ground for the cutover in a follow-up plan.) - Rewrite the validator in
internal/schema/validate.goto match the heading sequence as a positional quantified regex. EachMatcherconsumes betweenrepeat.minandrepeat.maxconsecutive headings whose text matchesregex:. Diagnostics point at the entry's source location. - Update fixtures and tests under
internal/schema/andinternal/rules/MDS020-required-structure/. Add one fixture per new parse-time diagnostic. - Rewrite the 5 inline kinds in
.mdsmith.ymland the affected fixture to the new shape. Runmdsmith check .andmdsmith fix .. - Rewrite
docs/guides/schemas.mdand the MDS020 README to describe only the new shape. Every reference toaliases:,required:,unlisted:, scope-levelrepeats:/sequential:/min:/max:, andrequire:is removed; one worked example of the new shape replaces the old one. - Remove the "upcoming" notice from
docs/reference/section-schema.md. Runmdsmith fix CLAUDE.mdso the catalog line for the page survives any title / summary edits. - Add a "superseded" note to plan 146's entry-shape section pointing at this plan and the reference page.
- A section entry uses exactly one of
three shapes:
heading: null,heading: <string>, orheading: { regex, repeat?, sequential? }. -
repeat:omitted → exactly one;repeat: { min: 0 }→ zero or more;repeat: { min: 0, max: 1 }→ optional; bounded forms enforce the bounds. - Regex matching is whole-string
anchored against rendered plain text
(fixture:
## **Overview**matchesregex: 'Overview'). -
{n}expands to a numeric capture;sequential: trueflags out-of-order or gapped numbers. -
{field}interpolates the document's frontmatter value at validate-time. -
heading: nulloutside index 0 parse-errors. -
heading:mapping withoutregex:parse-errors. -
repeat: {},repeat: { max: 0 },min > maxeach parse-error. -
sequential: truewithout{n}parse-errors. - Invalid regex parse-errors with the RE2 message and field path.
- Removed keys each parse-error with a "removed; see plan 2606022124" message naming the replacement.
- The 5 rewritten inline kinds and the rewritten fixture emit the same MDS020 diagnostics they did before the cutover (where the constraint is preserved).
-
docs/guides/schemas.mdand the MDS020 README describe only the new shape —grep -E 'aliases:|required:|unlisted:|repeats:|^require:'against either file returns no matches in prose. -
docs/reference/section-schema.mdreads as a current spec — its "upcoming" notice is removed and the page links fromdocs/guides/schemas.md. -
mdsmith check .reports no diagnostics. - All tests pass:
go test ./.... -
go tool golangci-lint runreports no issues.