python-vibe-guard 0.9.0__tar.gz → 0.11.0__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 (49) hide show
  1. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/PKG-INFO +38 -2
  2. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/README.md +37 -1
  3. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyproject.toml +1 -1
  4. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/python_vibe_guard.egg-info/PKG-INFO +38 -2
  5. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/python_vibe_guard.egg-info/SOURCES.txt +4 -0
  6. python_vibe_guard-0.11.0/pyvibe/__init__.py +1 -0
  7. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/analyzer.py +9 -2
  8. python_vibe_guard-0.11.0/pyvibe/baseline.py +73 -0
  9. python_vibe_guard-0.11.0/pyvibe/cli.py +485 -0
  10. python_vibe_guard-0.11.0/pyvibe/diff.py +121 -0
  11. python_vibe_guard-0.11.0/tests/test_baseline.py +234 -0
  12. python_vibe_guard-0.11.0/tests/test_diff.py +304 -0
  13. python_vibe_guard-0.9.0/pyvibe/__init__.py +0 -1
  14. python_vibe_guard-0.9.0/pyvibe/cli.py +0 -198
  15. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/python_vibe_guard.egg-info/dependency_links.txt +0 -0
  16. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/python_vibe_guard.egg-info/entry_points.txt +0 -0
  17. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/python_vibe_guard.egg-info/top_level.txt +0 -0
  18. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/__main__.py +0 -0
  19. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/autofix.py +0 -0
  20. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/explain.py +0 -0
  21. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rule_docs.py +0 -0
  22. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/__init__.py +0 -0
  23. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/async_requests.py +0 -0
  24. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/async_sleep.py +0 -0
  25. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/asyncio_run.py +0 -0
  26. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/base.py +0 -0
  27. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/celery_time_limit.py +0 -0
  28. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/contextvar_cleanup.py +0 -0
  29. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/create_task_orphan.py +0 -0
  30. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/ensure_future_orphan.py +0 -0
  31. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/gather_no_return_exceptions.py +0 -0
  32. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/httpx_client_sync.py +0 -0
  33. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/httpx_sync.py +0 -0
  34. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/loop_run_until_complete.py +0 -0
  35. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/open_async.py +0 -0
  36. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/os_blocking.py +0 -0
  37. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/queue_put_nowait.py +0 -0
  38. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/retry_no_backoff.py +0 -0
  39. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/silent_except.py +0 -0
  40. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/sqlite_async.py +0 -0
  41. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/subprocess_async.py +0 -0
  42. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/threading_lock.py +0 -0
  43. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/rules/while_true_no_await.py +0 -0
  44. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/pyvibe/sarif.py +0 -0
  45. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/setup.cfg +0 -0
  46. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/tests/test_exclude.py +0 -0
  47. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/tests/test_explain.py +0 -0
  48. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/tests/test_rules.py +0 -0
  49. {python_vibe_guard-0.9.0 → python_vibe_guard-0.11.0}/tests/test_sarif.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-vibe-guard
3
- Version: 0.9.0
3
+ Version: 0.11.0
4
4
  Summary: Runtime anti-pattern scanner for async Python — detects what AI-generated code gets wrong
5
5
  License: MIT
6
6
  Keywords: async,linter,fastapi,asyncio,static-analysis
@@ -173,6 +173,10 @@ python -m pyvibe src/ --exclude tests
173
173
  # Show the research evidence behind a rule (accuracy, false positives, sources)
174
174
  python -m pyvibe explain PYVIBE-002
175
175
 
176
+ # Baseline mode: suppress pre-existing findings, only fail on new ones
177
+ python -m pyvibe baseline create src/ # snapshot current findings
178
+ python -m pyvibe src/ --baseline # only reports findings NOT in the baseline
179
+
176
180
  # Exit code: 0 = clean, 1 = violations found, 2 = path error
