Skip to content

Move Audit Tests to tests/, Parse YAML and Markdown, Add Check Registry - #3416

Merged
ptr727 merged 21 commits into
developfrom
audit-refactor-registry-parsers
Oct 24, 2026
Merged

ptr727 merged 21 commits into
developfrom
audit-refactor-registry-parsers

Conversation

@ptr727

@ptr727 ptr727 commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

Implements the maintainer's decision of 2026-10-23 on #3391's follow-ups for the audit, in his words: "audit, agree on moving tests to tests and that should be consistent for the fleet to have tests and keep them in tests, no hand written parser when a field tested and maintained versions readily exists, so switch it and proceed".

What Changes

Tests Move to tests/

  • _selftest_cases, the inline matrix in spec/audit.py, is now tests/test_audit_helpers.py, a unittest module with one subTest per case.
    • The temporary git repositories are built by helpers on the test classes.
    • The globals() swaps are now unittest.mock.patch calls.
    • The invalid-byte probe tests move out of tests/test_audit.py along with the probe.
  • --selftest stays, as a smoke check of each helper family that runs in under a second.

Parsers

  • spec/audit.py declares pyyaml and markdown-it-py in PEP 723 inline metadata.
  • YAML: workflow jobs, contract tokens, the python-directories input, the concurrency group, the schedule gate, and dependabot.yml's ecosystems are now read from the parsed document.
    • A job's verbatim region is cut at the parser's own marks.
    • Contract tokens are matched against a view of the parsed job, indented two spaces per level, with block-scalar bodies left out except an if: condition.
  • Markdown: headings, fenced blocks, the README title and intro, link stripping, and the License column are now read from markdown-it-py tokens.
  • Text scans that stay: link definitions, image references, and tool rows are still read as text. A parsed link keeps its target but drops the reference name and position those checks read, and a comment beside each scan says so.
  • pyproject.toml: the audit reads only whether the file is present, never its content, so there was no read to move to tomllib.

The One Hand-Written Reader That Stays

The fence step and the ## region reading are still hand-written. They now live in spec/carried_text.py, which uses the standard library only.

  • What shares it: the audit and all three scripts below use fence_step. The audit and scripts/carry.py also use normalize, and the audit uses extract_section and strip_sections.
  • Why it stays hand-written: scripts/carry.py, scripts/build_dist.py, and scripts/canonical_review.py run under a plain python3, in CI and from the host menus, and carry.py writes the verbatim regions the audit then hashes. The four tools must split a document at the same boundaries, so moving the audit alone onto markdown-it-py would let carry.py write a region the audit reads as drift.
  • Follow-up: a later lane moves the region reading of all four tools onto markdown-it-py together, along with the switch of their callers to uv run --no-project --script.

Registry

  • spec/checks.json registers one entry per check: its id, family, a one-line description, the spec file it reads, its scope, and whether it runs on the hub itself.
  • audit_repo walks the registry in order and calls check_<id> for each entry. It runs the per-file checks file by file, so findings keep their order.
  • Hub special cases that apply to a single check are now the registry's hub flag. The hub's verbatim-section excision and the hub argument to the baseline read stay in code, since neither belongs to one check.
  • The suppression ids and the driftNote id parse now resolve against the registry. CHECK_ID_RE moves into spec/validate.py.
  • spec/validate.py now checks:
    • the registry's shape;
    • that every file check has a baseline check before it in its run, on the hub's filtered run too;
    • that no check carries an unknown key, and that only a repo check sets ground: false;
    • that every id a driftNote in registry/repos.json names is defined.

Size

Trimming the docstrings reached the ceiling only by dropping what a caller needs, so the Markdown reading and README checks move to spec/audit_markdown.py. That family depends on nothing else in the audit. Each docstring keeps what a caller needs: what the function returns, what None or an empty result means, and what it raises.

File Lines Ceiling in tests/test_audit_size.py
spec/audit.py 2442 3000
spec/audit_markdown.py 665 1000

spec/checks.json registers 33 checks.

Callers

  • spec/workflow_reuse.py and spec/fidelity_honesty.py import the audit, so they declare the same libraries. Their usage lines, RESYNC.md, and docs/reusable-workflows.md now run them through uv run --no-project --script.
  • tests/requirements.txt gains pyyaml>=6. The merge with develop keeps Restructure the Prose Gate into Extract, Normalize, and Evaluate Layers #3411's lines beside it.
  • mypy's missing-import override adds yaml.

Validate Action

The coverage step now runs the helper suite, with the suite's requirements installed, in place of spec/audit.py --selftest:

uvx --with-requirements tests/requirements.txt coverage@latest run --source=scripts,spec,host-setup -m unittest discover -s tests -p test_audit_helpers.py

