pytest-stogger 2026.5.12__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. pytest_stogger-2026.5.12/.agents/impl_specs/project-wide-rules.md +194 -0
  2. pytest_stogger-2026.5.12/.agents/reports/quality-audit.md +322 -0
  3. pytest_stogger-2026.5.12/.gitignore +14 -0
  4. pytest_stogger-2026.5.12/.python-version +1 -0
  5. pytest_stogger-2026.5.12/PKG-INFO +9 -0
  6. pytest_stogger-2026.5.12/README.md +62 -0
  7. pytest_stogger-2026.5.12/docs/conf.py +85 -0
  8. pytest_stogger-2026.5.12/docs/config.md +79 -0
  9. pytest_stogger-2026.5.12/docs/dev/adr/project-wide-rules.md +39 -0
  10. pytest_stogger-2026.5.12/docs/fixtures.md +62 -0
  11. pytest_stogger-2026.5.12/docs/index.md +13 -0
  12. pytest_stogger-2026.5.12/docs/rules.md +50 -0
  13. pytest_stogger-2026.5.12/pyproject.toml +109 -0
  14. pytest_stogger-2026.5.12/src/pytest_stogger/__init__.py +0 -0
  15. pytest_stogger-2026.5.12/src/pytest_stogger/exemptions.py +48 -0
  16. pytest_stogger-2026.5.12/src/pytest_stogger/files.py +56 -0
  17. pytest_stogger-2026.5.12/src/pytest_stogger/matchers.py +205 -0
  18. pytest_stogger-2026.5.12/src/pytest_stogger/plugin.py +616 -0
  19. pytest_stogger-2026.5.12/src/pytest_stogger/report.py +66 -0
  20. pytest_stogger-2026.5.12/src/pytest_stogger/rules.py +1081 -0
  21. pytest_stogger-2026.5.12/tests/__init__.py +0 -0
  22. pytest_stogger-2026.5.12/tests/conftest.py +1 -0
  23. pytest_stogger-2026.5.12/tests/impl_spec/__init__.py +0 -0
  24. pytest_stogger-2026.5.12/tests/impl_spec/test_project_wide_rules.py +236 -0
  25. pytest_stogger-2026.5.12/tests/impl_spec/test_pytest_stogger_new_checks.py +701 -0
  26. pytest_stogger-2026.5.12/tests/impl_spec/test_quality_investments.py +258 -0
  27. pytest_stogger-2026.5.12/tests/impl_spec/test_stogger_rules_fixes.py +499 -0
  28. pytest_stogger-2026.5.12/tests/impl_spec/test_suppression_budget.py +121 -0
  29. pytest_stogger-2026.5.12/tests/test_architecture.py +63 -0
  30. pytest_stogger-2026.5.12/tests/test_coverage_gaps.py +819 -0
  31. pytest_stogger-2026.5.12/tests/test_entry_points.py +303 -0
  32. pytest_stogger-2026.5.12/tests/test_rule_detection.py +210 -0
  33. pytest_stogger-2026.5.12/uv.lock +1000 -0