177
181
  ```
178
182
 
@@ -255,6 +259,38 @@ python -m pyvibe explain PYVIBE-002
255
259
  If a rule has no evidence file, it prints a clear `No evidence file found for PYVIBE-XXX`
256
260
  and exits non-zero — it never invents data.
257
261
 
262
+ ### Baseline mode
263
+
264
+ Adopting python-vibe-guard on an existing codebase usually means hundreds of pre-existing
265
+ findings you can't fix before CI needs to go green. Baseline mode snapshots the current
266
+ findings once, then only fails on genuinely **new** ones:
267
+
268
+ ```bash
269
+ # Snapshot every current finding into .pyvibe-baseline.json
270
+ python -m pyvibe baseline create src/
271
+
272
+ # Refuses to run if a baseline already exists — use `update` to overwrite it
273
+ python -m pyvibe baseline update src/
274
+
275
+ # Full scan, but only reports findings NOT already in the baseline
276
+ python -m pyvibe src/ --baseline
277
+ python -m pyvibe src/ --baseline --json
278
+ python -m pyvibe src/ --baseline --sarif
279
+
280
+ # `pyvibe scan` is an equivalent, explicit subcommand form of the same flags
281
+ python -m pyvibe scan src/ --baseline
282
+ ```
283
+
284
+ A finding is considered "already known" on an exact match of `(file path, rule ID, line
285
+ number)`. Anything that doesn't match — a new violation, or an existing one that moved to
286
+ a different line — is reported and fails the scan (exit code `1`); an unchanged baseline
287
+ produces exit code `0`.
288
+
289
+ `.pyvibe-baseline.json` is a plain JSON file — whether to commit it or add it to
290
+ `.gitignore` is up to your team. Commit it if you want the accepted-debt snapshot shared
291
+ and reviewed like any other file; gitignore it if each contributor/CI run should
292
+ regenerate its own baseline instead.
293
+
258
294
  ---
259
295
 
260
296
  ## CI/CD integration
@@ -352,7 +388,7 @@ python -m pytest tests/ -v
352
388
  python tests/test_rules.py
353
389
  ```
354
390
 
355
- 226 tests: true positives + false-positive guards for every rule, plus SARIF output and `pyvibe explain` coverage.
391
+ 268 tests: true positives + false-positive guards for every rule, plus SARIF output, `pyvibe explain`, `pyvibe review`, and baseline mode coverage.
356
392
 
357
393
  ---
358
394
 
@@ -158,6 +158,10 @@ python -m pyvibe src/ --exclude tests
158
158
  # Show the research evidence behind a rule (accuracy, false positives, sources)
159
159
  python -m pyvibe explain PYVIBE-002
160
160
 
161
+ # Baseline mode: suppress pre-existing findings, only fail on new ones
162
+ python -m pyvibe baseline create src/ # snapshot current findings
163
+ python -m pyvibe src/ --baseline # only reports findings NOT in the baseline
164
+
161
165
  # Exit code: 0 = clean, 1 = violations found, 2 = path error
162
166
  ```
163
167
 
@@ -240,6 +244,38 @@ python -m pyvibe explain PYVIBE-002
240
244
  If a rule has no evidence file, it prints a clear `No evidence file found for PYVIBE-XXX`
241
245
  and exits non-zero — it never invents data.
242
246
 
247
+ ### Baseline mode
248
+
249
+ Adopting python-vibe-guard on an existing codebase usually means hundreds of pre-existing
250
+ findings you can't fix before CI needs to go green. Baseline mode snapshots the current
251
+ findings once, then only fails on genuinely **new** ones:
252
+
253
+ ```bash
254
+ # Snapshot every current finding into .pyvibe-baseline.json
255
+ python -m pyvibe baseline create src/
256
+
257
+ # Refuses to run if a baseline already exists — use `update` to overwrite it
258
+ python -m pyvibe baseline update src/
259
+
260
+ # Full scan, but only reports findings NOT already in the baseline
261
+ python -m pyvibe src/ --baseline
262
+ python -m pyvibe src/ --baseline --json
263
+ python -m pyvibe src/ --baseline --sarif
264
+
265
+ # `pyvibe scan` is an equivalent, explicit subcommand form of the same flags
266
+ python -m pyvibe scan src/ --baseline
267
+ ```
268
+
269
+ A finding is considered "already known" on an exact match of `(file path, rule ID, line
270
+ number)`. Anything that doesn't match — a new violation, or an existing one that moved to
271
+ a different line — is reported and fails the scan (exit code `1`); an unchanged baseline
272
+ produces exit code `0`.
273
+
274
+ `.pyvibe-baseline.json` is a plain JSON file — whether to commit it or add it to
275
+ `.gitignore` is up to your team. Commit it if you want the accepted-debt snapshot shared
276
+ and reviewed like any other file; gitignore it if each contributor/CI run should
277
+ regenerate its own baseline instead.
278
+
243
279
  ---
244
280
 
245
281
  ## CI/CD integration
@@ -337,7 +373,7 @@ python -m pytest tests/ -v
337
373
  python tests/test_rules.py
338
374
  ```
339
375
 
340
- 226 tests: true positives + false-positive guards for every rule, plus SARIF output and `pyvibe explain` coverage.
376
+ 268 tests: true positives + false-positive guards for every rule, plus SARIF output, `pyvibe explain`, `pyvibe review`, and baseline mode coverage.
341
377
 
342
378
  ---
343
379
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-vibe-guard"
7
- version = "0.9.0"
7
+ version = "0.11.0"
8
8
  description = "Runtime anti-pattern scanner for async Python — detects what AI-generated code gets wrong"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-vibe-guard
3
- Version: 0.9.0
3
+ Version: 0.11.0
4
4
  Summary: Runtime anti-pattern scanner for async Python — detects what AI-generated code gets wrong
5
5
  License: MIT
6
6
  Keywords: async,linter,fastapi,asyncio,static-analysis
@@ -173,6 +173,10 @@ python -m pyvibe src/ --exclude tests
173
173
  # Show the research evidence behind a rule (accuracy, false positives, sources)
174
174
  python -m pyvibe explain PYVIBE-002
175
175
 
176
+ # Baseline mode: suppress pre-existing findings, only fail on new ones
177
+ python -m pyvibe baseline create src/ # snapshot current findings
178
+ python -m pyvibe src/ --baseline # only reports findings NOT in the baseline
179
+
176
180
  # Exit code: 0 = clean, 1 = violations found, 2 = path error
177
181
  ```
178
182
 
@@ -255,6 +259,38 @@ python -m pyvibe explain PYVIBE-002
255
259
  If a rule has no evidence file, it prints a clear `No evidence file found for PYVIBE-XXX`
256
260
  and exits non-zero — it never invents data.
257
261
 
262
+ ### Baseline mode
263
+
264
+ Adopting python-vibe-guard on an existing codebase usually means hundreds of pre-existing
265
+ findings you can't fix before CI needs to go green. Baseline mode snapshots the current
266
+ findings once, then only fails on genuinely **new** ones:
267
+
268
+ ```bash
269
+ # Snapshot every current finding into .pyvibe-baseline.json
270
+ python -m pyvibe baseline create src/
271
+
272
+ # Refuses to run if a baseline already exists — use `update` to overwrite it
273
+ python -m pyvibe baseline update src/
274
+
275
+ # Full scan, but only reports findings NOT already in the baseline
276
+ python -m pyvibe src/ --baseline
277
+ python -m pyvibe src/ --baseline --json
278
+ python -m pyvibe src/ --baseline --sarif
279
+
280
+ # `pyvibe scan` is an equivalent, explicit subcommand form of the same flags
281
+ python -m pyvibe scan src/ --baseline
282
+ ```
283
+
284
+ A finding is considered "already known" on an exact match of `(file path, rule ID, line
285
+ number)`. Anything that doesn't match — a new violation, or an existing one that moved to
286
+ a different line — is reported and fails the scan (exit code `1`); an unchanged baseline
287
+ produces exit code `0`.
288
+
289
+ `.pyvibe-baseline.json` is a plain JSON file — whether to commit it or add it to
290
+ `.gitignore` is up to your team. Commit it if you want the accepted-debt snapshot shared
291
+ and reviewed like any other file; gitignore it if each contributor/CI run should
292
+ regenerate its own baseline instead.
293
+
258
294
  ---
259
295
 
260
296
  ## CI/CD integration
@@ -352,7 +388,7 @@ python -m pytest tests/ -v
352
388
  python tests/test_rules.py
353
389
  ```
354
390
 
355
- 226 tests: true positives + false-positive guards for every rule, plus SARIF output and `pyvibe explain` coverage.
391
+ 268 tests: true positives + false-positive guards for every rule, plus SARIF output, `pyvibe explain`, `pyvibe review`, and baseline mode coverage.
356
392
 
357
393
  ---
358
394
 
@@ -9,7 +9,9 @@ pyvibe/__init__.py
9
9
  pyvibe/__main__.py
10
10
  pyvibe/analyzer.py
11
11
  pyvibe/autofix.py
12
+ pyvibe/baseline.py
12
13
  pyvibe/cli.py
14
+ pyvibe/diff.py
13
15
  pyvibe/explain.py
14
16
  pyvibe/rule_docs.py
15
17
  pyvibe/sarif.py
@@ -35,6 +37,8 @@ pyvibe/rules/sqlite_async.py
35
37
  pyvibe/rules/subprocess_async.py
36
38
  pyvibe/rules/threading_lock.py
37
39
  pyvibe/rules/while_true_no_await.py
40
+ tests/test_baseline.py
41
+ tests/test_diff.py
38
42
  tests/test_exclude.py
39
43
  tests/test_explain.py
40
44
  tests/test_rules.py
@@ -0,0 +1 @@
1
+ __version__ = "0.11.0"
@@ -1,6 +1,6 @@
1
1
  import ast