uvx alone reads no script's inline metadata, so spec/workflow_reuse.py --selftest gets the same --with-requirements. OPERATIONS.md's local commands match.

Before and After Audit Comparison

The full sweep, covering every cataloged repository plus the fleet membership check, ran once at develop f646430 and once at this branch's head. The finding lists match line for line, apart from the run stamp. Before the parser commit, the hub plus PlexCleaner, ESPHome-Config, and Blog also matched at 7862c48.

Where a parser now reads a shape the hand-written reader refused, the helper suite asserts the new reading. None of these shapes occurs in the audited repositories.

  • A folded or continued concurrency group, or one written as a flow mapping, is now read rather than reported as unreadable. A sequence is still refused.
  • A folded python-directories value, or a double-quoted one with escapes, is now read the way GitHub passes it, rather than refused.
  • A workflow that does not parse as YAML produces one interface DRIFT saying its contract is unread, rather than one finding per missing job.
  • A [text][ref] in the README tagline counts as a link only where ref is defined, which is how GitHub renders it.

Verification

Each command below ran in the worktree after the last commit.

  • uvx ruff@latest format --check . -> 269 files already formatted
  • uvx ruff@latest check . -> All checks passed!
  • uvx mypy@latest -> Success: no issues found in 75 source files
  • npx markdownlint-cli2 over the changed Markdown -> Summary: 0 error(s)
  • python -m unittest discover -s tests, with tests/requirements.txt installed -> Ran 2837 tests ... OK (skipped=7)
  • uv run --no-project --script spec/audit.py --selftest -> SELFTEST PASS
  • python3 scripts/prose_lint.py . --allow-comments --diff origin/develop -> no violations. Without --allow-comments, every reported line is comment-added, which is why this pull request carries the comments label.
  • python3 scripts/repo_gate.py -> composite-actions, eol, eol-coverage, and sha-pin all report 0 issues, exit 0.
  • python3 spec/validate.py -> Spec validation OK: 22 cataloged repos classify cleanly. ...
  • python3 scripts/build_dist.py --check -> Generated skill distributions are current. It ran under a plain python3, as did scripts/canonical_review.py report.

Local Review

local-strict-review passes ran at the frontier tier over the whole branch diff. Each pass was recorded before the push it covers, and each push is attested on this pull request. The fixes they drove:

  • Workflow reading:
    • Contract tokens anchored on a newline match a job's first key.
    • A workflow is parsed once.
    • An unparseable workflow produces one interface finding.
    • An unparseable dependabot.yml produces one dependabot finding.
    • The concurrency refusal names the shapes the parser actually refuses.
  • driftNote ids: an id the catalog defines keeps its catalog reading, and only an id spec/checks.json alone defines reads as evaluated. This keeps the fleet sweep identical to develop.
  • Taglines: a link-free tagline is returned unchanged, an image counts as markup, and a code span is taken from the source as written.
  • Registry validation: spec/validate.py refuses:
    • an unknown key;
    • a ground: false outside a repo check;
    • a file check with no baseline check before it, on the hub's filtered run too;
    • a malformed id or suppression type. It reports that one rather than raising on it.
  • Tests:
    • New tests pin which checks skip the hub, the hub's own run, and the baseline slot each file check reads.
    • Others pin registry.driftnotes running last, split_jobs at a sibling key and at a file with no final newline, and the schedule gate reading every on: shape.
    • A declared Dependabot ecosystem is tested, and the smoke self-test is gated by a test.
    • test_strip_md_links_stays_linear checks a ratio rather than wall-clock time, since CI's coverage-traced run took 1.08 s against the old 1 s bound. It holds 20000 brackets under 8x the time for 5000, plus 0.05 s of slack. A quadratic reader fails that check unless it takes under about 6 ms at 5000 brackets, and the old wall-clock bound passed such a reader too.
  • Pointers: every line that invokes the audit, or a script that imports it, now spells uv run --no-project --script. Every pointer to a moved function names its new home. The trim's dropped rationales are restored as one line each. OPERATIONS.md says which tools share which spec/carried_text.py primitive.

Declined with evidence:

  • The validate action's helper-suite run duplicates the Python leg, and --selftest stays. The brief fixes both ("runs the test module under coverage instead", "--selftest stays as a smoke check").

  • spec/validate.py refuses a driftNote whose parenthesized dotted token names no check, and spec/checks.json carries family, description, and spec. The brief requires both.

  • The following stay as they read today, since a full sweep of every cataloged repository matches develop and GitHub renders each case the same way:

    • markdown-it reads a heading inside an HTML block, a fence, or a container differently from the old line regexes;
    • a ## X ## closing sequence reads differently for presence than for verbatim extraction;
    • an undefined [x][ref] in a tagline reads as literal text.

    The follow-up lane that moves all region and link reading onto markdown-it-py removes the remaining split.

  • split_jobs returns {} for a flow-style jobs: mapping, exactly as develop's reader did, and no contract declares verbatimJobs.

