Skip to content

Add df12 pylint checkers and ambrleaks snapshot scanner - #6

Merged
leynos merged 54 commits into
mainfrom
initial-lints
Jul 26, 2026
Merged

Add df12 pylint checkers and ambrleaks snapshot scanner#6
leynos merged 54 commits into
mainfrom
initial-lints

Conversation

@leynos

@leynos leynos commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Summary

This branch turns the generated package skeleton into the df12 pylint
plugin. It registers twelve messages encoding recurring review feedback —
prefer structural pattern matching over isinstance and constant
dispatch, require failure messages on asserts, remove trivial wrappers
and assignment re-exports, demand explanations on suppression pragmas,
move snapshot-worthy assertions into syrupy, and adopt modern syntax
(PEP 695 type statements; no from __future__ import annotations on
a 3.14+ baseline) — and ships ambrleaks,
a standalone console script that scans syrupy .ambr snapshot files for
unredacted hex strings, UUIDs, emails, phone numbers, URLs, and absolute
paths.

The checkers are verified at three tiers: example-based pytest suites,
Hypothesis property tests over whole input families, and end-to-end
tests through the pinned leynos/pylint-pypy-shim runner. The pure
dispatch kernels carry PEP 316 contracts model-checked by CrossHair
behind an opt-in make crosshair gate. A Hypothesis property found a
real detection gap during development (negative number literals parse as
UnaryOp, not Const); the fix is included with the shrunk
counterexample pinned as a regression test.

Messages

Symbol ID Catches
prefer-structural-pattern-matching R9101 isinstance dispatch chains and terminating guard runs
assert-missing-message C9102 asserts without a failure message
prefer-match-over-constant-chain R9103 if/elif chains comparing one subject with constants
trivial-attribute-wrapper R9104 bodies that only forward attribute access or a proxied call
reexport-by-assignment C9105 module-level aliases of imported names made by assignment
lint-suppression-without-explanation C9106 noqa/ruff/pylint pragmas with no recorded reason
typecheck-suppression-without-explanation C9107 type: ignore/pyright/ty/mypy pragmas with no reason
prefer-snapshot-assertion R9108 equality against large inline literals in tests
prefer-snapshot-substring R9109 repeated substring probes on one subject in a test
trivial-alias-wrapper R9110 bodies that only forward arguments to another function
prefer-type-statement R9111 module-level type aliases better declared with the PEP 695 type statement
redundant-future-annotations C9112 from __future__ import annotations on a 3.14+ baseline

Review walkthrough

Validation

  • make all (build, check-fmt, lint, typecheck, test, spelling): exit 0
  • make test: 163 passed, 1 skipped (opt-in CrossHair gate)
  • Pylint (via the PyPy shim): rated 10.00/10; interrogate: 100%
    docstring coverage; ty check: all checks passed
  • make crosshair: 1 passed — all four kernel postconditions confirmed over all paths
  • End-to-end: all twelve message symbols reported under
    pylint-pypy-shim; a clean module stays silent

Notes

  • Message IDs use base 91, within the 51–99 range pylint reserves for
    custom checkers.
  • The snapshot-phone ambrleaks rule ships disabled by default because
    national number formats collide with identifiers; enable it per
    project via ambrleaks.toml.
  • No roadmap task or issue exists for this branch; the scope came from
    review feedback on downstream projects.

References

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @leynos, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b86b47ba-a62b-4208-a84f-42eccbdb925a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

The package becomes a registered Pylint plugin with nine checkers and adds the ambrleaks snapshot scanner. It also introduces CrossHair and property-based verification, integration tests, runtime configuration, and expanded project documentation.

Changes

Lint plugin and snapshot scanning

Layer / File(s) Summary
Checker implementations and registration
df12_python_lints/*.py
Add shared AST helpers, nine Pylint checkers, diagnostic messages, and plugin registration through register.
Ambrleaks scanner and CLI
df12_python_lints/ambrleaks/*, pyproject.toml
Add snapshot rules, entropy filtering, .ambr discovery, configuration, allowlists, baselines, and CLI exit codes.
Checker, scanner, and integration validation
tests/test_*.py
Add unit, property, plugin-registration, scanner, CrossHair, and shim-based tests covering reports, exclusions, configuration, and clean modules.
Verification commands and repository configuration
Makefile, pyproject.toml, AGENTS.md, typos.toml, typos.local.toml
Add the opt-in CrossHair target, dependencies, contracts, timeout settings, repository guidance rewrapping, and typo-checking mappings.
Project and user documentation
README.md, docs/*.md
Document plugin installation, lint messages, ambrleaks operation, configuration, baselines, licensing, and contribution workflow.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant AmbrleaksCLI
  participant Config
  participant Scanner
  participant SnapshotFiles
  User->>AmbrleaksCLI: Run ambrleaks with paths and options
  AmbrleaksCLI->>Config: Load TOML rules and allowlists
  AmbrleaksCLI->>SnapshotFiles: Discover and read .ambr files
  SnapshotFiles-->>Scanner: Snapshot text
  Scanner-->>AmbrleaksCLI: Findings and fingerprints
  AmbrleaksCLI-->>User: Findings and exit status
Loading

Poem

Nine checkers wake and gleam,
Snapshots yield their hidden seam.
CrossHair traces paths unseen,
Docs make every rule routine.
Clean builds march, crisp and bright.

🚥 Pre-merge checks | ✅ 19 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Testing (Unit And Behavioural) ❓ Inconclusive placeholder2 Need repository evidence before deciding.
✅ Passed checks (19 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: new df12 pylint checkers plus the ambrleaks snapshot scanner.
Description check ✅ Passed The description directly covers the same plugin, scanner, tests, and validation work in the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Testing (Overall) ✅ Passed PASS: Example, property, and E2E tests cover real regressions (no-op checkers, wrong thresholds, lazy discovery, baseline cleanup, logging), not just call counts.
User-Facing Documentation ✅ Passed docs/users-guide.md documents every new user-facing checker and ambrleaks; README signposts the core flows without excess detail.
Developer Documentation ✅ Passed docs/developers-guide.md documents the plugin architecture, ambrleaks boundaries, observability, performance, and verification tiers; no roadmap/execplan files were present.
Module-Level Documentation ✅ Passed Every Python module has a top-level docstring; the AST scan found 36/36 modules documented, including shared helper and test modules.
Testing (Property / Proof) ✅ Passed Hypothesis properties cover the new invariants, and CrossHair confirms the _chains contracts over all paths.
Testing (Compile-Time / Ui) ✅ Passed Python-only changes; no Rust/TS compile-time path exists, and ambrleaks/UI output is covered by focused semantic tests plus opt-in CrossHair checks.
Unit Architecture ✅ Passed The scanner/config cores are pure, filesystem work sits in explicit read/write boundaries, and the CLI orchestrates side-effects at the edge with dedicated tests.
Domain Architecture ✅ Passed Keep the pure helpers in _chains.py and _expressions.py; confine filesystem and CLI concerns to ambrleaks/cli.py, config.py, and scanner.py with explicit boundaries.
Observability ✅ Passed PASS: ambrleaks logs config/scan/baseline boundaries, logs failures with logger.exception, and surfaces them under --verbose; no cross-process boundary needs metrics/tracing.
Security And Privacy ✅ Passed No secrets, unsafe deserialisation, injection, or overbroad permissions were introduced; the new scanner/config paths are local and the sample data is synthetic.
Performance And Resource Use ✅ Passed Approve it: the hot paths stay linear and streaming, and the few materialised collections are file-scoped and bounded.
Concurrency And State ✅ Passed State stays local or immutable; the only global logger has an explicit CLI boundary and test restore, and baseline writes use temp-file replace cleanup.
Architectural Complexity And Maintainability ✅ Passed PASS: Shared kernels and ambrleaks boundaries are reused immediately, docs state the reuse/maintenance story, and the import graph is acyclic.
Rust Compiler Lint Integrity ✅ Passed PASS: The repo has no Rust sources or Cargo manifests, and the PR only touches Python/tests, so the Rust lint-integrity rules are not implicated.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch initial-lints

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

@leynos
leynos marked this pull request as ready for review July 23, 2026 11:17
chatgpt-codex-connector[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@leynos

leynos commented Jul 23, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews resumed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
df12_python_lints/ambrleaks/rules.py (1)

89-98: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Still missing absolute POSIX paths outside the enumerated directory list.

Broaden the pattern past this fixed directory allowlist — /srv/data/key-style paths under an unlisted top-level directory still slip through undetected.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@df12_python_lints/ambrleaks/rules.py` around lines 89 - 98, Update the
pattern in the “snapshot-posix-path” Rule to detect absolute POSIX paths
regardless of their top-level directory, including paths such as /srv/data/key
when the directory is not explicitly listed. Preserve the existing path-boundary
and termination constraints while removing the fixed top-level directory
allowlist.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@df12_python_lints/_chains.py`:
- Around line 25-98: Complete the public numpydoc sections: in
df12_python_lints/_chains.py lines 25-98, add Parameters and Returns
documentation to repeated_subject, narrowing_prefix, and elif_chain_tests; in
df12_python_lints/constant_chain.py lines 141-149,
df12_python_lints/match_dispatch.py lines 101-109, and
df12_python_lints/suppressions.py lines 121-129, add Attributes sections
documenting each checker’s name and msgs fields.

In `@df12_python_lints/ambrleaks/cli.py`:
- Around line 268-286: Update the exception tuple in main to include
UnicodeDecodeError alongside the existing scan errors, so invalid UTF-8 input
follows the existing ambrleaks error message and exit-code-2 path.

In `@df12_python_lints/constant_chain.py`:
- Around line 56-81: In _is_constant_like, replace the isinstance(expr,
nodes.Name) check within the nodes.Attribute branch with a nested structural
match on expr. Preserve the existing behavior: return true for an uppercase
attribute name or a Name expression whose name begins with an uppercase
character, and false otherwise.

In `@tests/test_crosshair.py`:
- Around line 49-55: Update the CrossHair invocation in the test to include
--report_all, then assert that stdout contains “Confirmed over all paths” for
each kernel under verification. Retain the existing return-code and “no
checkable functions” checks, ensuring a quiet vacuous pass cannot satisfy the
test.

---

Duplicate comments:
In `@df12_python_lints/ambrleaks/rules.py`:
- Around line 89-98: Update the pattern in the “snapshot-posix-path” Rule to
detect absolute POSIX paths regardless of their top-level directory, including
paths such as /srv/data/key when the directory is not explicitly listed.
Preserve the existing path-boundary and termination constraints while removing
the fixed top-level directory allowlist.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f82b440f-8f80-46cd-bcb8-2976a9255e54

📥 Commits

Reviewing files that changed from the base of the PR and between f740cd7 and 2b84f09.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (37)
  • AGENTS.md
  • Makefile
  • README.md
  • df12_python_lints/__init__.py
  • df12_python_lints/_chains.py
  • df12_python_lints/_expressions.py
  • df12_python_lints/ambrleaks/__init__.py
  • df12_python_lints/ambrleaks/cli.py
  • df12_python_lints/ambrleaks/rules.py
  • df12_python_lints/ambrleaks/scanner.py
  • df12_python_lints/assert_messages.py
  • df12_python_lints/constant_chain.py
  • df12_python_lints/match_dispatch.py
  • df12_python_lints/pure.py
  • df12_python_lints/reexports.py
  • df12_python_lints/snapshot_asserts.py
  • df12_python_lints/suppressions.py
  • df12_python_lints/wrappers.py
  • docs/contents.md
  • docs/developers-guide.md
  • docs/users-guide.md
  • pyproject.toml
  • tests/test_ambrleaks.py
  • tests/test_assert_messages.py
  • tests/test_constant_chain.py
  • tests/test_crosshair.py
  • tests/test_e2e_shim.py
  • tests/test_match_dispatch.py
  • tests/test_plugin.py
  • tests/test_properties.py
  • tests/test_reexports.py
  • tests/test_snapshot_asserts.py
  • tests/test_stub.py
  • tests/test_suppressions.py
  • tests/test_wrappers.py
  • typos.local.toml
  • typos.toml
💤 Files with no reviewable changes (2)
  • tests/test_stub.py
  • df12_python_lints/pure.py

Comment thread df12_python_lints/_chains.py
Comment thread df12_python_lints/ambrleaks/cli.py
Comment thread df12_python_lints/constant_chain.py
Comment thread tests/test_crosshair.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/conftest.py`:
- Around line 1-16: Expand the module docstring in tests/conftest.py to explain
the shared fixtures’ purpose, utility, and usage as a test API. Update the
write_snapshot fixture docstring with NumPy-style Parameters and Returns
sections plus a meaningful usage example, while preserving its existing behavior
and type contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8a96078c-b4a7-4a30-9e0e-4c018c045b54

📥 Commits

Reviewing files that changed from the base of the PR and between 2b84f09 and adba0ff.

📒 Files selected for processing (6)
  • tests/conftest.py
  • tests/test_ambrleaks.py
  • tests/test_ambrleaks_cli.py
  • tests/test_constant_chain.py
  • tests/test_snapshot_asserts.py
  • tests/test_wrappers.py

Comment thread tests/conftest.py Outdated
Repository owner deleted a comment from coderabbitai Bot Jul 24, 2026
@leynos

leynos commented Jul 24, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Have the following failed checks now been resolved?

If further work is required, please provide an AI agent prompt for the remaining work to be done to address these failures.

Do not treat warnings as optional or aspirational. Where a change is out of scope for this PR, propose a GitHub issue unless one exists already. (Treat documentation and validation coverage as in scope).

❌ Failed checks (2 errors)

Check name Status Explanation Resolution
Module-Level Documentation ❌ Error tests/conftest.py still has only a one-line docstring; it omits fixture purpose, usage, and the requested API docs/example. Expand the module docstring to explain write_snapshot’s purpose, how tests use it, and add Parameters/Returns plus a usage example.
Unit Architecture ❌ Error scan_file() and load_config() hide filesystem I/O behind plain query APIs, and scan_file() depends on Path.cwd() rather than an injected base path. Inject the base directory/filesystem at the boundary, and surface read/parsing failures explicitly instead of relying on ambient cwd.

@coderabbitai

This comment was marked as resolved.

leynos added 18 commits July 25, 2026 15:57
Turn the package into a pylint plugin registering two checkers:

- `prefer-structural-pattern-matching` (R9101) flags `isinstance`
  dispatch on a single subject, both as `if`/`elif` chains and as
  consecutive terminating guard `if` statements, and recommends a
  `match` statement with class patterns.
- `assert-missing-message` (C9102) flags `assert` statements without a
  failure message, so a shrunk property-test counterexample names the
  violated invariant.

Both checkers came out of recurring review feedback on downstream
projects. Remove the generated `hello` scaffold and its stub test, and
add `pylint` as a runtime dependency of the plugin.
Add `prefer-match-over-constant-chain` (R9103), reporting `if`/`elif`
chains where every branch compares one subject with constants,
enumeration members, or literals — by equality, by membership in a
literal collection, or by an `or` combination of such comparisons.
The message recommends a `match` statement over an enumeration of the
accepted values.

Extract the shared `if`/`elif` chain traversal helpers from the
dispatch checker into `_chains.py` so both chain-oriented checkers use
one implementation.
Describe the three checkers, their message identifiers, and how to load
the plugin in the users' guide, and replace the template README blurb
with a summary of the plugin.
`make fmt` rewraps AGENTS.md and `make spelling` refreshes the
generated `typos.toml` from the shared en-GB-oxendict base.
Add four checkers rounding out the initial lint set:

- `trivial-attribute-wrapper` (R9104) reports functions whose body
  only returns an attribute of a parameter or calls through such an
  attribute with the function's own parameters forwarded unchanged.
  Decorated functions are exempt because decorators such as `property`
  make the forwarding deliberate.
- `reexport-by-assignment` (C9105) reports module-level names bound by
  assigning an imported name or an attribute of an imported module,
  recommending a real `from ... import ... as ...` binding instead.
- `lint-suppression-without-explanation` (C9106) and
  `typecheck-suppression-without-explanation` (C9107) are token-based
  checks requiring every suppression pragma (`noqa`, `ruff: noqa`,
  `pylint: disable`; `type: ignore`, `pyright: ignore`, `ty: ignore`,
  `mypy:`) to record a reason, either inline or as a standalone
  comment on the line above.

Extract the pure attribute-chain helper into `_expressions.py` for
shared use by the wrapper and re-export checkers.
Extend the users' guide and README with the four new checkers, their
message identifiers, and their exemptions.
Ship an `ambrleaks` console script (installable with
`uv tool install df12-python-lints`) that scans syrupy `.ambr`
snapshot files for values that should have been redacted with a
matcher before recording: long hex strings (entropy-gated), UUIDs,
email addresses, E.164 phone numbers (opt-in), URLs, and absolute
POSIX and Windows paths. Findings are attributed to their `# name:`
test block.

Pylint only feeds Python modules to checkers, so snapshot files need
a file-level tool; the rule model (strict regex, entropy floor,
allowlist) follows gitleaks. Because syrupy rewrites `.ambr` files
wholesale on `--snapshot-update`, inline suppression markers cannot
survive; suppression instead lives in an `ambrleaks.toml` allowlist
(values, test-name globs, path globs, per-rule enablement) and an
optional JSON baseline whose fingerprints exclude line numbers so
they survive snapshot regeneration.
Add `prefer-snapshot-assertion` (R9108) and
`prefer-snapshot-substring` (R9109), reporting assertions in
`test_`-named functions that would carry their contract more clearly
as a syrupy snapshot:

- equality against a large inline literal — a collection with eight
  or more constant/name leaves (counted on the AST so formatting is
  irrelevant) or a multiline/long string, including
  `textwrap.dedent` wrappers; and
- three or more substring probes (`assert "..." in subject`) against
  one subject in a single test.

Comparisons with names such as `expected` fixtures, small literals,
and asserts outside test functions are never reported.
Replace the checker listing with the standard df12 README structure:
tagline, value proposition, quick start with a runnable example and
the reported message, feature list, documentation signposts, licence,
and contributing sections.
Add `trivial-alias-wrapper` (R9110), reporting functions whose body
only calls another function with the wrapper's own parameters
forwarded unchanged, such as `def foo(qux): return bar(qux)`.

The message is separate from `trivial-attribute-wrapper` (R9104) so
the two can be enabled independently: bare-name forwarding is more
contestable than attribute-chain forwarding. To keep false positives
out, the target must resolve to a module-level function or an import —
calling through a parameter is higher-order code, and wrapping a
class constructor or a builtin is a factory with a deliberate name,
so neither fires. Decorated functions remain exempt.
Move the dispatch-selection logic out of the match-dispatch checker
into two pure functions in `_chains.py` — `repeated_subject` and
`narrowing_prefix` — annotated with builtin-only types and PEP 316
contracts. The narrowing logic was the subtlest part of the guard-run
detection, so it now carries machine-checkable postconditions.

Add `make crosshair`, an opt-in gate (RUN_CROSSHAIR=1) that runs
`crosshair check --analysis_kind=PEP316` over the kernels via a
pytest wrapper, per the verification tiering: symbolic search runs on
kernel changes, not on every push. The wrapper also fails when
CrossHair reports "no checkable functions", so a vacuous pass cannot
hide the contracts silently unresolving again (as happened when the
annotations referenced a TYPE_CHECKING-only import). Truthiness on
the tuple parameter became `len()` comparisons because symbolic
execution cannot coerce `__bool__` on symbolic sequences.
Treat a unary operation on a constant (`-1`, `+1`, `~1`) as
constant-like in `prefer-match-over-constant-chain`. Negative number
literals parse as `UnaryOp` nodes rather than `Const`, so chains such
as `value == 0` / `value == -1` were silently missed.

Found by a Hypothesis property over generated constant chains; the
shrunk counterexample is pinned as a named regression test.
Cover the input families the example suites only sample: constant
chains of any length always fire and any variable branch poisons
them; isinstance guard runs of any length report exactly once; the
snapshot literal threshold depends only on AST leaf count, not
nesting; generated suppression pragmas are classified uniformly with
and without prose; the pure chain kernels honour their contracts; and
Shannon entropy respects its bounds and repetition invariance.

Identifiers and prose are constructed (prefixed alphabets) rather
than filtered, avoiding the rejection-sampling trap.
Lint fixture modules through `leynos/pylint-pypy-shim` — the same
runner as the project's lint gate, at the same pinned ref, read from
the Makefile so the two cannot drift — with the plugin loaded from
PYTHONPATH. One fixture holds a violation per checker and must
produce all ten message symbols; a clean fixture must produce none.
This proves the checkers work under PyPy-backed pylint, not only
under the CPython test harness.
Address two review findings:

- Pass-through detection now compares the call's arguments with the
  wrapper's ordered parameter list. Reordering (`target(b, a)`),
  repeating (`target(a, a)`), or omitting a parameter adapts the
  call — that is behaviour, so such wrappers are no longer reported
  by `trivial-attribute-wrapper`. Membership checking alone treated
  them as trivial.
- `trivial-alias-wrapper` no longer accepts every import binding as a
  function. Imported names are resolved through astroid inference and
  only count when they infer to a function, so an imported class
  constructor (`from collections import OrderedDict`) is exempt like
  a locally defined one, matching the documented contract.

Each false positive is pinned as a regression test, alongside checks
that true positives (in-order proxies, imported functions such as
`json.dumps`) still fire.
The users' guide now names the astroid AST library, which the
en-GB-oxendict gate corrected to "asteroid". Add it to the accepted
words in `typos.local.toml` and regenerate `typos.toml`.
Fingerprints deliberately exclude line numbers, so repeated values in
one snapshot block share a fingerprint. The baseline previously
deduplicated on write and suppressed by set membership on load, so a
newly introduced duplicate of a baselined value passed silently.

Write one baseline entry per occurrence (sorted, so ordering stays
deterministic) and load the baseline as occurrence counts with
`collections.Counter`: each recorded fingerprint suppresses exactly
one occurrence, in the scan's existing deterministic order, and any
surplus occurrence is reported and fails the run. Existing JSON
baselines (a list of fingerprint strings) remain compatible — prior
deduplicated baselines now simply accept one occurrence per entry.
Replace the raw astroid isinstance checks in `_chains.py`
(`elif_chain_tests`, `is_elif_branch`) and `match_dispatch.py`
(`_isinstance_subjects`, `_guard_subjects`) with match/case patterns
in the house style the plugin itself recommends, preserving behaviour
exactly. Remove the now-unused `_TERMINAL_STATEMENTS` tuple. The
contract-carrying pure kernels are untouched, keeping the CrossHair
model checks valid.
@leynos

leynos commented Jul 26, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews resumed.

@coderabbitai

This comment was marked as resolved.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/users-guide.md (1)

126-128: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use from module import name as alias here, or drop the parenthetical. import module as alias binds the module object, not the exported name shown by join = os.path.join, so it changes the public API.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/users-guide.md` around lines 126 - 128, Update the documentation
guidance to recommend only `from module import name as alias` for exposing an
imported name; remove the parenthetical suggesting `import module as alias`,
since that binds the module object and changes the documented API.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@df12_python_lints/ambrleaks/cli.py`:
- Around line 58-70: Update the public discover() docstring to follow NumPy
style by adding a Parameters section documenting paths and a Yields section
documenting the yielded pathlib.Path values, including that directory inputs
produce .ambr files and file inputs yield themselves.
- Around line 138-149: Replace the multi-line docstring on the private helper
_collect_findings with a concise single-line summary. Apply the same single-line
summary policy to the other referenced private helper as well, preserving only
its essential purpose and omitting extended implementation details.
- Line 73: Update the scan generator around the rglob traversal to avoid sorted
unbounded materialization: traverse directories in deterministic order while
yielding paths incrementally, so next() does not require consuming the full
tree. Refactor baseline filtering and writing to process findings incrementally
without sorting all fingerprints or converting findings to a list, while
preserving ordering and bounded state; add regression coverage for early yield
and incremental baseline-path handling.

In `@docs/developers-guide.md`:
- Around line 78-81: Update the sentence in the documentation around discover
and _collect_findings by adding a comma before “and” in “before the next file is
read, and the full finding set is never held in memory.”
- Around line 82-87: Revise the baseline-mode documentation to state explicitly
that --baseline materializes findings for apply_baseline and --write-baseline
sorts the complete fingerprint collection, so memory scales with input size.
Remove the claim that the snapshot tree is a fixed, bounded fixture set, and
either document this materialization as intentional or update the implementation
to stream both operations and add coverage.

---

Outside diff comments:
In `@docs/users-guide.md`:
- Around line 126-128: Update the documentation guidance to recommend only `from
module import name as alias` for exposing an imported name; remove the
parenthetical suggesting `import module as alias`, since that binds the module
object and changes the documented API.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2ec11472-2c99-4c23-a2d0-3f773970b04a

📥 Commits

Reviewing files that changed from the base of the PR and between f8b4183 and f2837cd.

📒 Files selected for processing (4)
  • df12_python_lints/ambrleaks/cli.py
  • docs/developers-guide.md
  • docs/users-guide.md
  • tests/test_ambrleaks_cli.py

Comment thread df12_python_lints/ambrleaks/cli.py
Comment thread df12_python_lints/ambrleaks/cli.py Outdated
Comment thread df12_python_lints/ambrleaks/cli.py Outdated
Comment thread docs/developers-guide.md Outdated
Comment thread docs/developers-guide.md Outdated
Resolve the Performance And Resource Use warning by removing every
whole-result materialisation from the scan path.

- discover(): yield straight from path.rglob("*.ambr") — drop the
  sorted() that materialised each directory's listing. Paths now arrive
  in filesystem order (documented); direct-file and recursive discovery
  are unchanged.
- apply_baseline(): refactor from list->list into a lazy generator over
  Iterable[Finding]. It copies the accepted mapping into a local Counter
  (caller state untouched), consumes one occurrence per matching
  fingerprint, and yields each survivor immediately.
- write_baseline(): new streaming filesystem boundary. It appends one
  JSON-encoded fingerprint per finding as it arrives (`[`, comma-joined
  entries, `]\n`), building the array in a same-directory temporary file
  that is atomically replaced, so a write failure never leaves a partial
  baseline and errors still reach main() as exit 2. Empty scans write
  `[]`. read_baseline stays compatible with the JSON-array format.
- _run(): the --baseline branch streams _collect_findings through
  apply_baseline into _print_findings, tallying scanned findings with a
  small pass-through counter so scanned/suppressed logs need no list(),
  len() over a materialised set, or second scan. The --write-baseline
  branch delegates to write_baseline. No list(findings)/sorted() over the
  full scan remains. Occurrence semantics, exit statuses, masking,
  --show-values, and allowlist filtering are unchanged.

scan_file() stays a per-file list API — _collect_findings still finishes
one discovered file before the next, so scanner memory stays bounded per
file and no scanner change was needed.

Tests: prove discover() yields its first path after pulling exactly one
item from a spied walk (not after exhausting/sorting it); prove baseline
suppression yields the first survivor before reading the rest; prove
write_baseline consumes a single-use iterator into a valid JSON array
(one entry per occurrence) and writes an empty array for an empty scan.
Existing multiplicity, masking, UTF-8, JSON-error, and base_dir coverage
retained. Users' and developers' guides updated for streaming and
discovery-order baselines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
codescene-access[bot]

This comment was marked as outdated.

- discover(): add NumPy Parameters and Yields sections documenting the
  paths argument and the yielded pathlib.Path values (directory inputs
  produce .ambr files; file inputs yield themselves).
- _collect_findings(): condense the multi-line docstring to a single-line
  summary, in line with the other single-line private helpers.
- users-guide: drop the "(or import module as alias)" alternative from the
  reexport-by-assignment guidance — that binds the module object, not the
  name, so it does not expose the re-exported name.

Documentation-only change; no executable code touched.

The related streaming/performance and test-coverage findings were already
addressed in an earlier commit (discover no longer sorts; apply_baseline
is a lazy generator; write_baseline streams incrementally; regression
tests prove early yield and incremental baseline handling), and the two
developers-guide punctuation/materialisation findings targeted prose that
was already rewritten, so no change was needed there.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
codescene-access[bot]

This comment was marked as outdated.

@leynos

leynos commented Jul 26, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Have the following failed checks now been resolved?

If further work is required, please provide an AI agent prompt for the remaining work to be done to address these failures.

Do not treat warnings as optional or aspirational. Where a change is out of scope for this PR, propose a GitHub issue unless one exists already. (Treat documentation and validation coverage as in scope).

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Testing (Overall) ❌ Error test_discovery_and_collection_stream_lazily only checks iterator types, so it still passes when discover()/baseline code fully materialise results first. Add a regression that proves next() yields before exhausting the tree (e.g. a sentinel source that raises on over-consumption) and test incremental baseline handling, not just output shape.
Performance And Resource Use ⚠️ Warning discover() still sorts every matched path, and --write-baseline/--baseline materialise all findings or fingerprints before continuing. Stream path discovery without sorting the whole tree, and batch baseline write/filtering so they do not require holding every finding in memory.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '=== ambrleaks CLI outline ==='
ast-grep outline df12_python_lints/ambrleaks/cli.py --items all

printf '%s\n' '=== streaming and baseline implementation ==='
rg -n -A190 -B20 \
  '^def (discover|_collect_findings|apply_baseline|write_baseline|read_baseline|_run|_print_findings)\b|--write-baseline|--baseline' \
  df12_python_lints/ambrleaks/cli.py

printf '%s\n' '=== full-result materialization / traversal locations ==='
rg -n -C3 --glob '*.py' \
  '\bsorted\s*\(|\blist\s*\(\s*findings\b|\blist\s*\(|\.rglob\s*\(|json\.(dump|dumps)\s*\(' \
  df12_python_lints/ambrleaks tests/test_ambrleaks*.py

printf '%s\n' '=== focused test outlines ==='
ast-grep outline tests/test_ambrleaks_cli.py --items all
ast-grep outline tests/test_ambrleaks_core.py --items all
ast-grep outline tests/test_ambrleaks.py --items all

printf '%s\n' '=== streaming and baseline tests ==='
rg -n -A80 -B10 --glob 'test_ambrleaks*.py' \
  '(stream|lazy|discover|_collect_findings|baseline|write_baseline|apply_baseline)' \
  tests

printf '%s\n' '=== quality gates ==='
rg -n -C3 '^(all|check-fmt|lint|typecheck|test):|^\.PHONY' Makefile

Length of output: 70602


@leynos Both findings are resolved in the current codegraph. No further implementation work, GitHub issue, or AI coding-agent prompt is required.

Check Status Current evidence
Testing (Overall) Resolved tests/test_ambrleaks_cli.py::TestDiscoveryAndPaths.test_discover_yields_before_walking_the_whole_tree() replaces the insufficient iterator-type-only assurance with a consumption regression: it patches Path.rglob() to yield 1,000 paths, calls next(discover([tmp_path])), and asserts exactly one path was pulled. A sorting or eager-materialization implementation would exhaust the synthetic traversal and fail. tests/test_ambrleaks_core.py::TestApplyBaseline.test_yields_surplus_before_consuming_the_rest() also proves a baseline survivor is yielded before subsequent input is consumed.
Performance And Resource Use Resolved df12_python_lints/ambrleaks/cli.py::discover() now directly yield from path.rglob("*.ambr"), with no sorted() or full path collection. apply_baseline() is lazy and occurrence-aware. _run() streams findings into baseline suppression and output without list(findings). write_baseline() emits a JSON array incrementally into a same-directory temporary file, then atomically replaces the baseline; it neither materializes nor sorts fingerprints.

The baseline behaviour remains intact: duplicate fingerprints are stored once per occurrence, suppression consumes only the recorded occurrence quota, and surplus duplicates are reported. TestWriteBaseline also covers iterator input, duplicate occurrence preservation, and the empty-stream JSON-array case.

I validated this from the current source and test graph. I did not execute repository test commands because the available inspection sandbox is restricted from executing repository files.

Replace the multi-paragraph docstring on the private helper
_print_findings with a concise single-line summary; behaviour and type
signature are unchanged, and no other code is touched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
codescene-access[bot]

This comment was marked as outdated.

@leynos

leynos commented Jul 26, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews resumed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@df12_python_lints/ambrleaks/cli.py`:
- Around line 271-283: Shorten the docstring of the private function
_count_stream to a single-line summary describing that it yields findings while
incrementing total[0]. Remove the extended narrative paragraph and preserve the
function’s implementation unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9b0b3ac7-9eaf-4632-b09f-fbe40f08cdd5

📥 Commits

Reviewing files that changed from the base of the PR and between f8b4183 and a2fd0e4.

📒 Files selected for processing (5)
  • df12_python_lints/ambrleaks/cli.py
  • docs/developers-guide.md
  • docs/users-guide.md
  • tests/test_ambrleaks_cli.py
  • tests/test_ambrleaks_core.py

Comment thread df12_python_lints/ambrleaks/cli.py
The streaming write_baseline added an error-cleanup branch (remove the
temporary file and re-raise on a mid-write failure) that no test
exercised, dropping coverage past the tolerance. Add a regression that
feeds a source raising partway through the stream and asserts the write
propagates the error while leaving neither a baseline nor a temporary
file behind.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
codescene-access[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_ambrleaks_core.py`:
- Around line 288-291: Update the _source function docstring to include a clear
usage-and-outcome example showing that it yields one Finding and then raises
_WriteError, while preserving the existing behavior and summary.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9326b205-0f94-4320-8105-c2eeee4b3966

📥 Commits

Reviewing files that changed from the base of the PR and between a2fd0e4 and 1724839.

📒 Files selected for processing (1)
  • tests/test_ambrleaks_core.py

Comment thread tests/test_ambrleaks_core.py
@leynos

leynos commented Jul 26, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews resumed.

- cli.py: shorten the private helper _count_stream's docstring to a
  single-line summary (yields findings while incrementing total[0]),
  dropping the narrative paragraph; implementation unchanged.
- test_ambrleaks_core.py: add a usage-and-outcome Examples section to the
  write-failure test's _source generator, showing it yields one Finding
  then raises _WriteError; summary and behaviour unchanged.

Documentation-only change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_ambrleaks_core.py`:
- Around line 288-295: Shorten the private nested helper `_source` docstring to
a single-line summary describing that it yields one finding before failing;
remove the multi-line explanation and Examples section that duplicates the test
flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7042fd76-ecdc-4f32-acab-aa958e51a955

📥 Commits

Reviewing files that changed from the base of the PR and between a2fd0e4 and 737d47d.

📒 Files selected for processing (2)
  • df12_python_lints/ambrleaks/cli.py
  • tests/test_ambrleaks_core.py

Comment thread tests/test_ambrleaks_core.py
@leynos
leynos merged commit 755b26f into main Jul 26, 2026
5 checks passed
@buzzybee-df12

Copy link
Copy Markdown

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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