Skip to content

The ecosystem roadmap: thirteen profiles and the schema-validation check - #176

Merged
lefticus merged 16 commits into
cpp-best-practices:mainfrom
leftibot:main
Nov 13, 2026
Merged

lefticus merged 16 commits into
cpp-best-practices:mainfrom
leftibot:main

Conversation

@leftibot

Copy link
Copy Markdown
Contributor

The ecosystem roadmap (#105, #107): thirteen changes, reviewed and merged one by one as a stack on leftibot/project_validator (its PRs #49 to #61), brought here as one. Each landed with every gate green: the full suite, 99% branch coverage, ruff, mypy, lizard, jscpd and the strict self-check.

Profiles

Profile Issue Kind Judged by
Shell and PowerShell #82 languages bats, shellspec, shunit2 under kcov; shfmt; shellcheck; Pester with its coverage; PSScriptAnalyzer
Docker #94 format, by file name the Dockerfile parses; hadolint; checkov, trivy config, KICS; trivy image, grype, Docker Scout
Kubernetes #95 format, by content helm lint, chart-testing, kube-linter and the policy scanners; helm-unittest and the chart's test hooks; kubeconform, kubeval, helm lint --strict
Ansible #96 format, by content ansible-lint (and its security rules), checkov, KICS; molecule, ansible-test
Nix #97 language nix flake check; nixfmt, alejandra, nix fmt -- --check; statix, deadnix; the systems built as the platform axis; result symlinks
Documentation sites #91 six formats (MkDocs, Sphinx, Docusaurus, mdBook, Hugo, Jekyll) the build; its strict switch as warnings-as-errors; markdownlint, vale; the built site
GitHub Action repositories #98 format, by content the self-test (uses: ./); actionlint; zizmor
JSON Schema #85 (part) format, by intent check-jsonschema, ajv, jsonschema, jv; prettier, biome
Protobuf #88 format buf build, protoc; buf lint; buf format --diff --exit-code; bufbuild/buf-action
OpenAPI and AsyncAPI #87 (part) two formats, by their key spectral, redocly, vacuum, swagger-cli, openapi-generator; the AsyncAPI CLI
XML Schema and XSLT #86 (part) two formats, by suffix xmllint with a schema, jing, SchXslt; XSpec; prettier's XML plugin
Jupyter #90 format, with a parser pytest --nbmake, nbconvert, papermill; ruff format, nbQA; ruff check; .ipynb_checkpoints/
LaTeX and Typst #92 (part) languages latexmk and the engines, xu-cheng/latex-action; chktex; latexindent; the auxiliary files. typst compile; typstyle, typstfmt

The check

schema-validation: CI validates each format's documents against their schemas, a tool-based check over a new aspect (schema), failing when no validator runs. JSON Schema, Kubernetes, Protobuf, OpenAPI and XML supply its tools.

What the core gained for them

  • Ecosystem.source_names: sources recognised by file name or name pattern (Dockerfile, Dockerfile.*), claimed once, named in --map.
  • Formats recognised by content: a format ecosystem's probe adds its format to the project's and the root unit's formats (Kubernetes, Ansible, documentation sites, GitHub Action repositories, JSON Schema, OpenAPI, XML schemas).
  • A template role: a data file under templates/ is noted as not parsed, rendered before it is read, instead of failing data-wellformed (Helm charts, Ansible roles).
  • TestFiles.written_in: a format's tests written in another format (a chart's YAML tests).
  • The repository stage lists the formats' build outputs too, so a committed site/ is a committed artifact.
  • The shell splitter keeps a # inside a word (nix build .#checks.x86_64-linux.default, nixpkgs#statix).
  • policy/tooling.required, the failing counterpart of advised.
  • Shared tools, each at its second user: kcov, EditorConfig, the misconfiguration scanners (from Terraform, with one trivy action), prettier.

Conventions

A format's lints are static-analysis, not compiler-warnings (documentation sites keep warnings-as-errors, whose strict build is a real switch). Helper shell scripts make Shell one of a project's languages, like any other source; --auxiliary dials that back. A shared tool records its run for every language it serves, so several fixtures' facts snapshots gained runs for languages they do not have; no result changed for them.

Left on the roadmap

