feat(python): Python starter checks, fixture test coverage, and real-world validation#15
Merged
brettdavies merged 6 commits intodevfrom Apr 16, 2026
Merged
Conversation
Implements the first Python source checks, proving the ast-grep + tree-sitter-python pipeline works end-to-end: - code-bare-except: detects bare `except:` clauses (P4 — actionable errors) - p4-sys-exit: detects `sys.exit()` outside `if __name__ == "__main__":` guard - p6-no-color: detects NO_COLOR env var handling for color output Adds language-parameterized helpers `has_pattern_in()` and `find_pattern_matches_in()` to `src/source.rs` so future Python checks can dispatch through the same ast-grep machinery as Rust checks. The original Rust-only helpers remain as thin wrappers. The bare-except and sys-exit checks walk the AST manually to detect patterns that aren't expressible as a single ast-grep template (e.g. "any except_clause without a type child", "any sys.exit() not under a __main__ guard"). The no-color check uses pattern matching across common Python env-var access forms with a string-literal fallback. Test coverage: 38 new unit tests covering happy paths, edge cases (string literals, comments, nested guards, multiple violations), and applicability across language manifests.
The source-only, broken-rust, and perfect-rust fixture tests were marked #[ignore] pending parseable source and built binaries in fixture directories. Both conditions are now met. Removing the ignore attributes brings these into CI coverage. Integration suite: 37 passed, 0 ignored (was 34 + 3 ignored).
__main__.py is the Python entry-point module (like Rust's main.rs). sys.exit() is expected there. Real-world validation against markitdown exposed this as a false positive.
All 6 implementation units complete: 3 Python source checks, 3 fixture tests un-ignored, real-world validation passed, design doc updated.
Review-driven fixes across Python source checks, source helpers, and project infrastructure: - Fix is_main_guard false positives: normalize comparison instead of exact string match — handles inline comments, parenthesized forms, no-spaces, and reversed operand order - Fix is_bare_except to split only on first line of node text - Fix __main__.py skip to check filename component, not suffix - Remove dead except* branch (PEP 654 requires a type after except*) - Replace RefCell with OnceLock for parsed_files lazy init (TOCTOU fix) - Refactor check_X functions to return CheckStatus instead of CheckResult — eliminates ID string literal triplication per check - Deduplicate has_string_literal into source::has_string_literal_in - Pre-build AST patterns in no_color (parse source once, not 10x) - Inline single-use find_bare_excepts/find_unguarded_sys_exits wrappers - Add tests for all new guard variants, multi-file aggregation, run()-level pass/warn branches, typed except*, and string fallback
Proves the tree-sitter-python pipeline works end-to-end via binary invocation, not just unit tests. The fixture contains a bare except clause and an unguarded sys.exit() call. The integration test verifies source-layer checks fire and code-bare-except specifically fails.
15 tasks
15 tasks
brettdavies
added a commit
that referenced
this pull request
Apr 16, 2026
…-chain pin (#17) ## Summary Two related hardening efforts bundled: 1. **Convention enforcement** — standardizes all source checks on a single pattern (`check_x()` returns `CheckStatus`; `run()` is the sole `CheckResult` constructor, using `self.id()`/`self.group()`/`self.layer()`). Documents the convention in CLAUDE.md, refactors all 16 Rust source checks to match, and adds a Rust-only integration test that walks the source tree and fails CI if any `check_x()` still returns `CheckResult`. 2. **Toolchain supply-chain pin** — `rust-toolchain.toml` now pins to a specific `X.Y.Z` release with a trailing rustc commit-SHA comment. Rustup verifies component SHA256s from the distribution manifest, so the pin is effectively a SHA pin (the manifest is the toolchain's lockfile). Both local and CI read the same file and install identical bits. Motivated by a CI-only clippy failure during this PR's own review: local clippy 1.94 passed a lint that CI clippy 1.95 rejected. Convention enforcement was identified during the [PR #15 code review](#15) — the 3 new Python checks were fixed in that PR, but the 16 existing Rust checks still used the old pattern. The toolchain pin was added mid-review after the clippy divergence exposed that floating `channel = "stable"` leaves local and CI on different rustc versions. ## Changelog <!-- No user-facing changes — internal refactor + supply-chain hardening. --> ## Type of Change - [x] `refactor`: Code refactoring (no functional changes) - [x] `chore`: Maintenance tasks (supply-chain toolchain pin) - [x] `docs`: Documentation update (CLAUDE.md conventions + toolchain policy) - [x] `test`: Adding or updating tests (convention enforcement test) ## Related Issues/Stories - Related PRs: #15 (review finding that identified the convention drift) - Follow-up: `dot-github` todo #4 — determine the automated bump workflow for `rust-toolchain.toml` across repos (central reusable workflow vs. local script vs. hybrid) ## Testing - [x] Unit tests added/updated - [x] Integration tests added/updated - [x] All tests passing **Test Summary:** - Unit tests: 304 passing (unchanged count — refactor preserves behavior) - Integration tests: 39 passing (+1 new: `convention_check_x_returns_check_status_not_check_result`) - Clippy: clean with `-Dwarnings` on rustc 1.94.1 (pinned) ## Files Modified **Modified:** - `CLAUDE.md` — added Source Check Convention section (Tier 1) and Toolchain Pin section documenting the 7-day quarantine policy - `rust-toolchain.toml` — pinned `channel = "1.94.1"` with trailing comment naming the rustc commit SHA and release date; bumped from floating `channel = "stable"` - `scripts/hooks/pre-push` — removed `rustup update stable` step; pin + rustup's manifest verification now handle toolchain integrity - 16 files in `src/checks/source/rust/` — `check_x()` returns `CheckStatus`, `run()` uses `self.id()`/`self.group()`/ `self.layer()` instead of duplicated string/enum literals - `src/checks/source/rust/no_pager.rs` — collapsed nested `if` inside a `match` arm into a match guard (fixes clippy `collapsible_match` lint introduced in clippy 1.95) - `tests/integration.rs` — added `convention_check_x_returns_check_status_not_check_result` enforcement test ## Key Details **Tier 1 (Document):** CLAUDE.md now codifies the source check structure. Covers the `Check` trait impl shape, the `check_x() → CheckStatus` separation, and the convention that `run()` is the sole `CheckResult` constructor. Also adds a new Toolchain Pin section explaining the supply-chain pin policy. **Tier 2 (Refactor):** All 16 Rust source checks aligned to the convention. Net -132 lines by removing duplicated `CheckResult` construction (previously the `id` / `label` / `group` / `layer` fields were hardcoded in both `run()` and every `check_x()` helper). **Tier 3 (Enforce):** Integration test walks `src/checks/source/` with `std::fs` (no shell dependencies), finds every `fn check_` signature (any visibility, single-line or multi-line), and fails if any return `CheckResult` instead of `CheckStatus`. Uses `CARGO_MANIFEST_DIR` for a stable absolute path. **Toolchain pin format:** `channel = "1.94.1" # rustc e408947bfd200af42db322daf0fadfe7e26d3bd1, released 2026-03-25`. Comment mirrors the GitHub Actions SHA-pin pattern (`action@<sha> # vN.N.N`). Policy: bump only via reviewed PR after the new stable has aged ≥7 days (supply-chain quarantine consistent with `UV_EXCLUDE_NEWER` / bun `minimumReleaseAge` / `npm_config_min_release_age` in dotfiles). ## Benefits - Eliminates ID string literal triplication (3 copies per check → 1 authoritative source in `fn id()`) - New checks can't drift — the convention is documented AND enforced by CI - -132 lines of boilerplate removed - Local and CI toolchains are guaranteed identical — no more "local clippy older than CI clippy" false greens - Toolchain updates route through reviewed PRs with a 7-day quarantine, matching the broader brettdavies supply-chain posture ## Breaking Changes - [x] No breaking changes ## Deployment Notes - [x] No special deployment steps required ## Checklist - [x] Code follows project conventions and style guidelines - [x] Commit messages follow Conventional Commits - [x] Self-review of code completed - [x] Tests added/updated and passing - [x] No new warnings or errors introduced - [x] Changes are backward compatible
brettdavies
added a commit
that referenced
this pull request
Apr 16, 2026
…-chain pin (#17) ## Summary Two related hardening efforts bundled: 1. **Convention enforcement** — standardizes all source checks on a single pattern (`check_x()` returns `CheckStatus`; `run()` is the sole `CheckResult` constructor, using `self.id()`/`self.group()`/`self.layer()`). Documents the convention in CLAUDE.md, refactors all 16 Rust source checks to match, and adds a Rust-only integration test that walks the source tree and fails CI if any `check_x()` still returns `CheckResult`. 2. **Toolchain supply-chain pin** — `rust-toolchain.toml` now pins to a specific `X.Y.Z` release with a trailing rustc commit-SHA comment. Rustup verifies component SHA256s from the distribution manifest, so the pin is effectively a SHA pin (the manifest is the toolchain's lockfile). Both local and CI read the same file and install identical bits. Motivated by a CI-only clippy failure during this PR's own review: local clippy 1.94 passed a lint that CI clippy 1.95 rejected. Convention enforcement was identified during the [PR #15 code review](#15) — the 3 new Python checks were fixed in that PR, but the 16 existing Rust checks still used the old pattern. The toolchain pin was added mid-review after the clippy divergence exposed that floating `channel = "stable"` leaves local and CI on different rustc versions. ## Changelog <!-- No user-facing changes — internal refactor + supply-chain hardening. --> ## Type of Change - [x] `refactor`: Code refactoring (no functional changes) - [x] `chore`: Maintenance tasks (supply-chain toolchain pin) - [x] `docs`: Documentation update (CLAUDE.md conventions + toolchain policy) - [x] `test`: Adding or updating tests (convention enforcement test) ## Related Issues/Stories - Related PRs: #15 (review finding that identified the convention drift) - Follow-up: `dot-github` todo #4 — determine the automated bump workflow for `rust-toolchain.toml` across repos (central reusable workflow vs. local script vs. hybrid) ## Testing - [x] Unit tests added/updated - [x] Integration tests added/updated - [x] All tests passing **Test Summary:** - Unit tests: 304 passing (unchanged count — refactor preserves behavior) - Integration tests: 39 passing (+1 new: `convention_check_x_returns_check_status_not_check_result`) - Clippy: clean with `-Dwarnings` on rustc 1.94.1 (pinned) ## Files Modified **Modified:** - `CLAUDE.md` — added Source Check Convention section (Tier 1) and Toolchain Pin section documenting the 7-day quarantine policy - `rust-toolchain.toml` — pinned `channel = "1.94.1"` with trailing comment naming the rustc commit SHA and release date; bumped from floating `channel = "stable"` - `scripts/hooks/pre-push` — removed `rustup update stable` step; pin + rustup's manifest verification now handle toolchain integrity - 16 files in `src/checks/source/rust/` — `check_x()` returns `CheckStatus`, `run()` uses `self.id()`/`self.group()`/ `self.layer()` instead of duplicated string/enum literals - `src/checks/source/rust/no_pager.rs` — collapsed nested `if` inside a `match` arm into a match guard (fixes clippy `collapsible_match` lint introduced in clippy 1.95) - `tests/integration.rs` — added `convention_check_x_returns_check_status_not_check_result` enforcement test ## Key Details **Tier 1 (Document):** CLAUDE.md now codifies the source check structure. Covers the `Check` trait impl shape, the `check_x() → CheckStatus` separation, and the convention that `run()` is the sole `CheckResult` constructor. Also adds a new Toolchain Pin section explaining the supply-chain pin policy. **Tier 2 (Refactor):** All 16 Rust source checks aligned to the convention. Net -132 lines by removing duplicated `CheckResult` construction (previously the `id` / `label` / `group` / `layer` fields were hardcoded in both `run()` and every `check_x()` helper). **Tier 3 (Enforce):** Integration test walks `src/checks/source/` with `std::fs` (no shell dependencies), finds every `fn check_` signature (any visibility, single-line or multi-line), and fails if any return `CheckResult` instead of `CheckStatus`. Uses `CARGO_MANIFEST_DIR` for a stable absolute path. **Toolchain pin format:** `channel = "1.94.1" # rustc e408947bfd200af42db322daf0fadfe7e26d3bd1, released 2026-03-25`. Comment mirrors the GitHub Actions SHA-pin pattern (`action@<sha> # vN.N.N`). Policy: bump only via reviewed PR after the new stable has aged ≥7 days (supply-chain quarantine consistent with `UV_EXCLUDE_NEWER` / bun `minimumReleaseAge` / `npm_config_min_release_age` in dotfiles). ## Benefits - Eliminates ID string literal triplication (3 copies per check → 1 authoritative source in `fn id()`) - New checks can't drift — the convention is documented AND enforced by CI - -132 lines of boilerplate removed - Local and CI toolchains are guaranteed identical — no more "local clippy older than CI clippy" false greens - Toolchain updates route through reviewed PRs with a 7-day quarantine, matching the broader brettdavies supply-chain posture ## Breaking Changes - [x] No breaking changes ## Deployment Notes - [x] No special deployment steps required ## Checklist - [x] Code follows project conventions and style guidelines - [x] Commit messages follow Conventional Commits - [x] Self-review of code completed - [x] Tests added/updated and passing - [x] No new warnings or errors introduced - [x] Changes are backward compatible
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
Implements Plan 004: 3 starter Python source checks proving the ast-grep + tree-sitter-python pipeline works end-to-end,
un-ignores 3 fixture integration tests, validates against 4 real-world CLIs (bird, xurl-rs, ripgrep, markitdown), then
resolves 20 review findings (P1/P2 correctness + testing gaps) and adds a Python integration fixture.
Changelog
Added
code-bare-exceptPython source check — detects bareexcept:clauses without exception typesp4-sys-exitPython source check — detectssys.exit()calls outsideif __name__ == "__main__":guards and__main__.pyfilesp6-no-colorPython source check — detects NO_COLOR env var handling (Warn, not Fail — behavioral check is theprimary gate)
has_pattern_in(),find_pattern_matches_in(), andhas_string_literal_in()supporting Python and Rust
Fixed
sys.exit()in__main__.py(Python entry point) no longer flaggedis_main_guard: now handles inline comments, parenthesized guards, no-space operators, and reversed operandorder (e.g.
if "__main__" == __name__:)is_bare_except: restrict parsing to first line of node text (prevents false negatives on error-recovery nodes)__main__.pyskip to check filename component, not path suffix (prevents false skips on files likemy__main__.py)parsed_fileslazy initialization (replacedRefCellwithOnceLock)except*branch from bare-except detection (PEP 654 makes bareexcept*:a syntax error)Type of Change
feat: New feature (non-breaking change which adds functionality)fix: Bug fix (non-breaking change which fixes an issue)test: Adding or updating testsRelated Issues/Stories
docs/plans/2026-04-02-004-feat-python-checks-validation-coverage-plan.md~/.gstack/projects/brettdavies-agentnative/brett-main-design-20260327-214808.md(updated)ce-reviewrun on 2026-04-15 surfaced 20 findings across 7 reviewers (correctness, testing, maintainability,project-standards, agent-native, learnings-researcher, adversarial) — all addressable findings fixed
Testing
Test Summary:
-DwarningsFiles Modified
Modified:
src/source.rs— addedhas_pattern_in(),find_pattern_matches_in(),has_string_literal_in()with generic languagedispatch
src/project.rs—parsed_fileslazy init now usesOnceLock(fixes TOCTOU)src/checks/source/python/mod.rs— registered 3 Python checkssrc/checks/source/rust/no_color.rs— uses sharedhas_string_literal_in()src/checks/behavioral/mod.rs,src/checks/project/dry_run.rs— adoptOnceLockconstructortests/integration.rs— removed#[ignore]from 3 fixture tests; addedtest_broken_python_fixtureCreated:
src/checks/source/python/bare_except.rs— bare-except detection via AST walkingsrc/checks/source/python/sys_exit.rs— sys.exit() guard analysis with robust__main__guard parser and__main__.pyskipsrc/checks/source/python/no_color.rs— NO_COLOR env var pattern matching with string-literal fallbacktests/fixtures/broken-python/pyproject.toml,tests/fixtures/broken-python/src/app.py— integration fixtureKey Details
templates (guard scope analysis, bare-except detection)
sys.exit()check skips__main__.pyfiles (entry point) — mirrors Rust'smain.rsskip forprocess::exit()NO_COLORcheck returns Warn (not Fail) since many Python libraries handle it transparentlyis_main_guardnormalizes theif __name__ == "__main__":comparison rather than exact-matching a small set ofcanonical forms, handling real-world variants (inline comments, parens, whitespace, reversed ordering)
has_string_literal_in()lives insrc/source.rsalongside the other cross-language helpers — the previousper-language private copies diverged in behavior (single vs double quote handling)
check_x()functions in the Python checks returnCheckStatusdirectly;run()is the soleCheckResultconstructor using
self.id()/self.group()/self.layer()— eliminates ID string literal triplication (the matchingrefactor for the 16 existing Rust checks ships in follow-up PR refactor: enforce check_x → CheckStatus convention across all source checks #16)
Real-World Validation Results
__main__.pyfalse positive caught and fixedChecklist