devtrust-apr 0.2.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.
- devtrust_apr-0.2.0/.gitignore +45 -0
- devtrust_apr-0.2.0/CHANGELOG.md +139 -0
- devtrust_apr-0.2.0/PKG-INFO +111 -0
- devtrust_apr-0.2.0/README.md +77 -0
- devtrust_apr-0.2.0/pyproject.toml +63 -0
- devtrust_apr-0.2.0/src/apr/__init__.py +14 -0
- devtrust_apr-0.2.0/src/apr/__main__.py +6 -0
- devtrust_apr-0.2.0/src/apr/cli.py +150 -0
- devtrust_apr-0.2.0/src/apr/engine.py +114 -0
- devtrust_apr-0.2.0/src/apr/llm.py +188 -0
- devtrust_apr-0.2.0/src/apr/models.py +111 -0
- devtrust_apr-0.2.0/src/apr/output.py +93 -0
- devtrust_apr-0.2.0/src/apr/prompts.py +193 -0
- devtrust_apr-0.2.0/src/apr/repox_integration.py +176 -0
- devtrust_apr-0.2.0/src/apr/rules.py +401 -0
- devtrust_apr-0.2.0/src/apr/rules_ai.py +640 -0
- devtrust_apr-0.2.0/src/apr/rules_js.py +201 -0
- devtrust_apr-0.2.0/tests/__init__.py +0 -0
- devtrust_apr-0.2.0/tests/conftest.py +91 -0
- devtrust_apr-0.2.0/tests/test_smoke.py +1031 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# OS / editor noise
|
|
2
|
+
.DS_Store
|
|
3
|
+
Thumbs.db
|
|
4
|
+
*.swp
|
|
5
|
+
*~
|
|
6
|
+
.idea/
|
|
7
|
+
.vscode/
|
|
8
|
+
*.iml
|
|
9
|
+
|
|
10
|
+
# Local environment
|
|
11
|
+
.env
|
|
12
|
+
.env.local
|
|
13
|
+
.env.*.local
|
|
14
|
+
*.local
|
|
15
|
+
|
|
16
|
+
# Build / dependency output
|
|
17
|
+
node_modules/
|
|
18
|
+
dist/
|
|
19
|
+
build/
|
|
20
|
+
target/
|
|
21
|
+
__pycache__/
|
|
22
|
+
*.pyc
|
|
23
|
+
.pytest_cache/
|
|
24
|
+
coverage/
|
|
25
|
+
.cache/
|
|
26
|
+
|
|
27
|
+
# Logs
|
|
28
|
+
*.log
|
|
29
|
+
logs/
|
|
30
|
+
|
|
31
|
+
# Secrets — NEVER commit these
|
|
32
|
+
*.pem
|
|
33
|
+
*.key
|
|
34
|
+
secrets/
|
|
35
|
+
*.secrets.json
|
|
36
|
+
|
|
37
|
+
# Docx working artifacts (keep .docx itself, drop temp files)
|
|
38
|
+
~$*.docx
|
|
39
|
+
~$*.xlsx
|
|
40
|
+
~$*.pptx
|
|
41
|
+
|
|
42
|
+
# Claude session-specific (keep configs, drop runtime)
|
|
43
|
+
.claude/sessions/
|
|
44
|
+
.claude/cache/
|
|
45
|
+
.claude/.tmp/
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Agent-PR Reviewer — changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `apr` are documented here. Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [SemVer](https://semver.org/spec/v2.0.0.html).
|
|
4
|
+
|
|
5
|
+
## [0.2.0] — 2026-05-09
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **`ai-review:hallucinated-symbol` is now multi-language.** The deterministic AI-pattern checker that flags calls to names that don't exist now consumes Repo X-ray v0.4's JavaScript / TypeScript call edges in addition to Python. The rule fires the same way for all three languages: walk the artifact's edges, scoped to changed files, and flag callees whose first segment doesn't resolve to an in-repo symbol or a known global / package root.
|
|
9
|
+
- **Language-aware allowlist.** `_KNOWN_NAMES_PY` (Python builtins + stdlib + ~50 popular pip roots) and `_KNOWN_NAMES_JS` (ECMAScript globals, browser DOM globals, Node.js globals, ~70 popular npm package roots) are now disjoint. A `.ts` file calling `os.path.join` is not silenced by the Python allowlist — it's correctly flagged as a wrong-language hallucination signal.
|
|
10
|
+
- **In-repo JS/TS imports already pass.** Repox v0.4 sets `target_file` on every call edge whose callee was bound via `import { x } from './y'` to an in-repo file. The rule's existing `target_file is set -> skip` short-circuit handles those without changes.
|
|
11
|
+
- **External JS/TS imports.** `apr.repox_integration._binding_hint` now extracts a usable binding name from JS/TS module specifiers — `react-dom/client` → `client`, `@scope/pkg/sub` → `sub`, bare `'express'` → `'express'`. Renamed default imports (`import _ from 'lodash'`) are silenced by the existing two-character-name skip rather than misflagged.
|
|
12
|
+
- **8 new tests** in `tests/test_smoke.py`:
|
|
13
|
+
- `test_ai_rule_js_hallucinated_callee_flagged` — truly invented JS callee fires.
|
|
14
|
+
- `test_ai_rule_js_console_log_safe` — `console.log` doesn't fire.
|
|
15
|
+
- `test_ai_rule_js_browser_globals_safe` — `document.querySelector`, `fetch`, `localStorage.getItem` don't fire.
|
|
16
|
+
- `test_ai_rule_js_node_globals_safe` — `process.exit`, `Buffer.from` don't fire.
|
|
17
|
+
- `test_ai_rule_js_npm_root_safe` — `express()` doesn't fire when imported.
|
|
18
|
+
- `test_ai_rule_ts_in_repo_call_resolved` — call edge with `target_file` set is silent.
|
|
19
|
+
- `test_ai_rule_js_wrong_language_callee_flagged` — JS file calling `os.path.join` IS flagged (Python allowlist is not consulted for JS files).
|
|
20
|
+
- `test_binding_hint_for_js_specifiers` — direct unit test for the new `_binding_hint` helper.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- `apr.repox_integration` extracts the new `_binding_hint(target_module)` helper instead of inline `lstrip + split`. Behavior unchanged for Python imports; JS/TS specifiers now produce more accurate hints (subpath last-segment, scoped-package last-segment).
|
|
24
|
+
- The version-pin smoke test (`test_apr_version_*`) now uses a structural SemVer regex check instead of an exact-string equality, matching the pattern used in agtrace, tokencost, and whychanged. This prevents the test from going stale on every minor bump and keeps `scripts/release.py --check` clean.
|
|
25
|
+
|
|
26
|
+
### Notes
|
|
27
|
+
- Schema unchanged (still `0.0.1` — additive: same `Finding` shape, same rule IDs, just broader coverage).
|
|
28
|
+
- Diff-comprehension rule is unchanged in v0.2.
|
|
29
|
+
- This release closes the only deferred v0.1 item: when we shipped repox v0.4 with JS/TS call edges, apr's hallucinated-symbol rule was still Python-only. v0.2.0 closes that gap and gives APR full multi-language coverage across all three Wave-1 supported languages (Python, JavaScript, TypeScript).
|
|
30
|
+
- Known limitation: renamed default imports like `import _ from 'lodash'` cannot be precisely tracked without extending the repox `Import` model to carry `local_names`. v0.2.0 mitigates by skipping ≤ 2-character first-segments; a future repox v0.5 + apr v0.3 will close this fully.
|
|
31
|
+
|
|
32
|
+
[0.2.0]: https://github.com/AbdullahBakir97/apr/compare/v0.1.1...v0.2.0
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## [0.1.1] — 2026-05-08
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
- **Real Anthropic backend for `ai-review:diff-comprehension`.** `AnthropicProvider.analyze_diff()` is now a working implementation:
|
|
40
|
+
- Calls the Messages API (non-streaming, single-turn) via the official `anthropic` SDK.
|
|
41
|
+
- Builds a JSON-shaped prompt (system + user) via the new `apr.prompts` module — diff is truncated at 60,000 chars by default to bound input cost on runaway diffs.
|
|
42
|
+
- Caps reply at 1024 tokens (configurable via the `AnthropicProvider` constructor).
|
|
43
|
+
- Parses the model's reply tolerantly: tries strict `json.loads` first, then extracts the first balanced `{...}` block when the model wraps JSON in prose.
|
|
44
|
+
- Filters invalid severity values silently rather than raising — no LLM tantrum can break review.
|
|
45
|
+
- New `apr.prompts` module — separates prompt construction + response parsing from the SDK so it can be unit-tested without the `anthropic` wheel installed.
|
|
46
|
+
- New env var `APR_ANTHROPIC_MODEL` lets operators override the default model (`claude-sonnet-4-6`) per deployment.
|
|
47
|
+
- 9 new tests: prompt construction, diff truncation, strict JSON parse, prose-wrapper recovery, garbage / invalid-severity rejection, end-to-end with a mocked SDK client, SDK-exception shielding, unparseable-reply handling, version assertion.
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
- `AnthropicProvider.__init__` accepts an optional `client` keyword for tests + future dependency injection.
|
|
51
|
+
- The SDK `import anthropic` happens lazily inside `_ensure_client`, so `apr` installs without the optional `[ai]` extra.
|
|
52
|
+
- All findings produced through this rule emerge with `rule_id="ai-review:diff-comprehension"` regardless of what the model claimed — vendor-internal IDs cannot leak into the report.
|
|
53
|
+
|
|
54
|
+
### Notes
|
|
55
|
+
- Schema unchanged (still 0.0.1 — same Finding shape).
|
|
56
|
+
- Cost guidance: a typical 500-line PR diff produces ~3K input tokens + ~200 output tokens, i.e. < $0.005 per review at Sonnet pricing. Bound by `max_diff_chars` and `max_tokens`. Operators with high PR volume should monitor and tune.
|
|
57
|
+
- The rule is still gated behind `--enable-ai --ai-provider=anthropic` and requires `ANTHROPIC_API_KEY` in the environment. Default behavior (no flag) is unchanged: deterministic rules only, no API calls, no cost.
|
|
58
|
+
|
|
59
|
+
[0.1.1]: https://github.com/AbdullahBakir97/apr/compare/v0.1.0...v0.1.1
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## [0.1.0] — 2026-05-08
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
- **AI rule pack (`ai-review:*`).** New `src/apr/rules_ai.py` ships two rules, both opt-in via `--enable-ai`:
|
|
67
|
+
- `ai-review:hallucinated-symbol` (warning, ai-pattern) — **deterministic.** Walks Repo X-ray's `call_graph.edges` and flags function calls whose callee doesn't resolve to an in-repo symbol AND isn't a Python built-in / well-known stdlib root / file-local imported alias. Catches the classic AI-generation failure mode where a function call references a name that doesn't exist anywhere in the codebase.
|
|
68
|
+
- `ai-review:diff-comprehension` (delegates to `LLMProvider`) — pluggable LLM-backed checker for "does the PR description accurately describe the diff." v0.1.0 ships the `NullProvider` (returns no findings) and an `AnthropicProvider` stub. The streaming Anthropic implementation is v0.1.1.
|
|
69
|
+
- **Repox integration** (`src/apr/repox_integration.py`) — best-effort loader for `.repox/architecture.json`. Returns `None` for older artifacts (v0.0.x / v0.1.x) without a call graph; the AI rule pack then no-ops cleanly.
|
|
70
|
+
- **LLM provider interface** (`src/apr/llm.py`) — `LLMProvider` Protocol + `NullProvider` + `AnthropicProvider` stub + `build_provider()` factory. Vendor swaps are a matter of writing a new provider class; the engine and rules don't change.
|
|
71
|
+
- **CLI flags** — `--enable-ai/--no-enable-ai` (default off), `--ai-provider null|anthropic`, `--diff PATH` (unified-diff file for diff-comprehension).
|
|
72
|
+
- **Optional `[ai]` extra** in `pyproject.toml` — `pip install apr[ai]` brings in the `anthropic` SDK.
|
|
73
|
+
- **9 new tests** covering: hallucinated-symbol fires correctly, builtin/import allow-list silences false positives, only changed files are scanned, AI off by default, provider exception shielding, vendor rule_id namespace isolation, the AnthropicProvider stub raising NotImplementedError, repox integration returning None for missing/old artifacts.
|
|
74
|
+
|
|
75
|
+
### Changed
|
|
76
|
+
- **`check_python_file` ordering fix.** Source-text rules (`todo-no-ticket`, `hardcoded-secret`) now run *before* AST parsing, so a syntax error doesn't silently suppress them. Regression tests added — was a real bug discovered when a BOM-encoded demo file produced only `syntax-error` and missed the embedded AKIA-shaped key.
|
|
77
|
+
- Promoted from `Development Status :: 3 - Alpha` to `Development Status :: 4 - Beta`.
|
|
78
|
+
- Engine `review()` gained kwargs: `enable_ai`, `llm_provider`, `diff`. Backward compatible — defaults preserve the v0.0.2 behavior.
|
|
79
|
+
|
|
80
|
+
### Notes
|
|
81
|
+
- Schema unchanged (still 0.0.1 — same Finding shape, additive rule IDs).
|
|
82
|
+
- AI rules are off by default to keep PR review fast and deterministic for the common case. Operators opt in per repo by adding `--enable-ai` to their CI invocation.
|
|
83
|
+
- The hallucinated-symbol allow-list is conservative: stdlib roots + ~50 common third-party roots + apr/repox/sts itself. False positives are corrected by adding the missing root, not by relaxing the rule.
|
|
84
|
+
|
|
85
|
+
[0.1.0]: https://github.com/AbdullahBakir97/apr/compare/v0.0.2...v0.1.0
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## [0.0.2] — 2026-05-08
|
|
90
|
+
|
|
91
|
+
### Added
|
|
92
|
+
- **JS / TS rule pack** via tree-sitter (`src/apr/rules_js.py`):
|
|
93
|
+
- `console-log` (info, quality) — leftover `console.log` / `.debug` / `.info` calls. Skipped in obvious entry files (presence of `process.argv`, `.listen(`, or `import.meta.main`).
|
|
94
|
+
- `debugger-statement` (warning, quality) — `debugger;` left in code.
|
|
95
|
+
- `var-declaration` (info, style) — `var x = ...` instead of `let`/`const`.
|
|
96
|
+
- `todo-no-ticket` (info, todo) — TODO/FIXME/XXX/HACK without `#123` or `PROJ-123`. Mirror of the Python rule.
|
|
97
|
+
- **Additional Python rules** in `src/apr/rules.py`:
|
|
98
|
+
- `mutable-default-arg` (warning, quality) — `def f(x=[])` shares state across calls.
|
|
99
|
+
- `broad-except` (info, quality) — `except Exception:` is wider than most code needs.
|
|
100
|
+
- `assert-in-prod` (warning, security) — `assert` is stripped under `python -O`. Test files (`tests/`, `test_*.py`) are exempt.
|
|
101
|
+
- `hardcoded-secret` (critical, security) — high-precision regex pack catches AWS access keys (`AKIA*`), GitHub tokens (`ghp_*`, `ghs_*`), OpenAI keys (`sk-*`), Anthropic keys (`sk-ant-*`), Slack bot tokens (`xoxb-*`), and inline `password=`/`api_key=` literals.
|
|
102
|
+
- 13 new tests covering each rule end-to-end (some `pytest.importorskip` JS/TS rules when tree-sitter wheels aren't available on the platform).
|
|
103
|
+
|
|
104
|
+
### Changed
|
|
105
|
+
- `apr.rules.check_file` now dispatches by extension: `.py` -> Python rules, `.js`/`.jsx`/`.mjs`/`.cjs`/`.ts`/`.tsx` -> tree-sitter rules, others -> empty.
|
|
106
|
+
- Tree-sitter (`tree-sitter>=0.23` + `tree-sitter-language-pack>=0.4,<1.0`) graduated to required deps.
|
|
107
|
+
|
|
108
|
+
### Notes
|
|
109
|
+
- Schema unchanged (still **0.0.1**) — additive: same `Finding` shape, just more rule IDs.
|
|
110
|
+
- The hardcoded-secret rule is precision-tuned (high-confidence patterns only). False positives on `password = "x"` style literals are possible but accepted at info+ severity. False negatives on bespoke key formats are accepted; `apr` is meant to complement, not replace, dedicated secret scanners.
|
|
111
|
+
|
|
112
|
+
[0.0.2]: https://github.com/AbdullahBakir97/apr/compare/v0.0.1...v0.0.2
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## [0.0.1] — 2026-05-08
|
|
117
|
+
|
|
118
|
+
### Added
|
|
119
|
+
- Initial scaffold: `apr` Python package with two CLI commands (`review`, `version`).
|
|
120
|
+
- Pydantic v2 schema (versioned 0.0.1): `Finding`, `ReviewInputs`, `ReviewStats`, `ReviewReport`.
|
|
121
|
+
- `rules` module — deterministic rule pack for Python:
|
|
122
|
+
- `bare-except` (warning) — bare `except:` clause.
|
|
123
|
+
- `print-debug` (info) — leftover `print(...)`, skipped in `__main__` guard files.
|
|
124
|
+
- `todo-no-ticket` (info) — TODO/FIXME/XXX/HACK without `#123` / `PROJ-123` reference.
|
|
125
|
+
- `empty-function-body` (info, ai-pattern) — function body is only `pass`.
|
|
126
|
+
- `syntax-error` (error) — file does not parse.
|
|
127
|
+
- `rules.check_pr_metadata` — `pr-title-uninformative` (warning), `pr-description-too-short` (info).
|
|
128
|
+
- `engine` — orchestrator: PR-level checks first, then per-file rule packs; stably sorted findings; severity-bucket stats.
|
|
129
|
+
- `output` — JSON + Markdown writers emitting to `.apr/review.{json,md}`. The Markdown report includes a "Suggested fixes" block surfacing up to 10 suggestions with file:line.
|
|
130
|
+
- Workspace dep on `repox` (apr will use the architecture model for v0.0.2+ file-context rules).
|
|
131
|
+
- 16 smoke tests covering each rule, the engine, both writers, and the CLI.
|
|
132
|
+
- Apache-2.0 license, hatchling build, typer/rich/pydantic deps.
|
|
133
|
+
|
|
134
|
+
### Notes
|
|
135
|
+
- Wave 2 lead bet of the DevTrust platform. Consolidates the patterns from three earlier GitHub Apps (`ai-quality-gate`, `pr-coach`, `commit-craft`) into one engine.
|
|
136
|
+
- v0.0.1 is **Python only by design**. JS/TS rules in v0.0.2.
|
|
137
|
+
- LLM-backed checks (`ai-review:*` rule IDs) land in v0.1+ once the deterministic rule output is stable enough to grade against.
|
|
138
|
+
|
|
139
|
+
[0.0.1]: https://github.com/AbdullahBakir97/apr/releases/tag/v0.0.1
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: devtrust-apr
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Agent-PR Reviewer — deterministic + AI-pattern review for pull requests.
|
|
5
|
+
Project-URL: Homepage, https://github.com/AbdullahBakir97/DevTrust
|
|
6
|
+
Project-URL: Repository, https://github.com/AbdullahBakir97/DevTrust
|
|
7
|
+
Project-URL: Documentation, https://github.com/AbdullahBakir97/DevTrust/tree/main/src/products/03-agent-pr-reviewer/code#readme
|
|
8
|
+
Project-URL: Changelog, https://github.com/AbdullahBakir97/DevTrust/blob/main/src/products/03-agent-pr-reviewer/code/CHANGELOG.md
|
|
9
|
+
Project-URL: Issues, https://github.com/AbdullahBakir97/DevTrust/issues
|
|
10
|
+
Author: Abdullah Bakir
|
|
11
|
+
License-Expression: Apache-2.0
|
|
12
|
+
Keywords: ci,code-review,developer-tools,github-app,pr-review
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Requires-Dist: devtrust-repox
|
|
26
|
+
Requires-Dist: pydantic>=2.10.0
|
|
27
|
+
Requires-Dist: rich>=13.9.0
|
|
28
|
+
Requires-Dist: tree-sitter-language-pack<1.0,>=0.4.0
|
|
29
|
+
Requires-Dist: tree-sitter>=0.23.0
|
|
30
|
+
Requires-Dist: typer>=0.15.0
|
|
31
|
+
Provides-Extra: ai
|
|
32
|
+
Requires-Dist: anthropic>=0.40.0; extra == 'ai'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# Agent-PR Reviewer (`apr`)
|
|
36
|
+
|
|
37
|
+
> Deterministic + AI-pattern review for pull requests. Wave 2 lead bet of the DevTrust connected platform.
|
|
38
|
+
|
|
39
|
+
## Status
|
|
40
|
+
|
|
41
|
+
**v0.2.0 beta** — deterministic Python + JS/TS rules, deterministic AI-pattern checker (`ai-review:hallucinated-symbol`) now multi-language via Repo X-ray v0.4 call edges, plus optional LLM-backed `ai-review:diff-comprehension` (Anthropic).
|
|
42
|
+
|
|
43
|
+
## Why
|
|
44
|
+
|
|
45
|
+
Three of your existing GitHub Apps — `ai-quality-gate`, `pr-coach`, `commit-craft` — each solve part of "make PR review better" but ship as separate apps with separate auth, separate webhooks, separate UIs. `apr` consolidates them into one engine so:
|
|
46
|
+
|
|
47
|
+
- One install. One webhook. One sticky comment per PR.
|
|
48
|
+
- Deterministic rules first (testable, gradeable, cheap), LLM layer second.
|
|
49
|
+
- Reuses Repo X-ray's architecture model for file context.
|
|
50
|
+
|
|
51
|
+
## Rules shipped through v0.0.2
|
|
52
|
+
|
|
53
|
+
### Python (`.py`)
|
|
54
|
+
|
|
55
|
+
| Rule ID | Severity | Category | What it catches |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| `bare-except` | warning | quality | `except:` without an exception class |
|
|
58
|
+
| `print-debug` | info | quality | leftover `print(...)` calls (skipped in `__main__` guard files) |
|
|
59
|
+
| `todo-no-ticket` | info | todo | TODO/FIXME/XXX/HACK without `#123` / `PROJ-123` reference |
|
|
60
|
+
| `empty-function-body` | info | ai-pattern | function body is only `pass` |
|
|
61
|
+
| `syntax-error` | error | quality | file does not parse |
|
|
62
|
+
| `mutable-default-arg` | warning | quality | `def f(x=[])` shares state across calls |
|
|
63
|
+
| `broad-except` | info | quality | `except Exception:` is broader than most code needs |
|
|
64
|
+
| `assert-in-prod` | warning | security | `assert` is stripped under `python -O`. Test files exempt. |
|
|
65
|
+
| `hardcoded-secret` | critical | security | AWS / GitHub / OpenAI / Anthropic / Slack tokens, inline credential literals |
|
|
66
|
+
|
|
67
|
+
### JavaScript / TypeScript (`.js .jsx .mjs .cjs .ts .tsx`) — v0.0.2+
|
|
68
|
+
|
|
69
|
+
| Rule ID | Severity | Category | What it catches |
|
|
70
|
+
|---|---|---|---|
|
|
71
|
+
| `console-log` | info | quality | leftover `console.log/debug/info`. Skipped in entry files (`process.argv`, `.listen(`, `import.meta.main`). |
|
|
72
|
+
| `debugger-statement` | warning | quality | `debugger;` left in code |
|
|
73
|
+
| `var-declaration` | info | style | `var` instead of `let`/`const` |
|
|
74
|
+
| `todo-no-ticket` | info | todo | mirror of the Python rule |
|
|
75
|
+
|
|
76
|
+
### PR-level
|
|
77
|
+
|
|
78
|
+
| Rule ID | Severity | Category | What it catches |
|
|
79
|
+
|---|---|---|---|
|
|
80
|
+
| `pr-title-uninformative` | warning | commit | PR title too short or one of `wip`/`draft`/`tmp`/`test` |
|
|
81
|
+
| `pr-description-too-short` | info | commit | PR description under 30 characters |
|
|
82
|
+
|
|
83
|
+
### AI-pattern (opt-in via `--enable-ai`) — v0.1.0+ / v0.2.0 multi-language
|
|
84
|
+
|
|
85
|
+
| Rule ID | Severity | Category | What it catches |
|
|
86
|
+
|---|---|---|---|
|
|
87
|
+
| `ai-review:hallucinated-symbol` | warning | ai-pattern | A function call whose name doesn't resolve to an in-repo symbol, an imported alias, or a known stdlib / global / popular package root. **Now spans Python + JS/TS as of v0.2.0.** Requires `.repox/architecture.json` (run `repox` first). |
|
|
88
|
+
| `ai-review:diff-comprehension` | warning/info | ai-pattern | LLM-backed check for "does the PR description accurately describe the diff?". Pass `--ai-provider anthropic` and set `ANTHROPIC_API_KEY`. |
|
|
89
|
+
|
|
90
|
+
## CLI
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
apr version
|
|
94
|
+
apr review --repo .
|
|
95
|
+
apr review --repo . --changed src/foo.py --changed src/bar.py
|
|
96
|
+
apr review --repo . --title "Fix nullable fields" --description "..."
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Output: `.apr/review.json` (schema-versioned, machine-readable) + `.apr/review.md` (human companion).
|
|
100
|
+
|
|
101
|
+
## Roadmap
|
|
102
|
+
|
|
103
|
+
- ✅ **v0.0.2** — JS/TS rule pack (`console-log`, `debugger-statement`, `var-declaration`, `todo-no-ticket`).
|
|
104
|
+
- ✅ **v0.1.0** — AI rule pack: deterministic `ai-review:hallucinated-symbol` + LLM-pluggable `ai-review:diff-comprehension`.
|
|
105
|
+
- ✅ **v0.1.1** — real Anthropic backend for `ai-review:diff-comprehension`.
|
|
106
|
+
- ✅ **v0.2.0** — `ai-review:hallucinated-symbol` extended to JS/TS via repox v0.4 call edges. APR now covers all three Wave-1 languages.
|
|
107
|
+
- **v0.3.0** (next) — auto-suggest fixes via the GitHub Suggested Changes API; per-import `local_names` for renamed JS imports.
|
|
108
|
+
|
|
109
|
+
## Status
|
|
110
|
+
|
|
111
|
+
Apache-2.0. See [CHANGELOG](CHANGELOG.md).
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Agent-PR Reviewer (`apr`)
|
|
2
|
+
|
|
3
|
+
> Deterministic + AI-pattern review for pull requests. Wave 2 lead bet of the DevTrust connected platform.
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
**v0.2.0 beta** — deterministic Python + JS/TS rules, deterministic AI-pattern checker (`ai-review:hallucinated-symbol`) now multi-language via Repo X-ray v0.4 call edges, plus optional LLM-backed `ai-review:diff-comprehension` (Anthropic).
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
Three of your existing GitHub Apps — `ai-quality-gate`, `pr-coach`, `commit-craft` — each solve part of "make PR review better" but ship as separate apps with separate auth, separate webhooks, separate UIs. `apr` consolidates them into one engine so:
|
|
12
|
+
|
|
13
|
+
- One install. One webhook. One sticky comment per PR.
|
|
14
|
+
- Deterministic rules first (testable, gradeable, cheap), LLM layer second.
|
|
15
|
+
- Reuses Repo X-ray's architecture model for file context.
|
|
16
|
+
|
|
17
|
+
## Rules shipped through v0.0.2
|
|
18
|
+
|
|
19
|
+
### Python (`.py`)
|
|
20
|
+
|
|
21
|
+
| Rule ID | Severity | Category | What it catches |
|
|
22
|
+
|---|---|---|---|
|
|
23
|
+
| `bare-except` | warning | quality | `except:` without an exception class |
|
|
24
|
+
| `print-debug` | info | quality | leftover `print(...)` calls (skipped in `__main__` guard files) |
|
|
25
|
+
| `todo-no-ticket` | info | todo | TODO/FIXME/XXX/HACK without `#123` / `PROJ-123` reference |
|
|
26
|
+
| `empty-function-body` | info | ai-pattern | function body is only `pass` |
|
|
27
|
+
| `syntax-error` | error | quality | file does not parse |
|
|
28
|
+
| `mutable-default-arg` | warning | quality | `def f(x=[])` shares state across calls |
|
|
29
|
+
| `broad-except` | info | quality | `except Exception:` is broader than most code needs |
|
|
30
|
+
| `assert-in-prod` | warning | security | `assert` is stripped under `python -O`. Test files exempt. |
|
|
31
|
+
| `hardcoded-secret` | critical | security | AWS / GitHub / OpenAI / Anthropic / Slack tokens, inline credential literals |
|
|
32
|
+
|
|
33
|
+
### JavaScript / TypeScript (`.js .jsx .mjs .cjs .ts .tsx`) — v0.0.2+
|
|
34
|
+
|
|
35
|
+
| Rule ID | Severity | Category | What it catches |
|
|
36
|
+
|---|---|---|---|
|
|
37
|
+
| `console-log` | info | quality | leftover `console.log/debug/info`. Skipped in entry files (`process.argv`, `.listen(`, `import.meta.main`). |
|
|
38
|
+
| `debugger-statement` | warning | quality | `debugger;` left in code |
|
|
39
|
+
| `var-declaration` | info | style | `var` instead of `let`/`const` |
|
|
40
|
+
| `todo-no-ticket` | info | todo | mirror of the Python rule |
|
|
41
|
+
|
|
42
|
+
### PR-level
|
|
43
|
+
|
|
44
|
+
| Rule ID | Severity | Category | What it catches |
|
|
45
|
+
|---|---|---|---|
|
|
46
|
+
| `pr-title-uninformative` | warning | commit | PR title too short or one of `wip`/`draft`/`tmp`/`test` |
|
|
47
|
+
| `pr-description-too-short` | info | commit | PR description under 30 characters |
|
|
48
|
+
|
|
49
|
+
### AI-pattern (opt-in via `--enable-ai`) — v0.1.0+ / v0.2.0 multi-language
|
|
50
|
+
|
|
51
|
+
| Rule ID | Severity | Category | What it catches |
|
|
52
|
+
|---|---|---|---|
|
|
53
|
+
| `ai-review:hallucinated-symbol` | warning | ai-pattern | A function call whose name doesn't resolve to an in-repo symbol, an imported alias, or a known stdlib / global / popular package root. **Now spans Python + JS/TS as of v0.2.0.** Requires `.repox/architecture.json` (run `repox` first). |
|
|
54
|
+
| `ai-review:diff-comprehension` | warning/info | ai-pattern | LLM-backed check for "does the PR description accurately describe the diff?". Pass `--ai-provider anthropic` and set `ANTHROPIC_API_KEY`. |
|
|
55
|
+
|
|
56
|
+
## CLI
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
apr version
|
|
60
|
+
apr review --repo .
|
|
61
|
+
apr review --repo . --changed src/foo.py --changed src/bar.py
|
|
62
|
+
apr review --repo . --title "Fix nullable fields" --description "..."
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Output: `.apr/review.json` (schema-versioned, machine-readable) + `.apr/review.md` (human companion).
|
|
66
|
+
|
|
67
|
+
## Roadmap
|
|
68
|
+
|
|
69
|
+
- ✅ **v0.0.2** — JS/TS rule pack (`console-log`, `debugger-statement`, `var-declaration`, `todo-no-ticket`).
|
|
70
|
+
- ✅ **v0.1.0** — AI rule pack: deterministic `ai-review:hallucinated-symbol` + LLM-pluggable `ai-review:diff-comprehension`.
|
|
71
|
+
- ✅ **v0.1.1** — real Anthropic backend for `ai-review:diff-comprehension`.
|
|
72
|
+
- ✅ **v0.2.0** — `ai-review:hallucinated-symbol` extended to JS/TS via repox v0.4 call edges. APR now covers all three Wave-1 languages.
|
|
73
|
+
- **v0.3.0** (next) — auto-suggest fixes via the GitHub Suggested Changes API; per-import `local_names` for renamed JS imports.
|
|
74
|
+
|
|
75
|
+
## Status
|
|
76
|
+
|
|
77
|
+
Apache-2.0. See [CHANGELOG](CHANGELOG.md).
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
# `apr` collides with the Apache Portable Runtime on PyPI; namespaced
|
|
7
|
+
# to avoid conflict. Module name stays `apr`.
|
|
8
|
+
name = "devtrust-apr"
|
|
9
|
+
version = "0.2.0"
|
|
10
|
+
description = "Agent-PR Reviewer — deterministic + AI-pattern review for pull requests."
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
requires-python = ">=3.11"
|
|
13
|
+
license = "Apache-2.0"
|
|
14
|
+
authors = [{ name = "Abdullah Bakir" }]
|
|
15
|
+
keywords = ["ci", "code-review", "developer-tools", "github-app", "pr-review"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: Apache Software License",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Programming Language :: Python :: 3.14",
|
|
27
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
dependencies = [
|
|
31
|
+
"typer>=0.15.0",
|
|
32
|
+
"rich>=13.9.0",
|
|
33
|
+
"pydantic>=2.10.0",
|
|
34
|
+
# apr consumes Repo X-ray's architecture model for file context.
|
|
35
|
+
"devtrust-repox",
|
|
36
|
+
# JS / TS rule pack via tree-sitter (mirrors repox's pin).
|
|
37
|
+
"tree-sitter>=0.23.0",
|
|
38
|
+
"tree-sitter-language-pack>=0.4.0,<1.0",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.optional-dependencies]
|
|
42
|
+
# Real LLM-backed rules (ai-review:diff-comprehension). The
|
|
43
|
+
# deterministic ai-review:hallucinated-symbol rule does NOT need
|
|
44
|
+
# this extra - it works from the repox artifact alone.
|
|
45
|
+
ai = [
|
|
46
|
+
"anthropic>=0.40.0",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[project.scripts]
|
|
50
|
+
apr = "apr.cli:app"
|
|
51
|
+
|
|
52
|
+
[project.urls]
|
|
53
|
+
Homepage = "https://github.com/AbdullahBakir97/DevTrust"
|
|
54
|
+
Repository = "https://github.com/AbdullahBakir97/DevTrust"
|
|
55
|
+
Documentation = "https://github.com/AbdullahBakir97/DevTrust/tree/main/src/products/03-agent-pr-reviewer/code#readme"
|
|
56
|
+
Changelog = "https://github.com/AbdullahBakir97/DevTrust/blob/main/src/products/03-agent-pr-reviewer/code/CHANGELOG.md"
|
|
57
|
+
Issues = "https://github.com/AbdullahBakir97/DevTrust/issues"
|
|
58
|
+
|
|
59
|
+
[tool.hatch.build.targets.wheel]
|
|
60
|
+
packages = ["src/apr"]
|
|
61
|
+
|
|
62
|
+
[tool.uv.sources]
|
|
63
|
+
devtrust-repox = { workspace = true }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Agent-PR Reviewer (`apr`) - the Wave 2 lead bet of the DevTrust platform.
|
|
2
|
+
|
|
3
|
+
Consolidates the patterns from three existing GitHub Apps into one
|
|
4
|
+
deterministic, fast, AI-pattern-aware PR reviewer:
|
|
5
|
+
|
|
6
|
+
- ai-quality-gate -> AI-likelihood + verbose-pattern detection
|
|
7
|
+
- pr-coach -> coaching feedback (description quality, TODOs)
|
|
8
|
+
- commit-craft -> commit-message review and normalization
|
|
9
|
+
|
|
10
|
+
v0.0.1 ships the deterministic rule layer; LLM-backed review is layered
|
|
11
|
+
on top in v0.1+ once the rule output is stable enough to grade against.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
__version__ = "0.2.0"
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Agent-PR Reviewer command-line interface.
|
|
2
|
+
|
|
3
|
+
apr version
|
|
4
|
+
apr review [--repo PATH] [--changed FILE ...] [--title S] [--description S]
|
|
5
|
+
[--diff PATH] [--enable-ai] [--ai-provider null|anthropic]
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Annotated
|
|
13
|
+
|
|
14
|
+
import typer
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
from rich.table import Table
|
|
17
|
+
|
|
18
|
+
from apr import __version__
|
|
19
|
+
from apr.engine import review as review_engine
|
|
20
|
+
from apr.llm import LLMProvider, build_provider
|
|
21
|
+
from apr.output import write_json, write_markdown
|
|
22
|
+
|
|
23
|
+
app = typer.Typer(
|
|
24
|
+
name="apr",
|
|
25
|
+
help="Agent-PR Reviewer - deterministic + AI-pattern review for PRs.",
|
|
26
|
+
no_args_is_help=True,
|
|
27
|
+
add_completion=False,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
console = Console()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@app.command()
|
|
34
|
+
def version() -> None:
|
|
35
|
+
"""Print the installed Agent-PR Reviewer version."""
|
|
36
|
+
console.print(f"apr [bold]v{__version__}[/bold]")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@app.command()
|
|
40
|
+
def review(
|
|
41
|
+
repo: Annotated[
|
|
42
|
+
Path,
|
|
43
|
+
typer.Option("--repo", "-r", help="Repo to review. Defaults to current directory."),
|
|
44
|
+
] = Path("."),
|
|
45
|
+
changed: Annotated[
|
|
46
|
+
list[str] | None,
|
|
47
|
+
typer.Option(
|
|
48
|
+
"--changed",
|
|
49
|
+
"-c",
|
|
50
|
+
help="Path(s) that changed. Repeat for multiple files.",
|
|
51
|
+
),
|
|
52
|
+
] = None,
|
|
53
|
+
title: Annotated[
|
|
54
|
+
str | None,
|
|
55
|
+
typer.Option("--title", "-t", help="The PR title (for metadata checks)."),
|
|
56
|
+
] = None,
|
|
57
|
+
description: Annotated[
|
|
58
|
+
str | None,
|
|
59
|
+
typer.Option("--description", "-d", help="The PR description / body."),
|
|
60
|
+
] = None,
|
|
61
|
+
diff_path: Annotated[
|
|
62
|
+
Path | None,
|
|
63
|
+
typer.Option(
|
|
64
|
+
"--diff",
|
|
65
|
+
help=(
|
|
66
|
+
"Path to a unified-diff file. Required for the "
|
|
67
|
+
"ai-review:diff-comprehension rule when --enable-ai."
|
|
68
|
+
),
|
|
69
|
+
),
|
|
70
|
+
] = None,
|
|
71
|
+
enable_ai: Annotated[
|
|
72
|
+
bool,
|
|
73
|
+
typer.Option(
|
|
74
|
+
"--enable-ai/--no-enable-ai",
|
|
75
|
+
help=(
|
|
76
|
+
"Run the ai-review:* rule pack. Off by default. "
|
|
77
|
+
"ai-review:hallucinated-symbol needs a "
|
|
78
|
+
".repox/architecture.json (run `repox build .` first)."
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
] = False,
|
|
82
|
+
ai_provider: Annotated[
|
|
83
|
+
str,
|
|
84
|
+
typer.Option(
|
|
85
|
+
"--ai-provider",
|
|
86
|
+
help="LLM backend: 'null' (default, no calls) or 'anthropic'.",
|
|
87
|
+
),
|
|
88
|
+
] = "null",
|
|
89
|
+
quiet: Annotated[
|
|
90
|
+
bool,
|
|
91
|
+
typer.Option("--quiet", "-q", help="Suppress non-essential output."),
|
|
92
|
+
] = False,
|
|
93
|
+
) -> None:
|
|
94
|
+
"""Run the review and emit `.apr/review.{json,md}`."""
|
|
95
|
+
if not repo.exists() or not repo.is_dir():
|
|
96
|
+
console.print(f"[red]Error:[/red] not a directory: {repo}")
|
|
97
|
+
raise typer.Exit(code=2)
|
|
98
|
+
|
|
99
|
+
repo = repo.resolve()
|
|
100
|
+
files = list(changed or [])
|
|
101
|
+
|
|
102
|
+
diff_text: str | None = None
|
|
103
|
+
if diff_path is not None:
|
|
104
|
+
try:
|
|
105
|
+
diff_text = diff_path.read_text(encoding="utf-8", errors="replace")
|
|
106
|
+
except OSError as exc:
|
|
107
|
+
console.print(f"[red]Error:[/red] cannot read diff: {exc}")
|
|
108
|
+
raise typer.Exit(code=2) from exc
|
|
109
|
+
|
|
110
|
+
provider: LLMProvider | None = None
|
|
111
|
+
if enable_ai:
|
|
112
|
+
# Anthropic API key from the conventional env var.
|
|
113
|
+
api_key = os.environ.get("ANTHROPIC_API_KEY")
|
|
114
|
+
provider = build_provider(ai_provider, api_key)
|
|
115
|
+
|
|
116
|
+
if not quiet:
|
|
117
|
+
console.print(f"[bold]Reviewing[/bold] {repo}")
|
|
118
|
+
console.print(f"[dim]Changed files:[/dim] {len(files)}")
|
|
119
|
+
if enable_ai:
|
|
120
|
+
console.print(f"[dim]AI rules:[/dim] enabled (provider: {ai_provider})")
|
|
121
|
+
|
|
122
|
+
report = review_engine(
|
|
123
|
+
repo,
|
|
124
|
+
files,
|
|
125
|
+
pr_title=title,
|
|
126
|
+
pr_description=description,
|
|
127
|
+
enable_ai=enable_ai,
|
|
128
|
+
llm_provider=provider,
|
|
129
|
+
diff=diff_text,
|
|
130
|
+
)
|
|
131
|
+
json_path = write_json(report, repo)
|
|
132
|
+
md_path = write_markdown(report, repo)
|
|
133
|
+
|
|
134
|
+
if quiet:
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
s = report.stats
|
|
138
|
+
table = Table(title="\nReview summary", show_header=True, header_style="bold")
|
|
139
|
+
table.add_column("Severity", style="cyan")
|
|
140
|
+
table.add_column("Count", justify="right")
|
|
141
|
+
table.add_row("info", str(s.info))
|
|
142
|
+
table.add_row("warning", str(s.warning))
|
|
143
|
+
table.add_row("error", str(s.error))
|
|
144
|
+
table.add_row("critical", str(s.critical))
|
|
145
|
+
table.add_row("[bold]total[/bold]", f"[bold]{s.total}[/bold]")
|
|
146
|
+
console.print(table)
|
|
147
|
+
if s.blocking > 0:
|
|
148
|
+
console.print(f"[red]Blocking findings:[/red] {s.blocking} (error + critical)")
|
|
149
|
+
console.print(f"\n[green]✓[/green] wrote [bold]{json_path}[/bold]")
|
|
150
|
+
console.print(f"[green]✓[/green] wrote [bold]{md_path}[/bold]")
|