Tabular data (#92); the other schema practices of #85 (schema-declared, schema-examples, schema-compatibility); link-check (#91) and migration-safety (#92) as new checks; #142 to #149.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt

leftibot and others added 16 commits November 12, 2026 13:54
Shell (.sh, .bash, .zsh, .bats, or a script without a suffix whose
shebang names a shell) and PowerShell (.ps1, .psm1) are one ecosystem
with no build system, judged by the tools CI runs on the scripts: bats,
shellspec or shunit2 under kcov, shfmt and shellcheck; Pester with its
code coverage and PSScriptAnalyzer. The lints stand for the compiler
warnings a shell does not have; shellcheck fails on any finding, so
running it treats warnings as errors, and PSScriptAnalyzer does with
-EnableExit. Nothing measures branches in a shell, so lines (and for
Pester, the commands run) are what coverage requires.

The issue's open question, when helper scripts make a project a Shell
project: always, like any other source; --auxiliary dials it back.

kcov was the Zig profile's and .editorconfig the .NET profile's; each
has its second user now, so project_validator/tools/kcov.py (with a
launcher, so that the test run under kcov is seen) and
project_validator/tools/editorconfig.py hold them. The shell-script
expander's shebang reading is a function the profile's probe shares.

Closes cpp-best-practices#82.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
Dockerfiles (Dockerfile, Containerfile, Dockerfile.<stage>,
<name>.Dockerfile) are a format: not a program, judged for hygiene. The
Dockerfile parses (known instructions with arguments, a FROM first after
any ARG; continuations, the escape directive and heredocs read as
BuildKit reads them), hadolint lints it and its security rules scan it,
the misconfiguration scanners (checkov, trivy config, KICS) scan it, and
CI audits the image it builds (trivy image, grype, Docker Scout). The
build (docker build, docker/build-push-action) shows a job works with
the ecosystem.

A Dockerfile has no telling suffix, so an ecosystem can now name its
sources (Ecosystem.source_names: names or name patterns, each to a
language or format), recognised when no suffix is, claimed once, named
in --map's signals, and left out of the unrecognised suffixes.

The misconfiguration scanners were the Terraform profile's; Dockerfiles
are their second user, so project_validator/tools/misconfiguration.py
holds them, and the one trivy action (image-ref for an image scan,
scan-ref otherwise) replaces the audits' and Terraform's. The data
parsers' UTF-8 reading is facts.wellformed.decoded.

Closes cpp-best-practices#94.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
Helm charts (Chart.yaml), kustomizations and manifests (YAML with
apiVersion, kind and metadata in a directory of them) make a project a
Kubernetes one: a format whose files are YAML, the Data format's. helm
lint, chart-testing and kube-linter lint it; kube-linter, checkov,
trivy config, KICS, polaris, kyverno and kube-score scan it; its tests
are helm-unittest's, or the chart's test hooks that helm test and ct
install run. Schema validation, Kubernetes versions as an axis and
Chart.lock wait for their own checks.

A content probe could only add languages; a format ecosystem's probe
now adds its format to the project's and the root unit's formats. A
chart's templates are Go templates, not YAML, so templates/ is a role
of its own and a data file with it is noted as not parsed, rendered
before it is read, instead of failing data-wellformed. A kind of test
file may say what its files are written in (the chart's tests are
YAML), so they are found among another format's sources.

Closes cpp-best-practices#95.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
A collection (galaxy.yml), a role (meta/main.yml with galaxy_info), an
ansible.cfg, a molecule scenario or a playbook (YAML whose plays name
hosts and tasks or roles) makes a project Ansible's: a format whose
files are YAML, the Data format's, with its Jinja templates accounted
for. ansible-lint lints it and its risky-command and no-log rules scan
it, beside checkov and KICS, the misconfiguration scanners that read
Ansible; its tests are molecule scenarios, or a collection's integration
targets that ansible-test runs. The production profile as warnings as
errors, yamllint and the target platforms wait for their own checks.

Closes cpp-best-practices#96.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
Flakes (flake.nix) and plain expressions (default.nix, shell.nix) are
judged by the checks CI builds (nix flake check), the formatter it runs
(nixfmt, alejandra, or the flake's own through nix fmt -- --check), the
lints statix and deadnix (errors with deadnix --fail), the systems it
builds for (the platform axis: the ones a job names, else the runner's
own) and the result symlinks nix build leaves, which must be ignored.
What nix runs in turn is followed: nix run nixpkgs#statix -- check .
runs statix, nix develop -c and nix shell -c run their command.

The shell splitter ended a word at any #, where the shell begins a
comment at the start of a word only: nix build .#checks.x86_64-linux
was split as nix build . A # inside a word stays.

Closes cpp-best-practices#97.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
MkDocs, Sphinx, Docusaurus, mdBook, Hugo and Jekyll sites are one format
ecosystem with a format per generator, each recognised by its
configuration (prose alone makes no site), so that each one's build,
strict switch and output directory are judged on their own: CI builds
the site (tests-run), the build fails on a warning (warnings-as-errors:
mkdocs build --strict, sphinx-build -W, onBrokenLinks: 'throw',
hugo --panicOnWarning, jekyll build --strict_front_matter), the prose is
linted (markdownlint, vale), and the built site is ignored and never
committed. Link checking waits for a check of its own.

The repository stage listed only the project's languages' build outputs
as what could be committed, so a format's never were; it lists the
formats' too.

Closes cpp-best-practices#91.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
A repository whose product is a reusable action (action.yml at its
root) is asked that a workflow of its own uses the action (uses: ./,
the self-test; the step is expanded into a marker the test runner
claims, as GitLab's templates are), that actionlint lints its workflows
and that zizmor audits them and the action. Every repository's
workflows get ci-lint and action-pinning already; this profile asks more
of one whose product is an action. The ecosystem has no CI usage signal
of its own: actionlint and zizmor are every repository's hygiene, so a
C++ project's lint job stays that project's.

Closes cpp-best-practices#98.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
CI validates each format's documents, in each unit, against their
schemas: a tool-based check over a new aspect, schema, whose policy
fails when no validator runs (a schema nothing is checked against
guards nothing); policy/tooling.required is the failing counterpart of
advised. JSON Schema, a format recognised by intent (*.schema.json, or
a $schema naming a json-schema.org dialect; never plain .json), supplies
check-jsonschema, ajv, the jsonschema CLI and jv, and is formatted with
prettier or biome; Kubernetes supplies kubeconform, kubeval and helm
lint --strict. prettier has its second user, so tools/prettier.py holds
it. The other schema practices of cpp-best-practices#85 are not here.

Part of cpp-best-practices#85.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
…matted by buf

.proto schemas (and the gRPC services they define) are a format: the
schemas compile (buf build, or protoc), buf lint runs with the rules
buf.yaml sets, and buf format --diff --exit-code fails on a file it
would change. bufbuild/buf-action is expanded into the commands it runs
unless its inputs turn them off. Breaking changes and buf.lock wait for
their own checks.

Closes cpp-best-practices#88.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
… their key

A YAML or JSON file whose top-level key is openapi (or swagger) or
asyncapi is an API description: a format whose files stay the Data
format's. CI lints the description against its specification (spectral
with a ruleset, redocly, vacuum, swagger-cli, openapi-generator's
validate; the AsyncAPI CLI), one tool that validates and analyses at
once. The version declared, the examples checked and breaking changes
are the other schema practices, not yet built.

Part of cpp-best-practices#87.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
…h XSpec

XSD, RELAX NG and Schematron schemas (XML Schema) and XSLT stylesheets
(XSLT) are two formats recognised from their suffixes; the files stay
XML, the Data format's, which gains .rng, .sch and .xspec. CI validates
the documents (xmllint with a schema, jing, SchXslt), runs the
stylesheets' XSpec suites, and formats both with prettier's XML plugin.
Two formats, so that a project of schemas alone is not asked for XSpec.
The ecosystem contract reckons a fixture's checks by its languages and
formats rather than by ecosystem, for the same reason.

Part of cpp-best-practices#86.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
Notebooks (.ipynb) are a format: a notebook is JSON with nbformat and
cells, which its parser checks for data-wellformed. CI runs the
notebooks (pytest --nbmake or --nbval, jupyter nbconvert --execute,
papermill), formats them (ruff format, or nbQA with black or ruff) and
lints them (ruff check, or nbQA with a linter), and .ipynb_checkpoints/
is ignored and never committed. Notebooks committed without outputs
wait for committed-artifacts. ruff reads notebooks, so the bare
fixture's broken one is excluded from the repository's own lint.

Closes cpp-best-practices#90.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
LaTeX is recognised by a document (a .tex that declares its class, or
a .latexmkrc); its sources count once one is found, so a stray fragment
makes no LaTeX project. CI compiles the documents (latexmk, a TeX engine,
tectonic, or xu-cheng/latex-action, whose pre- and post-compile scripts
are split into their commands), lints them (chktex, lacheck), formats
them (latexindent --check), and ignores what compiling leaves. Typst is
recognised by .typ sources, compiled with typst compile and formatted
with typstyle or typstfmt.

Part of cpp-best-practices#92.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
Add the XML schema profile: schemas validated, stylesheets tested with XSpec
Add the Jupyter notebook profile, a format with a parser of its own
Add the LaTeX and Typst profiles
@codecov

codecov Bot commented Nov 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.76%. Comparing base (9366428) to head (a140775).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #176      +/-   ##
==========================================
+ Coverage   99.75%   99.76%   +0.01%     
==========================================
  Files         225      251      +26     
  Lines       12451    13266     +815     
  Branches     1413     1474      +61     
==========================================
+ Hits        12420    13235     +815     
  Misses         31       31              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@lefticus
lefticus merged commit f9dcaff into cpp-best-practices:main Nov 13, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants