Add Plan 55 for metrics list/rank and shared abstraction - #22
Merged
Conversation
jeduden
force-pushed
the
codex/plan-55-metrics-rank-cli
branch
from
February 15, 2026 20:09
c2d5edc to
50efd0d
Compare
jeduden
pushed a commit
that referenced
this pull request
May 8, 2026
mdsmith currently uses two schema languages in a single schema file: CUE for front matter, a YAML scope tree for body structure (plans 132/142/143). This spike investigates whether to unify them, the directions the unification could take, and the trade-offs of each. Inputs: the mdbase research (gaps S-1 through S-7), plus a fresh web survey covering CUE, JSON Schema, TypeSpec, Pkl, KCL, Dhall, RELAX NG, and Schematron, plus a survey of every published Markdown linter / SSG / vault tool. Headline findings: - No off-the-shelf schema language solves both halves well. Every choice trades something. - The whole ecosystem treats Markdown body validation as an unsolved problem (markdownlint #22 has been open since 2016). mdsmith is in green-field territory for the body half. - The RELAX NG + Schematron split (grammar + rules) maps onto mdsmith's existing MDS020 + rule-plugins split. A "single language for both" goal fights this established pattern. Five directions sketched with the same toy schema (runbook). Preliminary recommendation: a single YAML DSL with a `cue:` escape hatch for the small fraction of FM constraints that need CUE's full power. Fallback: keep two surfaces with shared named-type primitives (plan 134). Plan 132 now points at the spike. The spike's final recommendation will fold back into the plan. https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ
jeduden
pushed a commit
that referenced
this pull request
May 10, 2026
mdsmith currently uses two schema languages in a single schema file: CUE for front matter, a YAML scope tree for body structure (plans 132/142/143). This spike investigates whether to unify them, the directions the unification could take, and the trade-offs of each. Inputs: the mdbase research (gaps S-1 through S-7), plus a fresh web survey covering CUE, JSON Schema, TypeSpec, Pkl, KCL, Dhall, RELAX NG, and Schematron, plus a survey of every published Markdown linter / SSG / vault tool. Headline findings: - No off-the-shelf schema language solves both halves well. Every choice trades something. - The whole ecosystem treats Markdown body validation as an unsolved problem (markdownlint #22 has been open since 2016). mdsmith is in green-field territory for the body half. - The RELAX NG + Schematron split (grammar + rules) maps onto mdsmith's existing MDS020 + rule-plugins split. A "single language for both" goal fights this established pattern. Five directions sketched with the same toy schema (runbook). Preliminary recommendation: a single YAML DSL with a `cue:` escape hatch for the small fraction of FM constraints that need CUE's full power. Fallback: keep two surfaces with shared named-type primitives (plan 134). Plan 132 now points at the spike. The spike's final recommendation will fold back into the plan. https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ
jeduden
added a commit
that referenced
this pull request
May 10, 2026
…, quick-win plans, spike (#242) * Plans 132-136: schema language ergonomics + actionable diagnostics Schema-language work derived from the mdbase research (docs/research/mdbase-vs-mdsmith/learn-from-mdbase.md): - 132 inline schema in kinds (S-1) - 133 actionable schema diagnostics for MDS020 - 134 named field-type shortcuts (S-2) - 135 schema inheritance via extends (S-3) - 136 field deprecation flag (S-6) Plan 133 is the cross-cutting "errors are actionable" work — one diagnostic per failure, with field, actual, expected, schema reference, and best-effort hints. https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ * Plans 137-140: dry-run, backlinks, field-presence, path-pattern Quick-win plans from the mdbase research, S effort each: - 137 fix --dry-run (C-6) - 138 backlinks subcommand (L-4) - 139 field-presence kind assignment (M-1) - 140 per-kind path-pattern (M-3) Plan 140 emits its diagnostic through plan 133's actionable shape; plans 138 and 139 surface their matches via plan 95's `mdsmith kinds` provenance. https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ * Address Copilot review on plans 137-139 - 137: point at runFix in cmd/mdsmith/main.go (no cmd/mdsmith/fix file exists today) - 138: drop the claim that backlinks JSON shape matches `mdsmith query --format json` (query has no --format flag yet); document the shape directly - 139: replace the non-existent internal/config/kinds.go with the actual location (resolveEffectiveKinds in merge.go, plus provenance.go) PR description updated separately to reflect that 137-140 are part of this PR. https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ * Address Copilot follow-up review on plans 137-140 - 137: drop fixed=0 from the dry-run example (the text already says we omit the fixed= field); replace [...] with [] inside the JSON snippet so it parses - 138: replace ellipsis with two concrete records in the JSON example - 139: prose now references kinds: (the list key) instead of an explicit kind: tag - 140: example uses kinds: [plan] in prose; the mock kinds show output uses the actual <name>: YAML key https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ * Address Copilot follow-up: glob syntax, package name, kinds: key - 132: filename require uses glob syntax. Changed TASK-[0-9]+.md to TASK-[0-9][0-9][0-9][0-9].md so the example actually matches a 4-digit task ID (the + was reading as a literal plus). - 134: shortcut library declares `package types` to match the import path tail and the `types.#date` identifier used downstream. - 139: prose now consistently references the `kinds:` list (front matter) rather than a singular `kind:` field. Two more occurrences in the summary and Background sections updated. https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ * Address Copilot follow-up: kinds CLI subcommand names - 135, 140: every reference to `mdsmith kinds <name>` now uses the implemented `mdsmith kinds show <name>` - 139: every reference to `mdsmith kinds <file>` now uses `mdsmith kinds resolve <file>` - 135: example output reshaped to the existing `kinds show` YAML format (`<name>:` keying, `# from` provenance comments) - 139: example output reshaped to the existing `kinds resolve` text format (`file:` then `effective kinds:`) https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ * Address Copilot follow-up: anchor datetime regex; fix matcher semantics - 134: #datetime now ends with the same kind of end anchor as #date and #time, with an optional trailing Z or numeric offset to cover ISO-8601 timezones. - 139: drop the "short-circuits on the first match" claim. The matcher unions kinds across every matching entry, the same as resolveEffectiveKinds does today; the plan's surface only adds a new selector type, not a new precedence rule. https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ * Address Copilot follow-up: shortcut split, RuleID/RuleName - 134: split shortcut-resolution semantics into two cases. A bare YAML scalar (single identifier, no quotes/operators) must resolve in the registry or the loader errors. Anything else passes through as raw CUE. This reconciles the prose with the existing "unknown bare name -> error" acceptance criterion. - 133: reframe task 6 and the matching AC around RuleID/RuleName (the lint diagnostic shape) rather than Code/Source (the LSP wire shape). The LSP conversion in internal/lint/diagnostic.go already maps RuleID -> Code and sets Source. https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ * Address Copilot follow-up: inheritance model, struct name, glob - 135: pick a single inheritance model (CUE refinement under unification). Child must satisfy parent. A child that narrows the parent is OK; a child that conflicts produces the "cannot unify" diagnostic. Updated the example so the failing case is a value outside the parent's disjunction (real conflict) rather than a type that "wins". - 139: KindAssignment -> KindAssignmentEntry to match the actual struct name in internal/config/config.go. - 140: example glob now requires at least one digit (plan/[0-9][0-9]*_*.md). The previous pattern would have matched plan/_slug.md. https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ * Split plan 132 into 132/142/143 (engine, content, cross-refs) Plan 132 grew past the file-length cap once we folded in the rich body schema (S-7) and the per-scope rule overrides. Splitting into three keeps each focused and lets later plans land incrementally: - 132 — schema engine: two sources (inline + proto.md), AST-rooted scope tree, sections with aliases / children / sequential numbering, per-scope rule overrides. Existing rules reuse-with-no-code-change; the engine re-implements MDS020 with no fixture changes. - 142 — section content constraints: max-words, forbidden-starts, required-patterns, etc. Each is a one-walk pass over the section body; diagnostics use plan 133's shape. - 143 — cross-references, acronyms, index side-output. The three S-7 capabilities that span the document. https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ * Reframe plan 142 as a content rule pack Replace the schema-shape keys (max-words, forbidden-starts, etc.) with normal mdsmith rules: - MDS036 grows max-words / min-words / max-paragraphs settings alongside its current line-based cap - MDS055 forbidden-paragraph-starts (new) - MDS056 forbidden-text (new) - MDS057 required-text-patterns (new) - MDS058 required-mentions (new) All four new rules are default-disabled. They configure document-wide today through the existing rules: surface and per-section via plan 132's per-scope override mechanism. The schema language gets no new shape; the work is in the rule set. https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ * Plan 132: recursive sections, level inferred from depth Heading hierarchy is the natural shape of the section tree, so the schema models it directly: - One recursive sections: list (no separate children:/fields: keys) - Heading text only — no #-markers; the level is the section's depth in the tree - repeats: + sequential: + min: / max: on a section whose children are a sequence rather than fixed - Document H1 stays the title; sections: starts at H2 Plan 143 follows the same convention: scope: matches by heading text, must-match: takes text only. https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ * Address Copilot follow-up: heading text, glob accuracy - 132 / 142: drop residual ## markers from heading: examples to match the level-from-depth convention introduced in the previous commit - 140: soften the goal text. The path-pattern glob can approximate "starts with digits" but cannot enforce "all digits, no other characters" — the plan now says so https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ * Plans: depends-on front-matter field Adds a depends-on: '[...int] | *[]' field to plan/proto.md so plans can declare other plans they build on. Default empty list, so existing plans pass unchanged. Backfilled the obvious dependencies for the plans introduced in this PR: - 134, 142, 143: depend on 132 (schema engine) - 135, 136: depend on 132 + 133 - 140: depends on 133 (actionable diagnostics) Earlier plans without obvious in-PR dependencies (132, 133, 137, 138, 139) keep the empty default. Older plans (52, 61, ..., 131) carry the empty default too; they can be backfilled later as a chore. https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ * Plan 132: open vs closed scopes, wildcard slot Define how the schema handles sections the document has but the schema does not list: - Default is open: unlisted headings allowed anywhere among listed sections - closed: true on a scope makes it strict; unlisted headings flag a diagnostic - "..." entry in sections: is a positional escape hatch that survives closed: true and marks where unlisted sections are tolerated Listed sections still appear in declared order in either mode. Optional sections may be skipped without breaking neighbors' order. https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ * Plan 133: drop GitHub #L<line> anchor from example The schema_ref example in plan 133 used a markdown link with a #L4 anchor. MDS027 resolves anchors against heading slugs, not GitHub-style line anchors, so the example was a misleading idiom. Replace with plain text; linenumber-as-anchor is a wire-format detail that doesn't belong in a markdown link anyway. https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ * Schema-language unification spike mdsmith currently uses two schema languages in a single schema file: CUE for front matter, a YAML scope tree for body structure (plans 132/142/143). This spike investigates whether to unify them, the directions the unification could take, and the trade-offs of each. Inputs: the mdbase research (gaps S-1 through S-7), plus a fresh web survey covering CUE, JSON Schema, TypeSpec, Pkl, KCL, Dhall, RELAX NG, and Schematron, plus a survey of every published Markdown linter / SSG / vault tool. Headline findings: - No off-the-shelf schema language solves both halves well. Every choice trades something. - The whole ecosystem treats Markdown body validation as an unsolved problem (markdownlint #22 has been open since 2016). mdsmith is in green-field territory for the body half. - The RELAX NG + Schematron split (grammar + rules) maps onto mdsmith's existing MDS020 + rule-plugins split. A "single language for both" goal fights this established pattern. Five directions sketched with the same toy schema (runbook). Preliminary recommendation: a single YAML DSL with a `cue:` escape hatch for the small fraction of FM constraints that need CUE's full power. Fallback: keep two surfaces with shared named-type primitives (plan 134). Plan 132 now points at the spike. The spike's final recommendation will fold back into the plan. https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ * Plan 144 + Direction B deep-dive spike - Plan 144: numeric sort mode for the catalog directive. PLAN.md sorts on `id` lexicographically today (52 lands after 100). The plan adds `sort: numeric:id` that parses each entry's field as int and sorts numerically when all parse, falling back to string compare otherwise. Renumbering existing plans is out of scope — it would break every commit/PR/review reference. - Direction B deep dive: a full spec for the YAML DSL that the spike preliminarily recommends. Walks the surface end to end: bare-name shortcuts, constraint maps per type, a `cue:` escape valve for cross-field constraints. Five worked examples (ADR, runbook, plan, deck, deck-with-Slide-{n}). A translation table from each existing CUE shape to the YAML form. End-to-end validator trace through one runbook. Migration story. The deep dive is research; nothing in this PR commits to shipping it. Plan 132 still points at the spike as the gating decision. https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ * Renumber 132/133/134 → 146/147/148 to disambiguate The rebase against main brought new plans at IDs 132 (claude-code-plugin), 133 (lsp-hover), and 134 (lsp-completion). With the new depends-on field referencing plan IDs, duplicates make dependencies ambiguous. Renumber my plans to free IDs (146/147/148) so each ID is unique: - 132_inline-schema-in-kinds → 146 - 133_actionable-schema-diagnostics → 147 - 134_named-field-type-shortcuts → 148 depends-on lists in 135/136/140/142/143 updated accordingly. Inline references throughout the plan files, the spike, and the deep-dive doc re-pointed. Also addresses three other Copilot comments: - Plan 144: pick one naming, "numeric:" prefix, consistently throughout the plan. - Plan 137: keep failures= in the stats line so the dry-run output stays machine-parsable alongside the existing fix output. fixed=0 literal, would-fix=N additive. https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ * Revert collateral damage from renumber + fix copilot drift The blanket renumbering script in the previous commit accidentally also rewrote main's plans 122 and 152, where "plan 132/133/134" referred to main's claude-code-plugin / lsp-hover / lsp-completion plans (not mine). Revert those two files to origin/main. Also fix a few stragglers: - direction-b-deep-dive.md: SchemaDiagnostic reference now points at plan 147 (was 133). - 137 AC: the summary line keeps fixed= alongside would-fix=, matching the design section. The earlier "omits fixed=N" bullet contradicted it. - 144 summary: rename "numeric-id sort mode" to "numeric: sort prefix" to match the rest of the plan. https://claude.ai/code/session_01KJXMzuc6utx3e1uLnJ6VeZ --------- Co-authored-by: Claude <noreply@anthropic.com>
jeduden
pushed a commit
that referenced
this pull request
May 19, 2026
…ssue refs Lines like " #22 \"Mandatory headings\"" and " #288.**" are GitHub issue/PR references soft-wrapped inside list items, not malformed ATX headings. Computing `after` before the MD023 check and returning nil when after[0] is a digit avoids flagging these lines. Add TestCheck_IssueReference, TestCheck_IssueReferenceAtColumn1, and TestFix_IssueReferenceUnchanged to pin the behaviour. https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz
jeduden
pushed a commit
that referenced
this pull request
May 19, 2026
The previous guard returned nil for any '#'+digit line regardless of indentation. That silenced MD018 for genuine malformed headings like #1Heading or ##22Title at column 1. Scope the guard to leading>0: a '#'+digit run on an indented line is almost certainly a soft-wrapped issue/PR reference (#22, #288) — at column 1 it is a missing-space defect and is flagged normally. Add TestCheck_DigitAtColumn1IsFlagged and TestFix_DigitAtColumn1 to pin the column-1 behaviour and prevent regression. https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz
jeduden
added a commit
that referenced
this pull request
May 19, 2026
* feat: add MDS059 atx-heading-whitespace rule Implements rule MDS059 covering the markdownlint ATX-heading whitespace family (MD018–MD021) and heading indentation (MD023). Detects missing space, multiple spaces, closed ATX markers, and leading indentation on raw ATX heading lines. Autofix normalizes every defect to open ATX with a single space and no leading indent. Skips fenced/indented code blocks and directive bodies. Closes plan 176. https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz * test(MDS059): add coverage tests for internal helpers Three statement-coverage gaps in rule.go — the all-hash guard in checkClosingATX, the level-out-of-range guard in normalizeLine, and the empty-string early return in extractContent — are only reachable by calling the unexported functions directly. Add rule_coverage_test.go to hit all three paths and reach 100% statement coverage. https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz * fix(MDS059): address Copilot review — CRLF, tab, C# false positive - Add \r to TrimRight calls so CRLF line endings never produce spurious "missing space after # in heading" diagnostics on empty ATX headings (##\r\n). - Flag a tab immediately after opening hashes as "missing space" and normalise it to a single space (#\tHeading → # Heading). - Only treat a trailing # run as a closing ATX marker when preceded by whitespace (CommonMark rule). Previously, content like "# C#" was wrongly diagnosed and mangled by Fix. The case with no preceding space now returns nil cleanly. Consequence: #Heading# is fixed only for MD018 (missing opening space) → # Heading#; MD020 coverage downgraded to partial in the coverage matrix. - Update rule README to reflect accurate behaviour. https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz * fix(MDS059): address second Copilot review - Coverage matrix: correct preamble to 41 of 52 (2 partial), 11 remaining; remove completed plan 176 from plan list. - normalizeLine: preserve trailing \r when the original line has one so CRLF files don't get mixed LF/CRLF after a partial fix. Test: TestFix_PreservesCRLFOnRewrittenLines. - Add TestCheck_SkipsPIBlock and TestFix_PreservesPIBlock to pin the directive-body (PI block) skip behaviour for Check and Fix. https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz * fix(test): drain interleaved notifications in lspPipe.shutdown shutdown() used request(), which reads exactly one frame. After a rename the server emits publishDiagnostics notifications that can arrive before the shutdown response, causing the id==99 assertion to see nil. Switch to requestPickResult(), which already loops past interleaved server frames, matching the pattern used by every other multi-step LSP test helper. https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz * docs(plan 176): clarify CommonMark interpretation for #Heading# case Split the acceptance criterion that covered both `# Heading #` and `#Heading#` into separate bullets. The `#Heading#` bullet now explicitly notes that per CommonMark a trailing `#` without preceding whitespace is content, not a closing marker, so MD020 is partial for that case. This matches the implementation and resolves the Copilot review comment about the checked-off criterion contradicting the fix output. https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz * fix(MDS064): rename message to "multiple spaces or tabs after # in heading" leadingSpaces() counts tabs as well as spaces, so the check at line 71 fires for mixed whitespace like "# \tHeading" (space then tab). The old message "multiple spaces after # in heading" was inaccurate in that case. Rename to "multiple spaces or tabs after # in heading" and add tests for the space+tab pattern. https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz * fix(MDS064): rename package headingwhitespace → atxheadingwhitespace; register in all.go The package name headingwhitespace did not follow the repo convention of deriving the directory name from the rule name with hyphens removed (blockquote-whitespace → blockquotewhitespace, list-marker-space → listmarkerspace). Rename to atxheadingwhitespace for consistency. Also fixes a production bug: the package was never blank-imported in internal/rules/all/all.go, so MDS064 was not registered and would never run in cmd/mdsmith. Add the import in alphabetical order. Also updates plan/176 front matter and design text to replace the provisional MDS060 ID with the shipped MDS064. https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz * fix(MDS064): skip '#' followed by digit to avoid false positives on issue refs Lines like " #22 \"Mandatory headings\"" and " #288.**" are GitHub issue/PR references soft-wrapped inside list items, not malformed ATX headings. Computing `after` before the MD023 check and returning nil when after[0] is a digit avoids flagging these lines. Add TestCheck_IssueReference, TestCheck_IssueReferenceAtColumn1, and TestFix_IssueReferenceUnchanged to pin the behaviour. https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz * fix(lint): sort atxheadingwhitespace import into alphabetical position The import was left at the position where headingwhitespace used to sit (after headingstyle) instead of its correct alphabetical slot after ambiguousemphasis. Moves it so gofmt/goimports is satisfied. https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz * docs(MDS064): clarify space requirement only applies when heading has content The README said the opening hashes must be followed by "exactly one space" without qualification, implying empty headings like "##" are invalid. Reword to make clear the space check only applies when the heading has content; empty headings are valid and produce no diagnostic. https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz * fix(MDS064): narrow digit guard to indented lines only The previous guard returned nil for any '#'+digit line regardless of indentation. That silenced MD018 for genuine malformed headings like #1Heading or ##22Title at column 1. Scope the guard to leading>0: a '#'+digit run on an indented line is almost certainly a soft-wrapped issue/PR reference (#22, #288) — at column 1 it is a missing-space defect and is flagged normally. Add TestCheck_DigitAtColumn1IsFlagged and TestFix_DigitAtColumn1 to pin the column-1 behaviour and prevent regression. https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Validation