2
2
  from pathlib import Path
3
- from typing import FrozenSet, List
3
+ from typing import FrozenSet, List, Optional
4
4
 
5
5
  from pyvibe.rules.base import Violation
6
6
  from pyvibe.rules.async_sleep import AsyncSleepRule
@@ -132,9 +132,16 @@ def analyze_file(
132
132
  path: Path,
133
133
  *,
134
134
  downgrade_in_tests: FrozenSet[str] = TEST_FILE_DOWNGRADE,
135
+ line_filter: Optional[FrozenSet[int]] = None,
135
136
  ) -> List[Violation]:
137
+ """line_filter: if given, only violations whose `line` is in this set
138
+ are returned (used by `pyvibe review` to report only diff-touched lines).
139
+ """
136
140
  source = path.read_text(encoding="utf-8", errors="ignore")
137
- return analyze_source(source, filepath=str(path), downgrade_in_tests=downgrade_in_tests)
141
+ violations = analyze_source(source, filepath=str(path), downgrade_in_tests=downgrade_in_tests)
142
+ if line_filter is not None:
143
+ violations = [v for v in violations if v.line in line_filter]
144
+ return violations
138
145
 
139
146
 
140
147
  DEFAULT_EXCLUDES = frozenset({
@@ -0,0 +1,73 @@
1
+ """Baseline support — `pyvibe baseline create/update` and `pyvibe scan --baseline`.
2
+
3
+ A baseline is a snapshot of existing findings ({filepath: [{rule_id, line,
4
+ message}, ...]}) that a team can choose to commit or gitignore (see README).
5
+ Once created, `pyvibe scan --baseline` only reports genuinely NEW
6
+ violations — anything already in the baseline is suppressed — so a
7
+ codebase can adopt python-vibe-guard incrementally instead of having to
8
+ fix every existing hit before CI goes green.
9
+ """
10
+ import json
11
+ from pathlib import Path
12
+ from typing import Dict, Set, Tuple
13
+
14
+ DEFAULT_BASELINE_PATH = ".pyvibe-baseline.json"
15
+
16
+
17
+ class BaselineNotFoundError(Exception):
18
+ """Raised when `pyvibe scan --baseline` can't find/parse the baseline file."""
19
+
20
+
21
+ def build_baseline(file_results: Dict) -> dict:
22
+ """file_results: {path: [Violation, ...]} as produced by analyze_file /
23
+ analyze_directory. Returns the JSON-serializable baseline:
24
+ {filepath: [{rule_id, line, message}, ...]}.
25
+ """
26
+ return {
27
+ str(path): [
28
+ {"rule_id": v.rule_id, "line": v.line, "message": v.message}
29
+ for v in violations
30
+ ]
31
+ for path, violations in file_results.items()
32
+ }
33
+
34
+
35
+ def write_baseline(file_results: Dict, output_path=DEFAULT_BASELINE_PATH) -> None:
36
+ baseline = build_baseline(file_results)
37
+ Path(output_path).write_text(json.dumps(baseline, indent=2), encoding="utf-8")
38
+
39
+
40
+ def load_baseline(path=DEFAULT_BASELINE_PATH) -> dict:
41
+ baseline_path = Path(path)
42
+ if not baseline_path.exists():
43
+ raise BaselineNotFoundError(
44
+ f"No baseline found at {path}. Run `pyvibe baseline create` first."
45
+ )
46
+ try:
47
+ return json.loads(baseline_path.read_text(encoding="utf-8"))
48
+ except json.JSONDecodeError as e:
49
+ raise BaselineNotFoundError(f"Baseline at {path} is not valid JSON: {e}") from e
50
+
51
+
52
+ def _baseline_keys(baseline: dict) -> Set[Tuple[str, str, int]]:
53
+ """Flatten the baseline into (filepath, rule_id, line) triples for exact,
54
+ O(1) membership checks.
55
+ """
56
+ return {
57
+ (filepath, entry["rule_id"], entry["line"])
58
+ for filepath, entries in baseline.items()
59
+ for entry in entries
60
+ }
61
+
62
+
63
+ def filter_new_violations(file_results: Dict, baseline: dict) -> Dict:
64
+ """Return only the violations in file_results NOT present in baseline,
65
+ matched exactly on (filepath, rule_id, line).
66
+ """
67
+ known = _baseline_keys(baseline)
68
+ new_results = {}
69
+ for path, violations in file_results.items():
70
+ new = [v for v in violations if (str(path), v.rule_id, v.line) not in known]
71
+ if new:
72
+ new_results[path] = new
73
+ return new_results