Commit 5b81056
feat(scripts)(settings): add ADR consistency lint infrastructure (#1552)
## Description
Added a PowerShell-based linter that enforces structural consistency
between Architecture Decision Record frontmatter and body sections in
*docs/planning/adrs/*. The linter is shipped as two reusable modules, a
CLI entry script, a JSON rule registry, and three Draft-07 JSON Schemas,
with editor schema bindings, a markdownlint exclusion for fixture
corpora, an `npm run lint:adr-consistency` entry, and a Pester 5.x test
suite covering all nine rules through a paired pass/fail fixture
pattern.
The change is additive. No existing linter was modified except a
one-line tolerance added to **skill structure validation** so skills can
carry a *templates/* subdirectory without warning. The new
`lint:adr-consistency` script is invokable on its own and is **not**
wired into the `lint:all` chain in this PR.
### Linter core
* Added
[scripts/linting/Modules/AdrConsistency.psm1](scripts/linting/Modules/AdrConsistency.psm1)
(857 lines) with the public `Invoke-AdrConsistencyValidation` dispatcher
and nine private `Test-*` rule functions covering `ADR-CONSISTENCY-001`
through `ADR-CONSISTENCY-009`. The module declares `#Requires -Version
7.0` and pins `PowerShell-Yaml` to version `0.4.7`, loads
`../rules/adr-consistency-rules.json` into `$script:RuleRegistry` keyed
by rule id, and routes all violation construction through a single
`New-AdrViolation` factory that owns rule lookup, `{token}`
substitution, and severity assignment.
* Added
[scripts/linting/Modules/AdrBodyParser.psm1](scripts/linting/Modules/AdrBodyParser.psm1)
(445 lines) with the public `Get-AdrBodySections` parser. The parser
splits an ADR body into structured sections — *AffectedComponents*,
*DecisionDrivers*, *DecisionOutcomeMatrixDrivers*, *BadConsequences*,
*RisksAndMitigationsRisks*, *Confirmation*, and three path-token sets —
and exposes only the parser; helpers like `Get-AdrH2Section`,
`Get-AdrH3SectionInH2` (for MADR v4 `### Consequences` under `##
Decision Outcome`), `Get-AdrTableRows`, `Get-AdrPathTokens`,
`Get-AdrBadConsequenceBullets`, and a stateful
`Remove-AdrFencedCodeBlocks` walker remain private.
* Added
[scripts/linting/Validate-AdrConsistency.ps1](scripts/linting/Validate-AdrConsistency.ps1)
as the CLI entry script. Parameters cover `-Paths`, `-Files`,
`-ExcludePaths`, `-WarningsAsErrors`, `-ChangedFilesOnly`,
`-BaseBranch`, and `-OutputPath` (defaulting to
*logs/adr-consistency-results.json*). The script imports
`Modules/AdrConsistency.psm1`, `Modules/LintingHelpers.psm1`, and
`../lib/Modules/CIHelpers.psm1`; resolves the repo root via `git
rev-parse --show-toplevel` with a `$PSScriptRoot` fallback; and uses an
ordinal-ignore-case `StartsWith` boundary check to reject paths that
escape the repository. Each violation prints to host output, and on CI
the script also emits `Write-CIAnnotation` per finding plus a single
`Write-CIStepSummary`. Exit code is `1` when error count is non-zero, or
when `-WarningsAsErrors` is set and warning count is non-zero.
### Rule registry, schemas, and editor integration
* Added
[scripts/linting/rules/adr-consistency-rules.json](scripts/linting/rules/adr-consistency-rules.json)
declaring all nine rules with `id`, `severity`, `scope`, `check`,
`message`, and `description` fields. Eight rules are `error` severity
and one (`ADR-CONSISTENCY-007`, *numeric-claim-generalized*) is `warn`.
Scopes split as `frontmatter` (002, 008), `body` (003, 004, 005, 006,
007), and `cross-region` (001, 009). The kebab-case `check` slug maps
mechanically to the `Test-<PascalCase>` PowerShell function name.
* Added
[scripts/linting/schemas/adr-consistency-rules.schema.json](scripts/linting/schemas/adr-consistency-rules.schema.json)
as a Draft-07 schema for the rule registry. It requires `rules` with
`minItems: 1`, pins `id` to `^ADR-CONSISTENCY-\d{3}$`, restricts
`severity` to `error|warn` and `scope` to
`frontmatter|body|cross-region`, and requires `check` to be kebab-case.
* Added
[scripts/linting/schemas/adr-frontmatter.schema.json](scripts/linting/schemas/adr-frontmatter.schema.json)
as a Draft-07 schema for `docs/planning/adrs/NNNN-{kebab-case}.md`
frontmatter. Required fields are `id`, `title`, `status`,
`proposed_date`, `deciders`, and `affected_components`. The schema
models the six-value status taxonomy (`proposed`, `accepted`,
`rejected`, `deprecated`, `superseded`, `withdrawn`), constrains
`effort` to `S|M|L|XL`, models `success_criteria` items with
`metric`/`target`/`measurement_window`/`source`, models `related` items
as `path`/`relation` pairs (with relation enum
`informational|influenced-by|influences`), pairs each `asr_triggers`
item with required `evidence`, and adds an `allOf` conditional that
requires `accepted_date` when `status` is `accepted`.
* Added
[scripts/linting/schemas/adr-config.schema.json](scripts/linting/schemas/adr-config.schema.json)
as a Draft-07 schema for `docs/planning/adrs/.adr-config.yml`. Required
keys are `project_slug`, `owner`, `default_status`,
`decision_id_format`, `template_source`, and `last_decision_id`.
`decision_id_format` is constrained to the literal `NNNN`;
`last_decision_id` is pinned to `^\d{4}$`.
* Modified
[scripts/linting/schemas/schema-mapping.json](scripts/linting/schemas/schema-mapping.json)
to register two new entries: ADR markdown files via the glob
`docs/planning/adrs/[0-9][0-9][0-9][0-9]-*.md` against the
*adr-frontmatter* schema, and the rule registry against the
*adr-consistency-rules* schema.
* Modified [.vscode/settings.json](.vscode/settings.json) to register
`./scripts/linting/schemas/adr-config.schema.json` against
`**/.adr-config.yml` and `**/.adr-config.yaml` under `yaml.schemas` so
editor validation matches the file-on-disk checker.
### Tests and fixtures
* Added
[scripts/tests/linting/AdrConsistency.Tests.ps1](scripts/tests/linting/AdrConsistency.Tests.ps1)
as a Pester 5.x suite tagged `Unit`. A `BeforeDiscovery` block declares
a nine-element `$RuleCases` array mapping each rule id to its fixture
directory; three `Describe` blocks then run data-driven `It -ForEach`
iterations over the cases, asserting that each `pass.md` produces zero
violations, each `fail.md` fires its target rule, no violation message
contains an unsubstituted `{token}` template, and the validator's return
shape exposes `File`/`Violations` properties with consistent fields. The
`BeforeAll` block force-imports the consistency module and mocks
`Write-Host`; the `AfterAll` block tears down both *AdrConsistency* and
its *AdrBodyParser* dependency.
* Added 18 fixture files under
[scripts/tests/linting/fixtures/adr-consistency/](scripts/tests/linting/fixtures/adr-consistency/)
— one folder per rule, each with `pass.md` and `fail.md`. All fixtures
derive from a single canonical 76-line *Fixture base ADR 9999* baseline
that follows MADR v4 layout: `## Context`, `## Decision Drivers`, `##
Considered Options`, `## Decision Outcome` (with a driver matrix and an
H3 *Consequences* block split into Good/Bad/Neutral H4 subsections), `##
Risks and Mitigations`, `## Confirmation`, `## More Information`, and
`## Affected Components`. Each `fail.md` mutates the baseline along
exactly one axis to trigger a single target rule (for example,
*risks-consequences-pairing/fail.md* swaps the Bad consequence to a
risk-shaped statement absent from the Risks and Mitigations table).
* Modified [.markdownlint-cli2.jsonc](.markdownlint-cli2.jsonc) to add
`scripts/tests/linting/fixtures/**` to the markdownlint `ignores` list
so the deliberately defective fail-fixture markdown does not trip
project-wide linting.
### Configuration touches
* Modified [package.json](package.json) to register
`lint:adr-consistency` (`pwsh -NoProfile -File
scripts/linting/Validate-AdrConsistency.ps1 -Paths docs/planning/adrs`)
between `lint:frontmatter` and `lint:collections-metadata`. The script
is **not** added to the `lint:all` chain in this PR.
* Modified
[scripts/linting/Validate-SkillStructure.ps1](scripts/linting/Validate-SkillStructure.ps1)
to add `'templates'` to `$script:RecognizedSubdirectories` alongside the
existing `scripts`, `references`, `assets`, `examples`, and `tests`
entries.
## Related Issue(s)
Closes #1551.
## Type of Change
Select all that apply:
**Code & Documentation:**
* [ ] Bug fix (non-breaking change fixing an issue)
* [x] New feature (non-breaking change adding functionality)
* [ ] Breaking change (fix or feature causing existing functionality to
change)
* [ ] Documentation update
**Infrastructure & Configuration:**
* [ ] GitHub Actions workflow
* [x] Linting configuration (markdown, PowerShell, etc.)
* [ ] Security configuration
* [ ] DevContainer configuration
* [ ] Dependency update
**AI Artifacts:**
* [ ] Reviewed contribution with `prompt-builder` agent and addressed
all feedback
* [ ] Copilot instructions (`.github/instructions/*.instructions.md`)
* [ ] Copilot prompt (`.github/prompts/*.prompt.md`)
* [ ] Copilot agent (`.github/agents/*.agent.md`)
* [ ] Copilot skill (`.github/skills/*/SKILL.md`)
> Note for AI Artifact Contributors:
>
> * Agents: Research, indexing/referencing other project (using standard
VS Code GitHub Copilot/MCP tools), planning, and general implementation
agents likely already exist. Review `.github/agents/` before creating
new ones.
> * Skills: Must include both bash and PowerShell scripts. See
[Skills](../docs/contributing/skills.md).
> * Model Versions: Only contributions targeting the **latest Anthropic
and OpenAI models** will be accepted. Older model versions (e.g.,
GPT-3.5, Claude 3) will be rejected.
> * See [Agents Not
Accepted](../docs/contributing/custom-agents.md#agents-not-accepted) and
[Model Version
Requirements](../docs/contributing/ai-artifacts-common.md#model-version-requirements).
**Other:**
* [x] Script/automation (`.ps1`, `.sh`, `.py`)
* [ ] Other (please describe):
## Sample Prompts (for AI Artifact Contributions)
<!-- Not applicable: this PR contains no AI artifacts. -->
For detailed contribution requirements, see:
* Common Standards:
[docs/contributing/ai-artifacts-common.md](../docs/contributing/ai-artifacts-common.md)
- Shared standards for XML blocks, markdown quality, RFC 2119,
validation, and testing
* Agents:
[docs/contributing/custom-agents.md](../docs/contributing/custom-agents.md)
- Agent configurations with tools and behavior patterns
* Prompts:
[docs/contributing/prompts.md](../docs/contributing/prompts.md) -
Workflow-specific guidance with template variables
* Instructions:
[docs/contributing/instructions.md](../docs/contributing/instructions.md)
- Technology-specific standards with glob patterns
* Skills: [docs/contributing/skills.md](../docs/contributing/skills.md)
- Task execution utilities with cross-platform scripts
## Testing
Ran `npm run lint:adr-consistency` against the working tree. The
validator discovered the existing *.adr-config.yml* style entries under
*docs/planning/adrs/* and reported `ADR consistency: 1 file(s) | 0
error(s) | 0 warning(s)` with exit code `0`. The Pester suite at
*scripts/tests/linting/AdrConsistency.Tests.ps1* exercises all nine
rules through paired pass/fail fixtures, validates the validator's
return contract, and asserts that no rule message contains an
unsubstituted `{token}` placeholder.
## Checklist
### Required Checks
* [x] Documentation is updated (if applicable)
* [x] Files follow existing naming conventions
* [x] Changes are backwards compatible (if applicable)
* [x] Tests added for new functionality (if applicable)
### AI Artifact Contributions
* [ ] Used `/prompt-analyze` to review contribution
* [ ] Addressed all feedback from `prompt-builder` review
* [ ] Verified contribution follows common standards and type-specific
requirements
### Required Automated Checks
The following validation commands must pass before merging:
* [x] Markdown linting: `npm run lint:md`
* [x] Spell checking: `npm run spell-check`
* [x] Frontmatter validation: `npm run lint:frontmatter`
* [x] Skill structure validation: `npm run validate:skills`
* [x] Link validation: `npm run lint:md-links` — 7 pre-existing baseline
failures on `main` not introduced by this PR
(`.github/skills/security/secure-by-design/SKILL.md`,
`docs/agents/code-review/README.md`,
`docs/agents/rai-planning/agent-overview.md`,
`docs/agents/rai-planning/phase-reference.md`,
`docs/architecture/README.md`, `docs/getting-started/collections.md`,
`docs/getting-started/mcp-configuration.md`)
* [x] PowerShell analysis: `npm run lint:ps`
* [x] Plugin freshness: `npm run plugin:generate`
* [x] Docusaurus tests: `npm run docs:test`
## Security Considerations
* [x] This PR does not contain any sensitive or NDA information
* [x] Any new dependencies have been reviewed for security issues
* [x] Security-related scripts follow the principle of least privilege
## Additional Notes
The new `lint:adr-consistency` script is intentionally not wired into
`lint:all` in this PR. The `docs/planning/adrs/` corpus does not yet
contain ADR markdown files, so the linter is a no-op against the current
tree. A follow-up PR will introduce the ADR Planner upgrade that
produces ADR documents and enable the linter as part of `lint:all`.
The `Validate-SkillStructure.ps1` change adds `templates` to
`$script:RecognizedSubdirectories`, allowing skills that ship
author-facing templates to validate without warnings. No existing skill
relies on a `templates/` directory being rejected.
---------
Co-authored-by: Bill Berry <wbery@microsoft.com>1 parent f5e8513 commit 5b81056
38 files changed
Lines changed: 4273 additions & 25 deletions
File tree
- .github/workflows
- .vscode
- scripts
- linting
- Modules
- rules
- schemas
- tests/linting
- fixtures/adr-consistency
- affected-components-cited
- affected-components-mirror
- driver-trigger-map-complete
- drivers-matrix-cardinality
- numeric-claim-generalized
- peer-planner-names
- risks-consequences-pairing
- state-placeholder-resolved
- success-criteria-source-resolves
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
80 | 80 | | |
81 | 81 | | |
82 | 82 | | |
| 83 | + | |
83 | 84 | | |
84 | 85 | | |
85 | 86 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
196 | 196 | | |
197 | 197 | | |
198 | 198 | | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
199 | 211 | | |
200 | 212 | | |
201 | 213 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| 9 | + | |
9 | 10 | | |
10 | 11 | | |
11 | 12 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
21 | 25 | | |
22 | 26 | | |
23 | 27 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| 17 | + | |
17 | 18 | | |
18 | 19 | | |
19 | 20 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
35 | 74 | | |
36 | 75 | | |
37 | 76 | | |
| |||
85 | 124 | | |
86 | 125 | | |
87 | 126 | | |
| 127 | + | |
| 128 | + | |
88 | 129 | | |
89 | 130 | | |
90 | 131 | | |
91 | | - | |
92 | | - | |
93 | | - | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
94 | 136 | | |
95 | 137 | | |
96 | 138 | | |
| |||
0 commit comments