Pre-Existing Findings, Not Filed

  • --check sentence-length reports pre-existing sentences that this branch only touched to rename a path: docs/reusable-workflows.md:40, scripts/build_dist.py:198, :202, :321, scripts/canonical_review.py:157, scripts/carry.py:418, :492, and tests/test_build_dist.py:949.
  • spec/validate.py's contains_description_markdown_link still matches brackets by hand.
  • CHECK_ID_RE reads any parenthesized lowercase dotted token as a check id, a file name such as (cspell.json) included.
  • scripts/canonical_review.py and scripts/carry.py each write the unfenced ## predicate inline rather than calling spec/carried_text.py. The follow-up lane moves them.
  • tests/test_audit_size.py follows the shape of the two size tests already in tests/. Those two disagree on assertLess versus assertLessEqual.
  • tests/workflow_steps.py and tests/test_docker_task_workflow.py read workflows by indentation, where PyYAML is now available to the suite.
  • The reports/*.md headers still print python3 for their generators. The generator strings now say uv run, so the next regeneration rewrites the headers.
  • scripts/carry.py keeps its own region reading (section_span, comparable_region) beside spec/carried_text.py. The follow-up lane covers it.

🤖 Generated with Claude Code

The inline matrix in spec/audit.py becomes tests/test_audit_helpers.py, a
unittest module with one subTest per case. Temporary git repositories are
built by setUp-style helpers, and the globals() swaps are mock.patch calls.
The invalid-byte probe tests follow the probe out of tests/test_audit.py.

--selftest stays as a smoke check of each helper family that runs in well
under a second.
spec/audit.py declares pyyaml and markdown-it-py in PEP 723 inline
metadata. Workflow jobs, contract tokens, the python-directories input,
the concurrency group, the schedule gate, and dependabot.yml's ecosystems
are read from the parsed document. Headings, fenced blocks, the README
title and intro, link stripping, and the License column are read from
markdown-it-py tokens.

The region reading the audit, carry.py, build_dist.py, and
canonical_review.py share moves to spec/carried_text.py, standard library
only, so those three keep running under a plain python3.

workflow_reuse.py and fidelity_honesty.py import the audit, so they
declare the same libraries and their command lines run through uv.
Each check audit_repo runs is an entry in spec/checks.json with an id, a
family, a one-line description, the spec file it reads, its scope, and
whether it runs on the hub itself. audit_repo walks the registry in order
and calls check_<id> for each, running the per-file checks file by file
so findings keep their order. The per-check hub exceptions are the hub
flag. The rest stay code.

The suppression ids and the driftNote id parse resolve against the
registry, and spec/validate.py checks the registry's shape and that
every id a driftNote names is defined.
Each docstring keeps what a caller needs: what the function returns,
what None or an empty result means, and what it raises. The Markdown
reading and the README checks move to spec/audit_markdown.py, which
depends on nothing else in the audit. A size test holds spec/audit.py
under 3,000 lines and the new module under 1,000.
The validate action's coverage step runs tests/test_audit_helpers.py in
place of the audit's --selftest, with tests/requirements.txt installed,
since uvx alone reads no script's inline metadata. workflow_reuse.py's
self-test gets the same libraries, and OPERATIONS.md's local commands
match.
…istry-parsers

# Conflicts:
#	pyproject.toml
#	tests/requirements.txt
A forbidden token anchored on a newline matches a job's first key again,
since a job's token view opens with its key line. A driftNote naming a
registered check says the audit evaluates it. spec/validate.py refuses a
file check with no baseline check before it in its run. The invalid-byte
probe's platform flag lives in the test module, and OPERATIONS.md names
the suite CI runs of the audit.
An unparseable workflow is one interface finding, and the concurrency
check no longer adds advice for a syntax error. The ambient GIT_DIR test
runs the helper suite, since the smoke self-test runs no git. AUDIT.md
names spec/checks.json where the runner resolves a driftNote id, and the
stale comments and docstrings the moved self-test left are updated.
Copilot AI lite review requested due to automatic review settings October 23, 2026 23:07
@ptr727 ptr727 added the comments Permits the comment lines the pull request adds or edits, which the prose gate otherwise refuses label Oct 23, 2026
@ptr727 ptr727 changed the title Move Audit Tests to tests/, Parse with PyYAML and markdown-it-py, Add a Check Registry Move Audit Tests to tests/, Parse YAML and Markdown, Add a Check Registry Oct 23, 2026
@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ptr727

ptr727 commented Oct 23, 2026

Copy link
Copy Markdown
Owner Author

A recorded local strict-review pass covers head d92bb4e7e5edc73f46204291a2f3877bdcba5997, the content this pull request carries at that commit against develop, and it recorded 11 findings.

@ptr727 ptr727 changed the title Move Audit Tests to tests/, Parse YAML and Markdown, Add a Check Registry Move Audit Tests to tests/, Parse YAML and Markdown, Add Check Registry Oct 23, 2026
@codecov

codecov Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.75000% with 29 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (develop@f646430). Learn more about missing BASE report.

Files with missing lines Patch % Lines
spec/audit_markdown.py 92.08% 27 Missing ⚠️
spec/validate.py 96.82% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             develop    #3416   +/-   ##
==========================================
  Coverage           ?   80.78%           
==========================================
  Files              ?       23           
  Lines              ?     8848           
  Branches           ?        0           
==========================================
  Hits               ?     7148           
  Misses             ?     1700           
  Partials           ?        0           
Flag Coverage Δ
python-3.13 80.78% <93.75%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ 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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved critical and moderate findings affect audit correctness, validation robustness, Markdown handling, and CI smoke-test coverage.

15 open issues
What changed in this PR

This pull request moves audit tests into tests/, adopts maintained YAML and Markdown parsers, introduces registry-driven audit checks, and updates shared tooling, validation, CI, and documentation.

Changes:

  • Relocates audit helper and probe tests, adds size checks, and updates test dependencies and coverage commands.
  • Adds PyYAML and markdown-it-py parsing while centralizing shared carried-text handling.
  • Adds spec/checks.json, registry validation, and registry-driven audit execution.
  • Updates standalone scripts, operational documentation, workflow commands, and mypy configuration.
File Description
tests/​test_spec_validate.py Tests check-registry validation and drift-note identifier handling.
tests/​test_git_env.py Runs the relocated helper suite under ambient Git environments.
tests/​test_fidelity_honesty.py Updates references to the moved helper tests.
tests/​test_build_dist.py Updates lazy-import tests for shared carried-text handling.
tests/​test_audit.py Removes relocated probe coverage and updates audit test references.
tests/​test_audit_size.py Enforces size ceilings for audit modules.
tests/​requirements.txt Adds the PyYAML test dependency.
spec/​workflow_reuse.py Adds parser dependencies and updates standalone usage.
spec/​validate.py Validates registry structure, ordering, and drift-note IDs.
spec/​fidelity_honesty.py Adds parser dependencies and updates standalone usage.
spec/​checks.json Registers audit checks and execution metadata.
spec/​carried_text.py Provides shared standard-library text boundary helpers.
spec/​audit_markdown.py Provides parser-backed Markdown and README checks.
scripts/​carry.py Uses shared carried-text handling for carried regions.
scripts/​canonical_review.py Uses shared carried-text handling for canonical content.
scripts/​build_dist.py Uses shared carried-text handling for distribution processing.
RESYNC.md Updates the fidelity report invocation.
pyproject.toml Adds the YAML missing-import override for mypy.
OPERATIONS.md Updates local audit and coverage commands.
docs/​reusable-workflows.md Updates the workflow reuse command.
AUDIT.md Documents registry-backed checks and drift-note behavior.
.github/​actions/​validate/​action.yml Updates CI coverage and audit helper execution.

Review effort: Lite

Comment thread spec/audit.py Resolved
Comment thread spec/audit.py Resolved
Comment thread spec/audit.py Resolved
Comment thread spec/checks.json Resolved
Comment thread spec/validate.py Resolved
Comment thread spec/audit.py Resolved
Comment thread spec/carried_text.py Resolved
Comment thread spec/checks.json Resolved
Comment thread tests/test_audit_helpers.py Resolved
Comment thread tests/test_audit_helpers.py Resolved
A driftNote id the catalog defines keeps its catalog reading, and only a
registry-only id reads as evaluated, so the full fleet sweep now matches
develop line for line. A link-free tagline is returned unchanged rather
than rebuilt from tokens. An unparseable dependabot.yml is one finding
rather than a missing ecosystem. The smoke self-test is gated by a test,
the test module has one git identity and one temporary-directory helper,
the dropped rationales are back as one line each, and OPERATIONS.md names
spec/carried_text.py among the shared primitives.
Each line that invokes spec/audit.py, the generated issue body, the
configure.sh note, AUDIT.md, RESYNC.md, and the audit-a-repo skill, now
spells it uv run --no-project --script, since the script imports PyYAML.
scripts/README.md and a test docstring name spec/carried_text.py's
region reading, and spec/validate.py refuses an unknown check key and a
ground false outside a repo check.
A tagline carrying an image reads as carrying markup again, with the image
reduced to its alt text. AUDIT.md says only an id spec/checks.json alone
defines reads as evaluated, matching the catalog's precedence. The audit
drops two urllib imports the Markdown split left unused, and
canonical_review.py names spec/carried_text.py's normalize.
@ptr727

ptr727 commented Oct 24, 2026

Copy link
Copy Markdown
Owner Author

A recorded local strict-review pass covers head e8469e0a70f22dfd54558465019d60d84ca7eedf, the content this pull request carries at that commit against develop, and it recorded 6 findings.

spec/validate.py reports a malformed check id or suppression type rather
than raising on it, and holds the baseline-before-file rule on the hub's
filtered run too. The remaining invocation lines run the audit and the
fidelity report through uv, AUDIT.md's id-resolution sentence says what
the runner does, the test module derives its init sequence from one
identity tuple, and OPERATIONS.md drops a code span markdownlint refuses.
check_interface parses its workflow once, through the same YAML helper
the concurrency and dependabot reads use. driftnote_findings resolves an
id with one catalog lookup and the registry, and the unused owner helper
goes. The validate action's description names the audit helper suite.
OPERATIONS.md says which tools share the fence step and which share the
region reading, AUDIT.md's secrets runbook runs the audit through uv, and
a validate.py docstring drops a pointer to code that no longer exists.
@ptr727

ptr727 commented Oct 24, 2026

Copy link
Copy Markdown
Owner Author

A recorded local strict-review pass covers head 9dbf189ef96ee65169a161e60e6aed5012124f65, the content this pull request carries at that commit against develop, and it recorded 3 findings.

A test holds registry.driftnotes as the last registered check, since it
counts the findings before it. OPERATIONS.md and scripts/README.md scope
the shared fence reading to the audit's region extraction, and AUDIT.md
states the registry-only id as the exception after its conclusion.
GOVERNANCE.md, spec/validate.py, and spec/resolve_description.py name
spec/audit_markdown.py as description_findings' home. The concurrency
refusal names the shapes the parser actually refuses, and a test helper
module drops a standard-library-only claim that no longer holds.
AUDIT.md says a registry-only id's finding names the check the audit
evaluates and the note goes once that check reports nothing, rather than
calling it an exception the run decides. OPERATIONS.md names both audit
suites CI runs.
@ptr727

ptr727 commented Oct 24, 2026

Copy link
Copy Markdown
Owner Author

A recorded local strict-review pass covers head 2c4cc9c5d72b76c4c660abcd79a50729498a046c, the content this pull request carries at that commit against develop, and it recorded 2 findings.

A positive case holds dependabot_ecosystems reading each declared
package-ecosystem, and OPERATIONS.md says carry.py shares only the
comparable form and the fence step from spec/carried_text.py.
strip_md_links takes a code span from the source rather than rebuilding
it, since the parser trims its padding. New tests pin which checks skip
the hub, which baseline slot each file check reads, and split_jobs at a
sibling key and at a file with no final newline. build_dist.py states
the reason it imports spec/carried_text.py lazily as it now stands.
audit_repo run as the hub skips only the hub-false checks and still runs
the file checks after them. check_dependabot_ecosystems reports an
unparseable file as one finding, and the schedule gate reads a list and
a scalar on: as well as a block mapping.
@ptr727

ptr727 commented Oct 24, 2026

Copy link
Copy Markdown
Owner Author

A recorded local strict-review pass covers head d32d3065129a2b7219bf5212eb49cdf5d6a06cfd, the content this pull request carries at that commit against develop, and it recorded 0 findings.

The CI unit job runs under coverage, where markdown-it's pass over 20000
unmatched brackets took 1.08 s against the 1 s bound. The test now times
5000 and 20000 brackets and holds the larger under eight times the
smaller, which a quadratic reader exceeds and a linear one does not.
@ptr727

ptr727 commented Oct 24, 2026

Copy link
Copy Markdown
Owner Author

A recorded local strict-review pass covers head b914f083bf696743319c8175f0c84016ad594af0, the content this pull request carries at that commit against develop, and it recorded 1 finding.

@ptr727
ptr727 merged commit c66778b into develop Oct 24, 2026
13 checks passed
@ptr727
ptr727 deleted the audit-refactor-registry-parsers branch October 24, 2026 03:43
ptr727 added a commit that referenced this pull request Oct 24, 2026
…#3417)

## Summary

Promotes the pull requests merged into develop since release 2.0.1148.
Each carries a maintainer decision of 2026-10-23 recorded on #3391.

- [#3407](#3407): rewrites
the write guard as a small lexer of 1,653 lines. It fails open under its
stated threat model and fails closed only on requirement 4. A test holds
it at 2,500 lines or fewer (item 1).
- [#3409](#3409): adds
CODESTYLE rules. Tests live in `tests/`, and a maintained parser
replaces a hand-written one. A script declares its dependencies in PEP
723 metadata and runs as `uv run --no-project --script`. It switches the
callers to that spelling and adds a managed allow rule to the
agent-safety installer (item 1 follow-ups).
- [#3410](#3410): splits
`pr_review.py` into the gate, a Copilot adapter reading its vocabulary
from JSON, and `pr_cleanup.py`. It collapses the exit codes and adds
`tests/requirements.txt` and the CI runners.
- [#3411](#3411): rebuilds
the prose gate on Pygments and markdown-it-py, with its vocabulary and
exemptions in `prose-gate.json` and a repo-local override.
- [#3415](#3415): holds
the Merge Gate on a part of a Copilot review body the tooling cannot
place. The hold lasts until an owner acknowledges it with outcomes (item
4 follow-up).
- [#3416](#3416): moves
the audit self-test to `tests/` and reads YAML and Markdown with PyYAML
and markdown-it-py. It adds the check registry `spec/checks.json` and
splits out `spec/audit_markdown.py`. It shares `spec/carried_text.py`
with carry, `build_dist`, and `canonical_review`.
- [#3418](#3418): fixes
the findings this promotion's first review round raised on the content
above. It aligns the drive-pr escalation line with pr-review-conduct and
names `tests/requirements.txt` where the prose said the tests need
nothing installed. It also restores the `uvx` launcher in a
release-guard assertion.
- [#3419](#3419): fixes
the finding the second review round raised. The Copilot adapter's prose
path reads a coverage count longer than `COUNT_DIGITS` as `unknown`,
sharing `count_state` with the marker path.
- [#3420](#3420): fixes
the finding the third review round raised, under the maintainer's
ruling. `hub_fetch_run.py` falls back to `git ls-remote` where the API
lookup of `main` fails, so the prose gate and its data are fetched at
one commit. Where neither lookup resolves, the documented fail-open
against `main` stays.
- [#3421](#3421): fixes
the findings the fourth review round raised. The Copilot adapter reads a
finding count longer than `COUNT_DIGITS` as unknown rather than zero,
through one length check and one None-aware sum. The host-setup
gates-and-tests row says `uv` installs the test requirements.

Closes #3408

## Constituent coverage

The Copilot rounds on this promotion ran at Lite effort and state no
full coverage, so Merge Gate item 2 rests on the promotion case. Each
change this promotion carries had full coverage on its own pull request
into develop, at its merged head:

| Pull request | Merged head | Coverage |
| --- | --- | --- |
| [#3407](#3407) |
`9efc1002` | attested local pass, `coverage=local` |
| [#3409](#3409) |
`bcc7a9e1` | attested local pass, `coverage=local` |
| [#3410](#3410) |
`00cec726` | attested local pass, `coverage=local` |
| [#3411](#3411) |
`bd128136` | attested local pass, `coverage=local` |
| [#3415](#3415) |
`5be9db87` | attested local pass, `coverage=local` |
| [#3416](#3416) |
`b914f083` | attested local pass, `coverage=local` |
| [#3418](#3418) |
`3a86cb51` | attested local pass, and a Copilot table naming exactly the
changed files, `coverage=table` |
| [#3419](#3419) |
`6e7b868d` | attested local pass, and a Copilot table naming exactly the
changed files, `coverage=table` |
| [#3420](#3420) |
`9b492290` | attested local pass, and a Copilot table naming exactly the
changed files, `coverage=table` |
| [#3421](#3421) |
`743010e6` | attested local pass, and a Copilot table naming exactly the
changed files, `coverage=table` |

🤖 Generated with [Claude Code](https://claude.com/claude-code)
ptr727 added a commit that referenced this pull request Oct 24, 2026
…3431)

The follow-up #3416 named: the audit, `scripts/carry.py`,
`scripts/build_dist.py`, and `scripts/canonical_review.py` now split a
document at headings and code blocks markdown-it-py reads, and
`spec/carried_text.py`, the last hand-written fence reader they shared,
is gone.

## What Changes

### One Reader, Now a Parser

- `spec/fidelity_text.py` replaces `spec/carried_text.py`. It parses
block structure only, CommonMark plus tables. `headings()` reads
top-level ATX headings, `code_blocks()` and `code_lines()` read fenced
and indented code at any depth, and `sections()` is the one place a
level-two region's extent is decided. `section_span()`,
`extract_section()`, `strip_sections()`, and
`canonical_review.split_markdown()` all slice from it.
- `normalize()` and the two workflow-YAML masks it applies,
`_ACTION_PIN` and `_JOB_NEEDS`, move into the same module unchanged. The
audit, `spec/audit_markdown.py`, and `scripts/carry.py` all call
`normalize()`, so one shared home is what the masks need.
- **The audit:** `spec/audit.py` and `spec/audit_markdown.py` import
from the new module. `_atx_headings()` and `unfenced_text()` read
through it too, so presence checks and region extraction split a
document alike.
- **`scripts/carry.py`:** `h2_headings()` and `section_span()` call the
shared reader. `fence_step()` is gone.
- **`scripts/canonical_review.py`:** `split_markdown()` cuts regions at
the shared reader's level-two headings.
- **`scripts/build_dist.py`:** include markers and heading bodies read
code lines and headings from the shared reader. A body leaving a fence
open is a fence the parser reads as unclosed. `_MAX_INDENT` stays only
for a marker line itself.

### Running Them

- `scripts/carry.py`, `scripts/build_dist.py`, and
`scripts/canonical_review.py` each declare `markdown-it-py>=4` in a PEP
723 block, in `spec/audit.py`'s form.
- Every caller now runs them as `uv run --no-project --script`:
- the validate action and `canonical-review-sweep.yml`, which now sets
up uv rather than Python;
- `host-setup/menu.sh` and `host-setup/menu.ps1`, whose plain-Python
helpers had no caller left and are removed;
- `RESYNC.md`, `STANDUP.md`, `OPERATIONS.md`, `scripts/README.md`,
`docs/token-efficiency-plan.md`, and the `local-strict-review` and
`skill-lifecycle` skills, regenerated;
  - each script's own usage text and the commands its messages print.
- **Two plain-`python3` importers stay safe.**
`scripts/skills_install.py` imports `build_dist`, and
`scripts/handoff.py` imports `carry`. Both modules import the shared
reader lazily, on the path that reads a region, so neither importer
loads markdown-it-py.

### Tests

- `tests/test_build_dist.py`'s check that importing `build_dist` loads
no `carried_text` now imports `skills_install` in a fresh interpreter
and checks that neither `fidelity_text` nor `markdown_it` loads. That is
the invariant the plain-`python3` install path relies on.
`tests/test_carry.py` gains the same check for `handoff`.
- The two per-line cache tests are gone, since nothing reads per line
any more. The test that the accessor adds `spec/` to `sys.path` once
stays.
- `test_an_indented_heading_is_a_boundary_as_it_is_to_the_audit`
asserted the old reader's quirk, a four-space `## ` line read as a
heading. It now asserts what the parser reads: three spaces open a
heading, and four open an indented code block.
- `tests/test_fidelity_text.py` covers the shared reader on constructed
documents, a quoted or listed heading and a fence ending a file with no
final newline among them. `tests/import_probe.py` holds the import probe
both laziness tests share.
- CI already installs `tests/requirements.txt`, which carries
`markdown-it-py`, before the suite runs.

## Behavior

- The full audit sweep, every cataloged repository plus the fleet
membership check, ran at develop 9f19b2a before the change and on this
branch after it, both against the same hub `main`. The two outputs match
line for line apart from the run stamp.

- `uv run --no-project --script scripts/canonical_review.py list` and
`status` print byte-identical output before and after, so every review
unit key and digest is unchanged.
- `uv run --no-project --script scripts/build_dist.py --check` reports
the generated trees current, and a no-flag run rewrote nothing beyond
the two edited skills' mirrors.
- A probe compared the old and new region readers over every local
Markdown file, heading boundaries, `extract_section`, and
`strip_sections` alike. The fleet's own documents read identically. The
only differences are in vendored third-party documents and are the
parser being right where the line reader was not: a fence indented four
spaces inside a list item, a fence whose closer is indented differently
from its opener, and a `## ` inside an HTML comment block.

## Local Review

`local-strict-review` passes ran at the `frontier` tier over the whole
branch diff, each recorded before the next commit and attested after
each push. The fixes they drove before the pull request opened:

- A fence left open at a file with no final newline read as closed, so
`build_dist.py` stopped refusing that include body. The last content
line now counts.
- A heading quoted in a blockquote or nested in a list item opened a
region. Only top-level headings do now, which no tracked document's
split depends on.
- The rule for where a region ends was written three times. `sections()`
now holds it.
- `build_dist.py` rebuilt a code-line set the shared reader already
returns.
- The `code_blocks()` docstring claimed every region copier refuses an
open fence. Only the include fill does, and it now says so. The
`heading_body()` docstring names where an include body ends differently
from a fidelity region.
- `scripts/README.md`, `host-setup/README.md`, and the menu comments
still described plain-Python paths.
- The shared import probe moved out of a test module into
`tests/import_probe.py`.

### After the Pull Request Opened

The third pass, after the edit budget for the opening push was spent,
and Copilot's round on that head raised the first two points below.
Later passes raised the rest, and the last pass, on the current head,
raised nothing:

- An include body holding a fence its blockquote or list item ends, with
no closing line, is refused, which the old stepper did not do. Filling
one was tried and reverted. An end marker indented to a list item's
content continues that item past the blank line the fill writes, so the
fence swallows the marker on the next scan. Refusing is the loud
outcome, no tracked source holds such a fence, and tests pin the list
item and blockquote cases. The docstrings now give that reason.
- The `audit_markdown.py` heading docstrings read as if only fenced
headings are skipped. `_atx_headings()` now states that it reads
top-level `#` headings only, and the readers built on it point there.
- "which the fidelity checks read with too" read awkwardly in three
docstrings, and now says "also use".
- The laziness tests imported `build_dist` and `carry` directly. They
now import `skills_install` and `handoff`, the real plain-`python3`
importers.
- `_unindented()`'s docstring said a line indented past three spaces is
code or a paragraph's continuation, which is false in a list item. It
now says the bound is held on the line's own indentation in every
container.
- `heading_body()`'s docstring, and the `skill-lifecycle` skill, said a
body ends at the next heading at its level or above. They now say a
top-level `#` heading, since the shared reader reads a setext heading
and one in a blockquote or list item as content.

## Pre-Existing Findings, Filed

- #3432: `scripts/build_dist.py` skips an include marker indented past
three spaces without refusing it, so the region stays empty and
`--check` exits 0. Class: pre-existing correctness defect.

## Pre-Existing Findings, Not Filed

- `--check sentence-length` reports sentences this branch touched only
to rename a command or a path:
`.agents/skills/skill-lifecycle/SKILL.md:18` and `:19` and their
generated copies, `OPERATIONS.md:40`, `RESYNC.md:77`,
`host-setup/README.md:119`, `host-setup/menu.ps1:252`,
`host-setup/menu.sh:394` and `:449`, `scripts/README.md:7`, `:381`, and
`:385`, `scripts/build_dist.py:313` and the comment it moved into
`heading_body()`, and `scripts/carry.py:420`. Class: style, on prose
this change did not write.

## Deferred

- #3426 covers `spec/validate.py`'s own Markdown readers and the
`references()` against `_LINK_DEF` split in `spec/audit_markdown.py`.
`repo-config/configure.sh` runs `spec/resolve_description.py`, which
imports `validate`, under a probed Python 3.7+, and reading link targets
from tokens changes what the carried-link check sees. Each needs its own
comparison, so this change stops at deleting `carried_text.py`.

## Verification

Each command below ran in the worktree on this branch.

- `uv run --no-project --with-requirements tests/requirements.txt python
-m unittest discover -s tests` at the final head -> `Ran 2872 tests ...
OK (skipped=7)`
- `uv run --no-project --script spec/audit.py`, before and after ->
identical apart from the run stamp, rerun after the review fixes that
touched region code. One downstream repository's own `main` moved
between runs, which only its header line shows.
- `uv run --no-project --script scripts/build_dist.py --check` ->
`Generated skill distributions are current.`
- `uv run --no-project --script scripts/canonical_review.py list` and
`status`, before and after -> byte-identical
- `uvx ruff@latest format --check .` and `uvx ruff@latest check .` ->
clean
- `uvx mypy@latest` -> `Success: no issues found in 76 source files`
- `python3 scripts/repo_gate.py` -> `composite-actions`, `eol`,
`eol-coverage`, and `sha-pin` report 0 issues
- `uvx --from actionlint-py actionlint
.github/workflows/canonical-review-sweep.yml` -> clean
- `npx markdownlint-cli2` over the changed Markdown -> `0 issues`
- `shellcheck host-setup/menu.sh` -> clean, and `host-setup/menu.ps1`
parses with no errors under pwsh
- `python3 spec/validate.py` -> `Spec validation OK`
- The prose gate over the diff reports only `comment-added` lines, which
is why this pull request carries the `comments` label. Most are comments
that moved with the code they describe.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comments Permits the comment lines the pull request adds or edits, which the prose gate otherwise refuses

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants