Skip to content

Commit 6852ca2

Browse files
jedudenclaude
andauthored
Add integration test for directive rule example fixtures (#274)
* Enforce bad/good/fixed examples for directive rules Every rule that implements gensection.Directive (MDS019 catalog, MDS021 include, MDS038 toc, MDS039 build) must ship bad/, good/, and — when fixable — fixed/ example folders. A new integration test in internal/integration/directive_examples_test.go walks rule.All() and fails when any of those folders are missing or when bad/ has a fix-able entry with no matching fixed/ counterpart. Non-fixable bad fixtures (cycle detection, build validation) opt out via a bad/.nofix sentinel file. Backfills MDS019-catalog/fixed/ so the loop tests the catalog fix path, and adjusts the MDS019 bad fixture so the post-fix body satisfies the default-enabled blank-line-around-lists rule. The markdown-audit skill (.claude/skills/markdown-audit/) now points at these example folders as the single source of truth for the <?catalog?> and <?include?> fix recipes. Check 6 (kind without schema) gains an inline-schema vs proto.md section that links to the four MDS020 example flavors so the audit reader picks the right shape per kind. https://claude.ai/code/session_01K7ZGjbkxFHTdTekshbN3kP * Add pattern/ folders showing authored→directive transformations The bad/good folders under each rule test diagnostics — bad/* must fire a diagnostic, good/* must pass all rules. That layer is the wrong shape for "what does the hand-maintained anti-pattern look like before someone adopts this directive?", which is what the markdown-audit skill needs to show users. Each directive rule (MDS019 catalog, MDS021 include, MDS038 toc, MDS039 build) now ships a separate pattern/bad/ and pattern/good/ pair: - pattern/bad — the user-authored Markdown someone would write today without the directive (hand-maintained index, duplicated section, hand-maintained TOC, hand-maintained build output snippet). - pattern/good — the same content rewritten with the directive, plus any sibling files the directive needs. The four directive rule READMEs grew a ## Pattern section that <?include?>s the new fixtures. A new directive-rule-readme kind (with proto at internal/rules/directive-proto.md) layers a stricter schema over rule-readme that requires that Pattern section with Without/With subsections. Until kind-schema composition lands (plan/156_kind-schema-composition.md), the directive-rule-readme schema duplicates rule-readme's structure and the four directive READMEs are excluded from the rule-readme glob so only one schema resolves per file. TestDirectiveRulesHaveExamples now also requires pattern/bad/*.md and pattern/good/*.md for every gensection.Directive rule. The markdown-audit skill patterns.md + SKILL.md point at pattern/ as the canonical before/after pair. https://claude.ai/code/session_01K7ZGjbkxFHTdTekshbN3kP * Plan 157: catalog filter by front matter property Files the use case (a "Directive rules" listing inside internal/rules/index.md that selects on a nature property) and the proposed surface (a where: parameter on the catalog directive that reuses the CUE expression matcher from mdsmith list query). https://claude.ai/code/session_01K7ZGjbkxFHTdTekshbN3kP * Inline ## Pattern snippets in directive rule READMEs Copilot flagged that <?include?> on pattern/bad and pattern/good rewrote relative link targets so the displayed paths (e.g. pattern/bad/data/alpha.md) did not match the source files (data/alpha.md). The rewrite is stable so mdsmith fix did not flag the body as stale, but the cosmetic mismatch confuses a reader trying to copy the snippet verbatim. Drop the include directives from the ## Pattern sections. The snippets in each directive README now match the source pattern/ files character-for-character, and the prose points to pattern/bad and pattern/good as the canonical folders the integration test still enforces. Also add bad/.nofix on MDS019 for the new dotdot.md fixture from main (#275). The dotdot escape is a validation-only diagnostic with no auto-fix. https://claude.ai/code/session_01K7ZGjbkxFHTdTekshbN3kP * Inline ## Examples snippets in MDS019 README Copilot flagged that the <?include?> for bad/default.md in the ## Examples section displayed paths with a `bad/` prefix (`[data/alpha.md](bad/data/alpha.md)`), while the source fixture uses `[data/alpha.md](data/alpha.md)`. The displayed body IS what `mdsmith fix` produces — the include rule's adjustLinks rewrites relative link targets so they resolve from the host file's directory. The body is stable, not stale. But the cosmetic mismatch makes the example misleading, and the row template `{filename}` placeholder gets the same rewrite applied. Replace both <?include?> blocks with inline snippets that match the fixture files character-for-character; link to good/default.md and bad/default.md by path for readers who want the source. https://claude.ai/code/session_01K7ZGjbkxFHTdTekshbN3kP * Add where: filter to catalog directive Extend <?catalog?> with a `where:` parameter that evaluates a CUE expression against each matched file's parsed front matter, dropping non-matching files before sort and render. Reuses the matcher from internal/query/ so the catalog and `mdsmith list query` accept the same grammar. Invalid CUE expressions emit an MDS019 diagnostic on the directive's opening line; missing fields and type mismatches silently exclude the file, matching list-query semantics. Covers tasks 1, 5, and 6 from plan/157. https://claude.ai/code/session_01K7ZGjbkxFHTdTekshbN3kP * Add nature key to rule-readme schema and backfill READMEs Add a required `nature` front-matter key to the rule-readme schema (internal/rules/proto.md) and the directive-rule-readme schema (internal/rules/directive-proto.md). The field labels each rule's kind so the catalog directive can filter listings by it. Allowed values: directive, generator, content, style, structure. Backfill `nature:` on every MDS rule README and add a fixture under MDS020 (bad/nature-missing.md) that exercises the new schema requirement. Covers tasks 2 and 3 from plan/157. https://claude.ai/code/session_01K7ZGjbkxFHTdTekshbN3kP * Add Directive rules section to internal/rules/index.md Uses the new catalog where: filter (plan 157 task 1) and the nature front-matter property (plan 157 tasks 2 + 3) to surface a filtered listing of just the four directive rules. Closes the remaining acceptance criterion in plan/157. https://claude.ai/code/session_01K7ZGjbkxFHTdTekshbN3kP * Use catalog where: filter in markdown-audit SKILL Replace the hand-maintained list of four directive rule pattern folders with a <?catalog?> directive that selects every rule README with nature: directive in its front matter. The list now stays in sync with the rule catalog automatically — a new directive rule starts showing up once its README declares nature: directive. https://claude.ai/code/session_01K7ZGjbkxFHTdTekshbN3kP --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 872435a commit 6852ca2

96 files changed

Lines changed: 1453 additions & 77 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/markdown-audit/SKILL.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,30 @@ fix recipe. When a sibling `patterns.md` is
116116
present (project install), read it once at the
117117
start for deeper heuristics and false positives.
118118

119+
For each `<?directive?>` fix, read the rule's
120+
`pattern/bad/` and `pattern/good/` folders. They
121+
hold the canonical before/after pair. The list
122+
below is generated from every rule whose front
123+
matter declares `nature: directive`, so it stays
124+
in sync with the rule catalog automatically:
125+
126+
<?catalog
127+
glob: "../../../internal/rules/MDS*/README.md"
128+
where: 'nature: "directive"'
129+
sort: id
130+
row: "- `internal/rules/{id}-{name}/pattern/` ({name})"
131+
?>
132+
- `internal/rules/MDS019-catalog/pattern/` (catalog)
133+
- `internal/rules/MDS021-include/pattern/` (include)
134+
- `internal/rules/MDS038-toc/pattern/` (toc)
135+
- `internal/rules/MDS039-build/pattern/` (build)
136+
<?/catalog?>
137+
138+
Do not paraphrase directive syntax from memory.
139+
The integration test
140+
`TestDirectiveRulesHaveExamples` enforces that
141+
each pattern folder pair is present.
142+
119143
Do not run `mdsmith check .` from this skill.
120144
That is the content-lint surface and runs
121145
separately.

.claude/skills/markdown-audit/patterns.md

Lines changed: 79 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,17 @@ False positives:
5656

5757
Severity: tax.
5858

59-
Fix: replace the block with a `<?catalog?>`
60-
directive targeting the shared directory.
61-
62-
```markdown
63-
<?catalog
64-
glob: "plan/*.md"
65-
sort: id
66-
row: "- [{title}](plan/{filename})"
67-
?>
68-
<?/catalog?>
69-
```
59+
Fix: use a `<?catalog?>` directive on the shared
60+
directory. Read the canonical before/after pair
61+
from the rule's own pattern folders:
62+
63+
- before (hand-maintained list): `internal/rules/MDS019-catalog/pattern/bad/`
64+
- after (catalog directive): `internal/rules/MDS019-catalog/pattern/good/`
65+
66+
These folders are the single source of truth. The
67+
rule's README `## Pattern` section pulls from
68+
them. A directive-rule integration test fails
69+
when they go missing.
7070

7171
Run `mdsmith fix <file>` and confirm the same
7272
items reappear in the regenerated body.
@@ -143,17 +143,14 @@ False positives:
143143
Severity: tax.
144144

145145
Fix: extract the shared body into one canonical
146-
file under `docs/`. Replace each copy with
147-
`<?include?>`.
148-
149-
```markdown
150-
<?include
151-
file: docs/development/index.md
152-
strip-frontmatter: "true"
153-
heading-level: "absolute"
154-
?>
155-
<?/include?>
156-
```
146+
snippet. Replace each copy with `<?include?>`.
147+
Read the canonical before/after pair from the
148+
rule's own pattern folders:
149+
150+
- before: `internal/rules/MDS021-include/pattern/bad/`
151+
(section duplicated across two files)
152+
- after: `internal/rules/MDS021-include/pattern/good/`
153+
(one snippet plus include directives)
157154

158155
Use `heading-level: "absolute"` when the host file
159156
needs the included headings to nest under an
@@ -211,27 +208,66 @@ in.
211208

212209
Severity: nice-to-have.
213210

214-
Fix:
215-
216-
1. Create `proto.md` in the kind's primary
217-
directory. Copy the shape from an existing
218-
`proto.md` in the repo.
219-
2. Fill the front matter section with required
220-
keys and CUE types.
221-
3. Add a `## <heading>` line per required section.
222-
4. Wire the schema in `.mdsmith.yml`:
223-
224-
```yaml
225-
kinds:
226-
plan:
227-
rules:
228-
required-structure:
229-
schema: plan/proto.md
230-
```
231-
232-
5. Run `mdsmith check .` and confirm every
233-
existing file passes. When a file fails, relax
234-
the schema — do not break the files.
211+
Fix: pick the schema flavor that fits the kind,
212+
then wire it in `.mdsmith.yml`.
213+
214+
Inline schema vs `proto.md` — when to use which:
215+
216+
- Inline schema in `.mdsmith.yml` when the shape
217+
is small (one or two required sections, no
218+
nested headings) or the kind exists only in
219+
this repo. One source file holds everything.
220+
- A `proto.md` file when the schema runs more
221+
than a screen, the required sections nest, the
222+
schema is shared across kinds via
223+
`<?include?>`, or the prose around each
224+
required field is itself useful documentation.
225+
The proto file is itself a lintable Markdown
226+
document.
227+
228+
Both flavors satisfy `required-structure`. Do not
229+
mix the two for a single kind — pick one.
230+
231+
The rule's example folder holds canonical samples
232+
of each flavor under
233+
`internal/rules/MDS020-required-structure/good/`.
234+
Read them before authoring:
235+
236+
- `inline-flat.md` — inline schema with flat
237+
required sections.
238+
- `inline-runbook.md` — inline schema with nested
239+
sections and heading aliases.
240+
- `default.md` plus `data/tmpl.md` — proto file
241+
referenced via the `schema:` setting.
242+
- `schema-compose.md` — proto file composed from
243+
shared fragments via `<?include?>`.
244+
245+
Wire-up for inline schema:
246+
247+
```yaml
248+
kinds:
249+
plan:
250+
schema:
251+
sections:
252+
- heading: "Goal"
253+
required: true
254+
- heading: "Tasks"
255+
required: true
256+
```
257+
258+
Wire-up for proto.md:
259+
260+
```yaml
261+
kinds:
262+
plan:
263+
rules:
264+
required-structure:
265+
schema: plan/proto.md
266+
```
267+
268+
After wiring, run `mdsmith check .` and confirm
269+
every existing file passes. When a file fails,
270+
relax the schema — do not break the files.
235271

236272
## Check 7 — File placement violation
237273

.mdsmith.yml

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ ignore:
258258
- "internal/rules/*/bad.md"
259259
- "internal/rules/*/good/**"
260260
- "internal/rules/*/fixed/**"
261+
- "internal/rules/*/pattern/**"
261262
- ".claude/worktrees/**"
262263
- "editors/**/node_modules/**"
263264
- "editors/**/dist/**"
@@ -290,6 +291,10 @@ kinds:
290291
rules:
291292
required-structure:
292293
schema: internal/rules/proto.md
294+
directive-rule-readme:
295+
rules:
296+
required-structure:
297+
schema: internal/rules/directive-proto.md
293298
skill:
294299
path-pattern: "{.claude/skills,editors/claude-code-audit/skills}/{proto.md,*/SKILL.md}"
295300
rules:
@@ -391,8 +396,25 @@ kind-assignment:
391396
kinds: [proto]
392397
- glob: ["plan/*.md"]
393398
kinds: [plan]
394-
- glob: ["internal/rules/proto.md", "internal/rules/MDS*/README.md"]
399+
- glob: ["internal/rules/proto.md",
400+
"internal/rules/MDS*/README.md",
401+
"!internal/rules/MDS019-catalog/README.md",
402+
"!internal/rules/MDS021-include/README.md",
403+
"!internal/rules/MDS038-toc/README.md",
404+
"!internal/rules/MDS039-build/README.md"]
395405
kinds: [rule-readme]
406+
# The four directive rule READMEs need a stricter schema
407+
# that requires a Pattern section. Until kind-schema
408+
# composition lands (plan/156), the directive-rule-readme
409+
# schema duplicates rule-readme's structure and adds the
410+
# Pattern requirement, and the four READMEs are excluded
411+
# from the rule-readme glob above so only one schema
412+
# resolves per file.
413+
- glob: ["internal/rules/MDS019-catalog/README.md",
414+
"internal/rules/MDS021-include/README.md",
415+
"internal/rules/MDS038-toc/README.md",
416+
"internal/rules/MDS039-build/README.md"]
417+
kinds: [directive-rule-readme]
396418
- glob: [".claude/skills/proto.md", ".claude/skills/*/SKILL.md",
397419
"editors/claude-code-audit/skills/*/SKILL.md"]
398420
kinds: [skill]

PLAN.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,6 @@ footer: |
8484
| 153 || opus | [Unify linkgraph and the LSP symbol index](plan/153_unify-linkgraph-and-lsp-index.md) |
8585
| 154 | 🔲 | sonnet | [arch-fix: extract cross-rule helpers](plan/154_arch-fix-rule-helper-extraction.md) |
8686
| 155 || sonnet | [arch-fix: relocate convention types out of markdownflavor](plan/155_arch-fix-convention-config-ownership.md) |
87+
| 156 | 🔲 | opus | [Composable required-structure schemas across multiple kinds](plan/156_kind-schema-composition.md) |
88+
| 157 | 🔳 | sonnet | [Catalog filter by front matter property](plan/157_catalog-where-filter.md) |
8789
<?/catalog?>

docs/guides/directives/generating-content.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,34 @@ gitignore: "false"
107107
?>
108108
```
109109

110+
### Filtering with `where`
111+
112+
The `where` parameter narrows the matched set by a CUE
113+
expression
114+
evaluated against each file's parsed front matter. The
115+
expression grammar is the same one
116+
[`mdsmith list query`](../../reference/cli/query.md)
117+
accepts, so a CLI query expression drops in unchanged:
118+
119+
```markdown
120+
<?catalog
121+
glob: "internal/rules/MDS*/README.md"
122+
where: 'nature: "directive"'
123+
row: "- [{name}]({filename})"
124+
?>
125+
<?/catalog?>
126+
```
127+
128+
The filter runs after globbing and front matter
129+
parsing, but before sort and render. Failure modes:
130+
131+
- An invalid CUE expression triggers an MDS019
132+
diagnostic on the directive's opening line.
133+
- A file whose front matter is missing the referenced
134+
field is excluded (no diagnostic).
135+
- A field whose value does not satisfy the constraint
136+
(wrong type or wrong value) is excluded.
137+
110138
### Sorting
111139

112140
Format: `[-][numeric:]KEY`. A `-` prefix means

editors/claude-code-audit/skills/markdown-audit/SKILL.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,31 @@ fix recipe. When a sibling `patterns.md` is
120120
present (project install), read it once at the
121121
start for deeper heuristics and false positives.
122122

123+
For each `<?directive?>` fix, read the rule's
124+
`pattern/bad/` and `pattern/good/` folders. They
125+
hold the canonical before/after pair. The list
126+
below is generated from every rule whose front
127+
matter declares `nature: directive`, so it stays
128+
in sync with the rule catalog automatically:
129+
130+
<?catalog
131+
source-dir: ".claude/skills/markdown-audit"
132+
glob: "../../../internal/rules/MDS*/README.md"
133+
where: 'nature: "directive"'
134+
sort: id
135+
row: "- `internal/rules/{id}-{name}/pattern/` ({name})"
136+
?>
137+
- `internal/rules/MDS019-catalog/pattern/` (catalog)
138+
- `internal/rules/MDS021-include/pattern/` (include)
139+
- `internal/rules/MDS038-toc/pattern/` (toc)
140+
- `internal/rules/MDS039-build/pattern/` (build)
141+
<?/catalog?>
142+
143+
Do not paraphrase directive syntax from memory.
144+
The integration test
145+
`TestDirectiveRulesHaveExamples` enforces that
146+
each pattern folder pair is present.
147+
123148
Do not run `mdsmith check .` from this skill.
124149
That is the content-lint surface and runs
125150
separately.

0 commit comments

Comments
 (0)