You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implement schema composition across multiple kinds (plan 156) (#288)
* Plan 156: composable required-structure schemas (engine)
Adds the composition engine so a file resolved by multiple kinds
can get the union of every kind's required-structure schema
instead of the last one winning.
- internal/schema.Compose merges frontmatter (CUE conjunction for
shared keys), sections (merge by heading label; `## ...` slots,
bare `?`, preamble stay distinct), Closed (stricter wins),
Matcher cardinality (required-by-any wins), Filename (first
non-empty; conflicts error), CrossReferences/Acronyms/Index
(acronyms: document-wide scope wins). Built against the
plan-156 #295 unified `heading:` discriminator model.
- The config merge layer accumulates each layer's `schema:` /
`inline-schema:` into an append-mode `schema-sources` list via
the new rule.SettingsTranslator interface, so internal/config
carries no rule-name special case (mirrors rule.ListMerger).
- MDS020 loads every source, composes them, validates the
composed schema; single-source keeps the legacy file/inline
paths; multi-source Fix writes the composed Index side-output.
- docs: schemas guide gains a "Composition across kinds" worked
example; cross-system doc records the contract.
Deferred: wiring directive-rule-readme to compose on top of
rule-readme. main now keeps Meta-Information last with Pattern
before it (#295/#302); appending directive-proto.md's Pattern
after rule-readme's Meta-Information would order it wrong. That
schema restructuring (review comment 2) is tracked separately;
.mdsmith.yml and directive-proto.md stay on main's standalone
directive-rule-readme schema for now.
Full suite, lint, and mdsmith check green.
* test: cover compose.go mergeMatcher/cloneContent branches
codecov/changes flagged 8 uncovered lines in internal/schema/
compose.go — the schema-model port's mergeMatcher and cloneContent
helpers. Add targeted same-package unit tests for: a/b nil matcher
arms, min/max widening (required-wins, wider-max, optional-both,
bounded+unbounded), Sequential OR, and the Columns deep-copy
branch. compose.go is now 100% line and branch covered (gobco
clean); full suite, lint, and mdsmith check green.
* fix: preserve dual-schema-source guard through layer translation
Copilot review (PR #288): when a single config layer sets both a
non-empty `schema:` and a non-empty `inline-schema:`,
extractSchemaSourceFromSettings returned at the `schema` arm and
TranslateLayerSettings then stripped both keys, silently dropping
the inline source. The rule's rejectDualSchemaSettings guard in
ApplySettings was bypassed because translation removed the keys
before ApplySettings ran, and top-level cfg.Rules / overrides /
convention presets are not covered by validateKindSchemaSources.
TranslateLayerSettings now detects a dual-source layer
(hasDualSchemaSource, mirroring rejectDualSchemaSettings'
non-empty semantics) and passes the layer through untouched, so
the keys survive deep-merge and the existing guard still surfaces
the original "cannot set both" config error. Cross-layer
composition is unaffected — the check only fires when one map
carries both. Added regression tests; new code is 100% line and
branch covered.
* test: direct branch unit tests for archetype/schema-source/kinds
isLikelyArchetypeName, extractSchemaSourceFromSettings, and
EffectiveKinds were only exercised indirectly via call sites,
leaving codecov/changes flagging per-file coverage drift. Add
table-driven unit tests that hit every return path directly so
the three functions report 100% line and branch coverage.
https://claude.ai/code/session_01C4XwUp4AkhzqjrvSSHMMZS
* fix: intersect composed cardinality and error on disjoint ranges
mergeMatcher widened the run-length max (took the larger of the two
maxima), which broke the composition contract "every input's
constraint holds": composing 1..3 with 5..10 silently yielded 5..10,
dropping the ..3 cap. Make cardinality a true intersection — min is
the larger bound, max is the smaller (0 = unbounded), and disjoint
ranges return a composition error, mirroring how conflicting filename
patterns surface. Thread the error through mergeScopes and
composeSectionLists; flatten the latter behind a section accumulator
so the added error paths stay within the complexity budget.
Also relocate the misplaced TestApplySettings_SchemaSourcesList doc
comment to its function so it no longer stacks above an unrelated
test.
https://claude.ai/code/session_01C4XwUp4AkhzqjrvSSHMMZS
* docs: file plan 169 for rule-README Meta-Information sync
PR #288 review raised that Meta-Information must be the terminal
section and its bullets should render from frontmatter. The
rule-readme schema permits later sections and MDS020's file-schema
path still uses the legacy parser, so frontmatter body-sync is
unwired. Scope that out of the plan-156 PR into its own plan rather
than expanding #288's blast radius across all rule READMEs.
https://claude.ai/code/session_01C4XwUp4AkhzqjrvSSHMMZS
---------
Co-authored-by: Claude <noreply@anthropic.com>
0 commit comments