@@ -0,0 +1,194 @@
1
+ ---
2
+ lifecycle:
3
+ requirements:
4
+ completed_at: "2026-05-07T00:00:00"
5
+ git_rev: dc19d32
6
+ design:
7
+ completed_at: "2026-05-07T00:00:00"
8
+ git_rev: dc19d32
9
+ plan:
10
+ completed_at: "2026-05-07T00:00:00"
11
+ git_rev: 08a35a6
12
+ workflow:
13
+ completed_at: "2026-05-07T00:00:00"
14
+ git_rev: 75a2a59
15
+ verify:
16
+ ---
17
+
18
+ ## Context
19
+
20
+ pytest-stogger enforces per-file AST rules (18 rules) and two cross-file meta-rules (logging-coverage, log-suppression-budget). FCIO projects exhibit project-wide anti-patterns — module-level `structlog.get_logger()` calls without `init_early_logging()`, and CLI entry points that import structlog without calling `init_logging()` — that require cross-file detection beyond per-file analysis.
21
+
22
+ ## Decisions
23
+
24
+ ### scope
25
+
26
+ #### Context
27
+
28
+ Three anti-patterns were identified: (1) no-early-init, (2) missing-init-in-entry, (3) init-logging-before-early.
29
+
30
+ #### Decision
31
+
32
+ Two rules: `no-early-init` and `missing-init-in-entry`. Pattern (3) is subsumed by (1) — if `init_early_logging()` is absent, `init_logging()` ordering is irrelevant.
33
+
34
+ #### Alternatives
35
+
36
+ a. All three rules — (3) adds no detection value beyond (1).
37
+ b. Only `no-early-init` — `missing-init-in-entry` catches a distinct failure mode.
38
+
39
+ #### Consequences
40
+
41
+ Two cross-file rules, each requiring all source files as input.
42
+
43
+ ### rule-behavior
44
+
45
+ #### Context
46
+
47
+ All existing rules use error severity and are enabled by default. Consistency reduces cognitive overhead for users.
48
+
49
+ #### Decision
50
+
51
+ Both rules: error severity (test fails), enabled by default, individually disableable via `disable_rules`.
52
+
53
+ #### Alternatives
54
+
55
+ a. Warning severity for `missing-init-in-entry` due to heuristic detection — rejected. False positives suppressible via `disable_rules`.
56
+
57
+ #### Consequences
58
+
59
+ Immediate enforcement on upgrade. Projects opt out explicitly if violations exist.
60
+
61
+ ### item-strategy
62
+
63
+ #### Context
64
+
65
+ Cross-file rules (logging-coverage, log-suppression-budget) each have a dedicated `pytest.Item` subclass with constructor accepting all source files. Per-file rules use `StoggerItem` with single-file context.
66
+
67
+ #### Decision
68
+
69
+ Two separate `pytest.Item` subclasses: `_NoEarlyInitItem` and `_MissingInitInEntryItem`. Each calls its rule function with all source files. Collected in `pytest_collection_modifyitems` alongside existing cross-file items. Node IDs: `stogger::no-early-init` and `stogger::missing-init-in-entry`.
70
+
71
+ #### Alternatives
72
+
73
+ a. Single shared `_ProjectWideItem` — mixes unrelated concerns, harder to disable individually.
74
+
75
+ #### Consequences
76
+
77
+ Consistent with existing cross-file pattern. Rule functions live in `rules.py`, imported by `plugin.py`. Each item independently disableable.
78
+
79
+ ### detection-scope
80
+
81
+ #### Context
82
+
83
+ Module-level logger initialization varies: `log = structlog.get_logger()`, `logger = structlog.get_logger()`, bare calls, or `get_logger()` after `from structlog import get_logger`.
84
+
85
+ #### Decision
86
+
87
+ `no-early-init` detects any `get_logger()` call at module level (not inside function/class body), regardless of assignment pattern. Covers `structlog.get_logger()` and bare `get_logger()` after import.
88
+
89
+ #### Alternatives
90
+
91
+ a. Only assignment pattern (`X = structlog.get_logger()`) — misses bare calls and non-standard variable names.
92
+
93
+ #### Consequences
94
+
95
+ Broader detection, fewer false negatives. Each module-level `get_logger()` call reported with file:line reference.
96
+
97
+ ### entry-point-pattern
98
+
99
+ #### Context
100
+
101
+ `missing-init-in-entry` needs to identify CLI entry points. Typer has `@app.callback()` and `@app.command()`. The callback is the init-level entry; commands register subcommands.
102
+
103
+ #### Decision
104
+
105
+ Two AST patterns identify entry points: (1) `if __name__ == "__main__":` blocks, (2) functions decorated with `@<any>.callback()` (any app variable name). `@app.command()` excluded — subcommand handlers are not entry points for init logic.
106
+
107
+ #### Alternatives
108
+
109
+ a. Include `@app.command()` — would cause false positives.
110
+
111
+ #### Consequences
112
+
113
+ Covers FCIO Typer app pattern. Rule checks these entry points for `init_logging()` calls.
114
+
115
+ ### violation-output
116
+
117
+ #### Context
118
+
119
+ Users need to navigate from pytest output to the offending code. Existing violation format: `{file}:{line} — {rule}: {description}`.
120
+
121
+ #### Decision
122
+
123
+ Per-file violation lines + project-level summary. Each file with a module-level `get_logger()` call gets its own violation line. Summary identifies root cause (e.g., `init_early_logging() not found in project`).
124
+
125
+ #### Alternatives
126
+
127
+ a. Single-line summary with file list — loses line-level detail for navigation.
128
+
129
+ #### Consequences
130
+
131
+ Consistent with existing format. Per-file lines enable direct editor navigation.
132
+
133
+ ### test-strategy
134
+
135
+ #### Context
136
+
137
+ Cross-file tests use direct rule function invocation (multi-file scenarios) and pytester integration (full plugin pipeline).
138
+
139
+ #### Decision
140
+
141
+ Spec validation tests in `tests/impl_spec/test_project_wide_rules.py` — direct rule function testing with multi-file scenarios. Pytester integration for collection and `disable_rules` config testing.
142
+
143
+ #### Alternatives
144
+
145
+ a. Only direct rule tests — misses collection and config integration.
146
+
147
+ #### Consequences
148
+
149
+ Multi-file test scenarios required: files with module-level `get_logger()` + files with/without `init_early_logging()`. Entry-point tests need Typer callback and `__main__` block fixtures.
150
+
151
+ ## Requirements
152
+
153
+ ### no-early-init detection
154
+
155
+ Scan all source files. Collect every module-level `get_logger()` call (file, line). Check if `init_early_logging()` call exists anywhere in the project. If module-level calls exist but `init_early_logging()` is absent: report violations.
156
+
157
+ ### missing-init-in-entry detection
158
+
159
+ Scan all source files. Identify entry points (`if __name__ == "__main__"` blocks, `@*.callback()` decorated functions). For each entry point, check if `init_logging()` is called within its scope. If entry points exist without `init_logging()`: report violations.
160
+
161
+ ### Integration
162
+
163
+ - Add rule names to `_KNOWN_RULE_NAMES` for `disable_rules` and `per-file-ignores` validation.
164
+ - Import new rule functions in `plugin.py`.
165
+ - Create items in `pytest_collection_modifyitems`, gated by `disable_rules` check.
166
+ - Rule functions must not import from `plugin`, `report`, `exemptions`, or `files` (architecture constraint).
167
+
168
+ ## Appendix
169
+
170
+ ```yaml
171
+ id: project-wide-rules
172
+ description: "Two cross-file AST rules: no-early-init (module-level get_logger without init_early_logging) and missing-init-in-entry (CLI entry points without init_logging)"
173
+ git_rev: 08a35a6
174
+ created_at: "2026-05-07T00:00:00"
175
+ specs:
176
+ - .agents/impl_specs/project-wide-rules.md
177
+ target_tests:
178
+ - file: tests/impl_spec/test_project_wide_rules.py
179
+ tests:
180
+ - TestNoEarlyInit::test_no_early_init_clean_when_init_exists
181
+ - TestNoEarlyInit::test_no_early_init_violation_when_no_init
182
+ - TestNoEarlyInit::test_no_early_init_clean_when_no_get_logger
183
+ - TestNoEarlyInit::test_no_early_init_detects_structlog_get_logger
184
+ - TestNoEarlyInit::test_no_early_init_detects_bare_get_logger_after_import
185
+ - TestNoEarlyInit::test_no_early_init_ignores_function_level
186
+ - TestNoEarlyInit::test_no_early_init_reports_file_and_line
187
+ - TestMissingInitInEntry::test_missing_init_in_entry_clean_when_init_called
188
+ - TestMissingInitInEntry::test_missing_init_in_entry_violation_when_no_init
189
+ - TestMissingInitInEntry::test_missing_init_in_entry_clean_when_no_entry_points
190
+ - TestMissingInitInEntry::test_missing_init_in_entry_detects_main_block
191
+ - TestMissingInitInEntry::test_missing_init_in_entry_detects_typer_callback
192
+ - TestMissingInitInEntry::test_missing_init_in_entry_ignores_typer_command
193
+ - TestMissingInitInEntry::test_missing_init_in_entry_reports_file_and_line
194
+ ```
@@ -0,0 +1,322 @@
1
+ # Quality Audit Report
2
+
3
+ ## Human Summary
4
+
5
+ Quality meta-audit of pytest-stogger (a pytest plugin for AST-based logging convention checking). The project has clean code with modern Python patterns, a single runtime dependency, and a sociable test suite (zero mocks). Tool suppression is conservative and justified. Three small fixes were applied: PERF102 (`.items()` → `.values()`), PLW1510 (explicit `check=False`), and ERA001 (removed redundant comment). Coverage sits at 68% with significant gaps in 3 of 13 rules (including the flagship `logging-coverage` cross-file rule). Five class-based test groups violate modern pytest conventions. Overall grade: C (78/100).
6
+
7
+ ## Completion Checklist
8
+
9
+ - [x] Entry point inventory + smoke test completed
10
+ - [x] Structural inventory completed (noqa, mock, complexity, test discovery, dependencies)
11
+ - [x] Quality gates collected (baseline + extreme)
12
+ - [x] All 4 investigation streams completed with structured review results
13
+ - [x] Tool tolerance audit produced with per-tool signals (ruff/ty/pytest)
14
+ - [x] Test collection integrity verified (all test files collected, no config hiding)
15
+ - [x] Skip/xfail/xpass audit completed (zero skips, zero xfails)
16
+ - [x] Test double strategy analyzed (mock:fake:golden:real per layer)
17
+ - [x] E2E coverage assessed for every entry point (PROVEN/SUSPECTED/UNKNOWN/BROKEN)
18
+ - [ ] Full CLI test not triggered — existing E2E evidence sufficient
19
+ - [x] Fixes applied for critical findings (PERF102, PLW1510, ERA001)
20
+ - [x] Fix loop completed (gates green after Round 1)
21
+ - [x] North Star generated from loaded skills
22
+ - [x] Course Corrections derived (Reality vs North Star diff)
23
+ - [ ] Git commit: pending
24
+
25
+ ## Entry Point Inventory
26
+
27
+ | Entry Point | Type | Source | Smoke | E2E Status | Evidence |
28
+ |-------------|------|--------|-------|------------|----------|
29
+ | Plugin registration (pytest11) | entry-point | pyproject.toml:17-18 | PASS | PROVEN | pytester integration tests |
30
+ | pytest_addoption | hook | plugin.py:222 | PASS | PROVEN | Indirect via pytester |
31
+ | pytest_configure | hook | plugin.py:235 | PASS | PROVEN | Per-file-ignores, unknown rule warning |
32
+ | pytest_collection_modifyitems | hook | plugin.py:241 | PASS | PROVEN | pytester verifies items created |
33
+ | --stogger-source | CLI flag | plugin.py:224-227 | PASS | SUSPECTED | Config tested, flag never passed |
34
+ | --stogger-exclude | CLI flag | plugin.py:228-232 | PASS | SUSPECTED | Config tested, flag never passed |
35
+ | StoggerItem | pytest.Item | plugin.py:291 | PASS | PROVEN | Indirect via pytester |
36
+ | _CoverageItem / logging-coverage | pytest.Item | plugin.py:329 | PASS | UNKNOWN | No test at all |
37
+ | stogger_config fixture | fixture | plugin.py:370 | PASS | SUSPECTED | Never requested in test |
38
+ | stogger_source fixture | fixture | plugin.py:376 | PASS | SUSPECTED | Never requested in test |
39
+ | stogger_exclude fixture | fixture | plugin.py:382 | PASS | SUSPECTED | Never requested in test |
40
+ | stogger_test_dir fixture | fixture | plugin.py:388 | PASS | SUSPECTED | Never requested in test |
41
+ | source_files fixture | fixture | plugin.py:396 | PASS | SUSPECTED | Never requested in test |
42
+ | test_files fixture | fixture | plugin.py:404 | PASS | SUSPECTED | Never requested in test |
43
+ | 10/12 file-level rules | rules | rules.py | PASS | PROVEN | Direct + spec + parametrized |
44
+ | log-info-layer-restriction | rule | rules.py:385 | PASS | UNKNOWN | No direct test |
45
+ | logging-coverage | rule | rules.py:533 | PASS | UNKNOWN | No test at all |
46
+ | format_violations | function | report.py | PASS | PROVEN | 8 tests across 2 files |
47
+ | parse_inline_ignores | function | exemptions.py | PASS | PROVEN | 5 tests |
48
+ | walk_python_files | function | files.py | PASS | PROVEN | 3 tests |
49
+ | is_method_call / find_method_calls | functions | matchers.py | PASS | PROVEN | 2+ indirect tests |
50
+
51
+ **Summary**: 8 PROVEN, 8 SUSPECTED, 3 UNKNOWN, 0 BROKEN
52
+
53
+ ## Tool Tolerance Audit
54
+
55
+ | Tool | Baseline | Extreme | Delta | Signal |
56
+ |------|----------|---------|-------|--------|
57
+ | ruff | 0 issues | **312 issues** | 312 suppressed | 🟢 green (justified) |
58
+ | ty | 0 errors | 0 errors | 0 | 🟢 green |
59
+ | pytest | 65 passed | 65 passed | 0 | 🟢 green |
60
+
61
+ ### ruff Suppression Breakdown (src/ only — 72 findings)
62
+
63
+ | Category | Count | Verdict |
64
+ |----------|-------|---------|
65
+ | DOC201 (missing Returns in docstring) | 31 | Legitimate — docstrings describe behavior |
66
+ | CPY001 (missing copyright) | 7 | Legitimate — no copyright headers |
67
+ | E501 (line too long) | 13 | Legitimate — 100 char limit vs default 88 |
68
+ | D1xx (missing docstrings) | 8 | Legitimate — pytest hooks, __init__ |
69
+ | SLF001 (accessing _nodeid) | 2 | Legitimate — required by pytest.Item |
70
+ | ANN401 (**kw: Any) | 2 | Legitimate — required by pytest.Item subclass |
71
+ | **PERF102 (use .values())** | **1** | **Fixed** — plugin.py:180 |
72
+ | S404 (subprocess import) | 1 | Legitimate — complexipy subprocess isolation |
73
+ | S603 (subprocess.run) | 1 | Legitimate — runs sys.executable |
74
+ | **PLW1510 (no check=)** | **1** | **Fixed** — rules.py:439 |
75
+ | C901 (too complex) | 1 | Legitimate — flagged by complexipy in tox |
76
+ | RUF052 (dummy var accessed) | 1 | Legitimate — _loc_re accessed by closure |
77
+ | Other minor | 5 | Legitimate/questionable |
78
+
79
+ **No critical hiding detected.** All suppressed categories are legitimate project decisions.
80
+
81
+ ### noqa/type:ignore density
82
+
83
+ - noqa comments: 0 (zero)
84
+ - type:ignore comments: 1 (rules.py:464 — intentional for json.loads return)
85
+ - Signal: 🟢 green
86
+
87
+ ## Test Collection Integrity
88
+
89
+ | Check | Result | Signal |
90
+ |-------|--------|--------|
91
+ | Tests on disk | 3 files | — |
92
+ | Tests collected | 57 nodes + 8 plugin items | — |
93
+ | Uncollected files | 0 | 🟢 green |
94
+ | Collection errors | 0 | 🟢 green |
95
+ | Config exclusions | None (no [tool.pytest.ini_options]) | 🟢 green |
96
+ | conftest hooks modifying collection | None | 🟢 green |
97
+
98
+ ## Skip/Xfail/Xpass Audit
99
+
100
+ | Category | Count | Signal |
101
+ |----------|-------|--------|
102
+ | @pytest.mark.skip | 0 | 🟢 green |
103
+ | @pytest.mark.skipif | 0 | 🟢 green |
104
+ | @pytest.mark.xfail | 0 | 🟢 green |
105
+ | XPASS | 0 | 🟢 green |
106
+ | Lazy skips | 0 | 🟢 green |
107
+ | Flaky-hidden | 0 | 🟢 green |
108
+
109
+ ## Test Double Strategy
110
+
111
+ | Layer | Mock | Spec'd Mock | Fake | Golden | Real | Total |
112
+ |-------|------|-------------|------|--------|------|-------|
113
+ | Unit | 0 | 0 | 0 | 0 | 57 | 57 |
114
+ | Integration (pytester) | 0 | 0 | 0 | 0 | 8 | 8 |
115
+
116
+ - Tautological tests (mock theater): 0
117
+ - Golden file smell: 0
118
+ - Mock density hotspots: None
119
+ - Overall double strategy verdict: **Sociable test suite — zero mocks, all real AST/file I/O**
120
+ - RED FLAGS: 0/10
121
+ - Signal: 🟢 green
122
+
123
+ ## Test Structure Summary
124
+
125
+ - Total tests: 65 (57 collected + 8 plugin-generated)
126
+ - Distribution: unit 57, integration 8, e2e 0
127
+ - RED FLAGS: 0/10
128
+ - Signal: 🟡 orange (class-based tests, coverage gaps)
129
+
130
+ ### Test Architecture Violation
131
+
132
+ 5 class-based test groups in `tests/impl_spec/test_stogger_rules_fixes.py`:
133
+ - TestBoundLogTracking
134
+ - TestInlineExemption
135
+ - TestConfigRestructure
136
+ - TestComplexityThreshold
137
+ - TestErrorCommunication
138
+
139
+ Forbidden by modern pytest conventions (python-audit: "Class-based tests → forbidden, use plain test_ functions").
140
+
141
+ ## Test Coverage
142
+
143
+ | Module | Coverage | Missing Lines | Signal |
144
+ |--------|----------|---------------|--------|
145
+ | __init__.py | 100% | — | 🟢 green |
146
+ | exemptions.py | 73% | 12-20, 37 | 🟡 orange |
147
+ | files.py | 74% | 3-13, 53-54 | 🟡 orange |
148
+ | matchers.py | 74% | 3-12, 43, 47 | 🟡 orange |
149
+ | plugin.py | 56% | 21-109, 112-121, 135-406 | 🔴 red |
150
+ | report.py | 79% | 3-8, 40, 57, 60 | 🟡 orange |
151
+ | rules.py | 74% | 21-38, 54-72, 84-93, 101-593 | 🟡 orange |
152
+
153
+ - Overall coverage: 68%
154
+ - Modules < 50%: none (but plugin.py at 56% is close)
155
+ - Entry points with 0% coverage: logging-coverage, log-info-layer-restriction
156
+ - Signal: 🟡 orange
157
+
158
+ ## Duration Anomalies
159
+
160
+ - Total suite time: 2.20s (wall time 4s)
161
+ - Duration stats: P50=<5ms, P90=~0.04s, P95=~0.08s, P99=0.34s
162
+
163
+ | Category | Count | Details |
164
+ |----------|-------|---------|
165
+ | EXTREME OUTLIER | 0 | — |
166
+ | FAKE SLOW | 0 | — |
167
+ | HIDDEN SLOW | 0 | — |
168
+ | Zero-duration | 0 | — |
169
+
170
+ - Signal: 🟢 green — no duration anomalies
171
+
172
+ ## Dependency Audit
173
+
174
+ | Category | Count | Signal |
175
+ |----------|-------|--------|
176
+ | Forbidden libraries | 0 | 🟢 green |
177
+ | Stdlib reinvention | 0 | 🟢 green |
178
+ | Unused dependencies | 0 | 🟢 green |
179
+ | Missing blessed libraries | 0 | 🟢 green |
180
+ | Available but unused | 0 | 🟢 green |
181
+
182
+ - Single runtime dependency: complexipy (used in rules.py)
183
+ - All stdlib modules used correctly: tomllib, pathlib, collections.Counter, dataclasses
184
+ - Signal: 🟢 green
185
+
186
+ ## E2E Coverage Assessment
187
+
188
+ - PROVEN: 8 entry points (plugin hooks, StoggerItem, 10 rules, format_violations, helpers)
189
+ - SUSPECTED: 8 entry points (2 CLI flags, 6 fixtures)
190
+ - UNKNOWN: 3 entry points (_CoverageItem, log-info-layer-restriction, logging-coverage)
191
+ - BROKEN: 0
192
+ - Full CLI test triggered: NO — existing evidence sufficient
193
+ - Signal: 🟡 orange
194
+
195
+ ## Stream Signals
196
+
197
+ | Stream | Signal | Summary |
198
+ |--------|--------|---------|
199
+ | A: Code Architecture | 🟡 orange | Clean layers but no enforcement, 5 complexity hotspots |
200
+ | B: Code Quality / Tool Tolerance | 🟢 green | Zero noqa, 1 type:ignore, no forbidden libs, justified suppression |
201
+ | C: Test Structure | 🟡 orange | Sociable (0 mocks) but 68% coverage, class-based tests, 3 rules untested |
202
+ | D: E2E + Production Reality | 🟡 orange | Plugin loads and works, but flagship rules untested |
203
+
204
+ ## Architectural North Star
205
+
206
+ | Dimension | True North | Source |
207
+ |-----------|------------|--------|
208
+ | Dependency Landscape | Minimal surface, blessed libs only | python-dev |
209
+ | Test Pyramid | 70/25/5 (unit/integration/e2e), sociable testing | python-tests |
210
+ | Type System | Type-first, PEP 604, PEP 695, no legacy | python-dev |
211
+ | Architecture | Layer boundaries enforced by pytest-archon | python-architecture |
212
+ | Code Patterns | dataclass(slots=True), pattern matching, no async | python-dev |
213
+ | Tool Config | ruff with extended rules, zero noqa | python-audit |
214
+
215
+ ## Course Corrections
216
+
217
+ ### NAV-1 Architecture Enforcement
218
+ - **Current heading:** Clean layers documented in AGENTS.md, no test_architecture.py
219
+ - **True north:** pytest-archon rules enforcing layer boundaries
220
+ - **Correction:** Introduce test_architecture.py with layer boundary rules
221
+
222
+ ### NAV-2 Test Architecture
223
+ - **Current heading:** 5 class-based test groups (CRITICAL per python-audit)
224
+ - **True north:** Plain test_ functions, classes only for shared fixtures
225
+ - **Correction:** Convert TestBoundLogTracking, TestInlineExemption, TestConfigRestructure, TestComplexityThreshold, TestErrorCommunication to plain functions
226
+
227
+ ### NAV-3 Coverage
228
+ - **Current heading:** 68% total, 3 of 13 rules untested including flagship logging-coverage
229
+ - **True north:** Critical entry points proven, 70%+ coverage
230
+ - **Correction:** Add tests for logging-coverage, log-info-layer-restriction, fixtures, CLI flags
231
+
232
+ ### NAV-4 Complexity
233
+ - **Current heading:** 5 functions exceed CC 15, highest at 37
234
+ - **True north:** CC ≤ 15 per function
235
+ - **Correction:** Refactor complex rule functions to extract helpers
236
+
237
+ ### NAV-5 Dependency Landscape
238
+ - **Current heading:** ON COURSE — single runtime dep, no forbidden libs
239
+ - **True north:** Minimal surface, blessed libs
240
+ - **Correction:** None needed
241
+
242
+ ### NAV-6 Type System
243
+ - **Current heading:** ON COURSE — modern hints, PEP 604, ty clean
244
+ - **True north:** Type-first development
245
+ - **Correction:** None needed
246
+
247
+ ### NAV-7 Tool Configuration
248
+ - **Current heading:** PERF102 and PLW1510 were suppressed — now fixed
249
+ - **True north:** Tool config catches actionable issues
250
+ - **Correction:** Consider enabling PERF and PLW rule categories
251
+
252
+ ### NAV-8 E2E Credibility
253
+ - **Current heading:** Smoke test import verification had stale API names
254
+ - **True north:** E2E tests verify real production behavior
255
+ - **Correction:** Smoke test was a meta-audit artifact, not project code — no project fix needed
256
+
257
+ ### NAV-9 Code Patterns
258
+ - **Current heading:** ON COURSE — dataclass(slots=True), modern patterns
259
+ - **True north:** Modern Python idioms
260
+ - **Correction:** None needed
261
+
262
+ - NAV-items total: 9
263
+ - Dimensions on course (no deviation): 4 (NAV-5, NAV-6, NAV-8, NAV-9)
264
+ - Dimensions off course: 5 (NAV-1, NAV-2, NAV-3, NAV-4, NAV-7)
265
+ - Signal: 🟡 orange
266
+
267
+ ## Test Automation
268
+
269
+ - Task runner: tox (configured in pyproject.toml)
270
+ - Single-command gate: YES — `uv run tox -p`
271
+ - Default coverage: full (no excluded markers)
272
+ - Signal: 🟢 green
273
+
274
+ ## Infrastructure Recommendations
275
+
276
+ - **Coverage pipeline**: Already configured — `tox -e cov` runs pytest with coverage
277
+ - **Duration regression**: Add `--durations=10` to tox test env to catch slow test regressions
278
+ - **Architecture enforcement**: Add `pytest-archon` with layer boundary rules
279
+
280
+ ## Critical Findings Fixed
281
+
282
+ | Finding | Fix | File |
283
+ |---------|-----|------|
284
+ | PERF102: pfi.items() → pfi.values() | Changed to `.values()` | src/pytest_stogger/plugin.py:180 |
285
+ | PLW1510: subprocess.run without check=False | Added `check=False` | src/pytest_stogger/rules.py:439 |
286
+ | ERA001: Redundant comment above typed data | Removed comment | tests/impl_spec/test_quality_investments.py:47 |
287
+
288
+ ## Full CLI Test Trace
289
+
290
+ Full CLI test not triggered — existing E2E evidence sufficient.
291
+
292
+ ## Code Volume
293
+
294
+ | File | Change |
295
+ |------|--------|
296
+ | src/pytest_stogger/plugin.py | +1/-1 |
297
+ | src/pytest_stogger/rules.py | +1/-0 |
298
+ | tests/impl_spec/test_quality_investments.py | +0/-1 |
299
+
300
+ Total: +2/-2 across 3 files. Proportional to findings.
301
+
302
+ ## Post-Fix Quality Gates
303
+
304
+ | Tool | Result |
305
+ |------|--------|
306
+ | tox | ✅ OK (py: OK) |
307
+ | ruff | ✅ 0 issues |
308
+ | ty | ✅ 0 errors |
309
+ | pytest | ✅ 65 passed |
310
+ | E2E smoke | ✅ PASS |
311
+
312
+ ## Recommendations
313
+
314
+ 1. **Medium priority**: Add tests for `logging-coverage` (flagship cross-file rule) and `log-info-layer-restriction`
315
+ 2. **Medium priority**: Convert 5 class-based test groups to plain test_ functions
316
+ 3. **Low priority**: Add test_architecture.py with pytest-archon layer boundary rules
317
+ 4. **Low priority**: Consider enabling PERF and PLW ruff rule categories
318
+ 5. **Low priority**: Refactor complex rule functions (CC 15+) to extract helpers
319
+
320
+ ## Raw Data Location
321
+
322
+ `.agents/tmp/quality/` — inventory/, baseline/, extreme/, analysis/, e2e/
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .tox/
8
+ .coverage
9
+ htmlcov/
10
+ .ruff_cache/
11
+
12
+ # Sphinx build output
13
+ docs/_build/
14
+ docs/autoapi/
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytest-stogger
3
+ Version: 2026.5.12
4
+ Summary: AST-based convention checking helpers for pytest.
5
+ Classifier: Framework :: Pytest
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Programming Language :: Python :: 3.13
8
+ Requires-Python: >=3.13
9
+ Requires-Dist: complexipy
@@ -0,0 +1,62 @@
1
+ # pytest-stogger
2
+
3
+ Automatic AST-based logging convention checking for pytest.
4
+
5
+ ## Installation
6
+
7
+ uv add --dev pytest-stogger
8
+
9
+ ## Quick Start
10
+
11
+ uv run pytest
12
+
13
+ The plugin creates one test item per source file plus a cross-file logging-coverage check. No test files to write.
14
+
15
+ Example output:
16
+
17
+ src/mypackage/__init__.py::stogger PASSED
18
+ src/mypackage/core.py::stogger PASSED
19
+ stogger::logging-coverage PASSED
20
+
21
+ ## Documentation
22
+
23
+ Full documentation is built with Sphinx:
24
+
25
+ tox -e docs
26
+
27
+ Open `docs/_build/html/index.html` after the build.
28
+
29
+ - **Built-in Rules** — all 13 rules with explanations
30
+ - **Configuration** — pyproject.toml options, CLI overrides, inline exemptions
31
+ - **Fixtures and Helpers** — session-scoped fixtures and utility functions for custom checks
32
+ - **API Reference** — auto-generated from source docstrings
33
+
34
+ ## Quality Gate
35
+
36
+ tox # runs fix → test → cov sequentially
37
+ tox -p # runs fix → test → cov in parallel where possible
38
+ tox -e fix # ruff fix + format, ty check, complexipy, vulture
39
+ tox -e test # pytest fast run
40
+ tox -e cov # pytest with coverage report
41
+
42
+ ## Testimonials
43
+
44
+ > We upgraded from a dev build to 2026.5.4. Previous version: 0 violations. After upgrade: 5 failures, 3 warnings. Turns out the old version simply didn't have the rules yet. Every single failure was legitimate. Total fix: 7 files, net -7 lines. The plugin literally made the codebase shorter while making it better.
45
+ >
46
+ > — nixflow maintainers, after discovering their "clean" codebase wasn't
47
+
48
+ > pytest-stogger nervt wie ein röchelnder Raucher über every damn event ID — und dann sitzt du um 3 Uhr Nacht vor einem Production-Incident und die Logs sind plötzlich lesbar. 5/5, würde mich wieder maßregeln lassen.
49
+ >
50
+ > — senior SRE, after their first on-call rotation with enforced conventions
51
+
52
+ > Rule 6 flagged three sequential `log.info()` calls repeating the same keys. My first reaction was "that's fine, it works." Then I refactored to `log.bind()` and deleted 12 lines. The plugin understood my code better than I did.
53
+ >
54
+ > — backend developer, reluctantly impressed
55
+
56
+ > We added pytest-stogger to a 40k-line codebase. 87 violations. Two hours later: 87 violations, zero remaining. The CI now enforces what code review couldn't. Our logging is structured, consistent, and actually useful in production. I'm annoyed it took us this long.
57
+ >
58
+ > — tech lead, after the third logging-related incident in one quarter
59
+
60
+ > The next diabolical idea from the pytest-stogger camp: now I'm getting errors on the meta level because I'm suppressing errors? Where does this end? Maybe I should just take logging seriously for once.
61
+ >
62
+ > — principal engineer, after discovering log-suppression-budget
@@ -0,0 +1,85 @@
1
+ """Sphinx configuration for pytest-stogger."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib.metadata import version as _get_version
6
+
7
+ # -- Project information -----------------------------------------------------
8
+
9
+ project = "pytest-stogger"
10
+ release = _get_version("pytest-stogger")
11
+ version = ".".join(release.split(".")[:2])
12
+ copyright = "2025, pytest-stogger contributors" # noqa: A001
13
+ author = "pytest-stogger contributors"
14
+
15
+ # -- Extensions --------------------------------------------------------------
16
+
17
+ extensions = [
18
+ "myst_parser",
19
+ "autoapi.extension",
20
+ "sphinx.ext.viewcode",
21
+ "sphinx_autodoc_typehints",
22
+ "sphinx_llm.txt",
23
+ "sphinx_copybutton",
24
+ ]
25
+
26
+ # Required: autoapi generates .rst files internally.
27
+ source_suffix = {".md": "markdown", ".rst": "restructuredtext"}
28
+
29
+ # -- autoapi -----------------------------------------------------------------
30
+
31
+ autoapi_type = "python"
32
+ autoapi_dirs = ["../src/pytest_stogger"]
33
+ autoapi_file_patterns = ["*.py"]
34
+ autoapi_generate_api_docs = True
35
+ autoapi_add_toctree_entry = True
36
+ autoapi_keep_files = True
37
+
38
+ autoapi_options = [
39
+ "members",
40
+ "undoc-members",
41
+ "show-inheritance",
42
+ "show-module-summary",
43
+ ]
44
+
45
+ autodoc_typehints = "description"
46
+
47
+ # Skip private members (underscore prefix) in autoapi output
48
+
49
+
50
+ def autoapi_skip_member(
51
+ _app, _what: str, name: str, _obj, skip: bool, _options, # noqa: FBT001
52
+ ) -> bool:
53
+ if name.startswith("_"):
54
+ return True
55
+ return skip
56
+
57
+
58
+ def setup(app): # noqa: D103
59
+ app.connect("autoapi-skip-member", autoapi_skip_member)
60
+
61
+
62
+ # -- MyST --------------------------------------------------------------------
63
+
64
+ myst_enable_extensions = [
65
+ "colon_fence",
66
+ "deflist",
67
+ "fieldlist",
68
+ ]
69
+ myst_heading_anchors = 3
70
+
71
+ # -- sphinx-llm --------------------------------------------------------------
72
+
73
+ llms_txt_build_parallel = True
74
+ llms_txt_full_build = True
75
+ llms_txt_description = "pytest-stogger — AST-based logging convention checking for pytest"
76
+
77
+ # -- sphinx-copybutton -------------------------------------------------------
78
+
79
+ copybutton_prompt_text = r">>> |\.\.\. |\$ |In \[\d*\]: | "
80
+ copybutton_prompt_is_regexp = True
81
+
82
+ # -- Theme -------------------------------------------------------------------
83
+
84
+ html_theme = "furo"
85
+ html_title = "pytest-stogger"