aeview 0.0.1__py3-none-any.whl

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.
aeview/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ try:
4
+ __version__ = version("aeview")
5
+ except PackageNotFoundError:
6
+ __version__ = "0.0.0"
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: deduplication
3
+ description: Judges which findings from different reviews describe the same underlying issue. Precision over recall.
4
+ ---
5
+
6
+ You are given a flat list of findings collected from several independent code reviews.
7
+ Each finding has a stable `id`. Decide which findings describe **the same underlying
8
+ issue** — the same root cause at the same location — and group them.
9
+
10
+ ## Rules
11
+
12
+ - **Precision over recall.** Only group findings you are confident are the same issue.
13
+ When in doubt, leave them separate. A wrongly-merged pair hides a real, distinct
14
+ problem; a wrongly-split pair only shows the user two views of one issue. The second
15
+ mistake is far cheaper.
16
+ - "Same issue" means same root cause and substantially the same location — not merely
17
+ the same file, the same category, or a similar severity.
18
+ - Do not rewrite, re-score, or summarize findings. You only group.
19
+ - A finding that matches nothing else forms a group of one (or is simply omitted from
20
+ the output — aeview treats any unlisted finding as its own group).
21
+
22
+ ## Output
23
+
24
+ Return groups of duplicate ids. For each group, name one `survivor` (the clearest,
25
+ best-located, highest-severity statement of the issue) and list the other ids as
26
+ `duplicates`. Findings not mentioned are kept as-is.
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: default
3
+ description: Adversarial general-purpose code reviewer. The fallback reviewer used when no repo-local reviewer is selected.
4
+ harnesses:
5
+ - { harness: claude-code, model: claude-opus-4-8 }
6
+ ---
7
+
8
+ You are an adversarial code reviewer. Your job is to find the problems in a change
9
+ before it ships — not to praise it. Approve only when you genuinely cannot break it.
10
+
11
+ ## Stance
12
+
13
+ - Assume the change is wrong until the diff proves otherwise. Confidence is earned.
14
+ - Hunt for the failure that ships to production: the unhandled input, the race, the
15
+ off-by-one, the silently-swallowed error, the security hole, the missing test.
16
+ - Prefer a small number of high-signal findings over a long list of nitpicks. Every
17
+ finding must be something a maintainer would want to fix before merge.
18
+
19
+ ## Attack surface
20
+
21
+ Work through, in order, and stop chasing anything the diff does not touch:
22
+
23
+ 1. **Correctness** — logic errors, wrong conditions, boundary/empty/null cases,
24
+ incorrect assumptions about inputs or return values.
25
+ 2. **Security** — injection, auth/authorization gaps, unsafe deserialization, secrets,
26
+ path traversal, unvalidated external input crossing a trust boundary.
27
+ 3. **Regression** — behavior this change breaks for existing callers or data.
28
+ 4. **Resource & concurrency** — leaks, unbounded growth, deadlocks, races, ordering.
29
+ 5. **Test gaps** — new logic with no test, or a bug a reasonable test would have caught.
30
+ 6. **Maintainability** — only when it rises to a real future-bug risk, not style.
31
+
32
+ ## Calibration
33
+
34
+ - `critical`: data loss, security breach, crash on a common path, corruption.
35
+ - `high`: wrong result or broken behavior on a realistic path.
36
+ - `medium`: edge-case bug, missing test for risky logic, latent foot-gun.
37
+ - `low`: minor correctness or clarity issue worth fixing but not blocking.
38
+
39
+ Set `confidence` honestly: 1.0 means you can point at the exact line and explain the
40
+ failing input; lower it when you are inferring.
41
+
42
+ ## Grounding
43
+
44
+ - Every finding cites a real file and line range from the change under review.
45
+ - `recommendation` is a concrete, specific fix — not "consider reviewing this".
46
+ - Do not invent code that is not in the diff. If you need surrounding context, read it.
47
+
48
+ ## Verdict
49
+
50
+ - `needs-attention` if there is at least one finding a maintainer should act on.
51
+ - `approve` only when the change is correct, safe, and adequately tested.
@@ -0,0 +1,8 @@
1
+ {
2
+ "fallbackReviewerHarnesses": [
3
+ { "harness": "claude-code", "model": "claude-opus-4-8" }
4
+ ],
5
+ "deduplicationHarness": { "harness": "claude-code", "model": "claude-opus-4-8" },
6
+ "retention": { "keepLast": 20, "ttlDays": 14 },
7
+ "reviewTimeoutSeconds": 1200
8
+ }
aeview/activate.py ADDED
@@ -0,0 +1,58 @@
1
+ """Auto mode: pick reviewers whose `auto-activate-paths` match the changed files.
2
+
3
+ A bare `aeview run` (no --reviewers) runs the `default` reviewer plus any reviewer whose
4
+ frontmatter `auto-activate-paths` globs match a file in the (already .aeviewignore-filtered)
5
+ diff. Matching is literal-glob via `PurePath.full_match`, NOT the gitignore engine `.aeviewignore`
6
+ uses: a single `*` does not cross a `/`, `**` crosses directory boundaries, there is no negation,
7
+ and a bare directory name matches nothing (write `dir/**`). Case-sensitive, so a reviewer
8
+ activates identically on macOS and Linux rather than following the platform default.
9
+
10
+ Each reviewer is anchored at its scope path (the parent of its `.aeview` dir): a changed file is
11
+ considered only if it lives under that path, and is matched relative to it. There is no clamp — a
12
+ home reviewer (`~/.aeview/reviewers/...`) anchored at `~` only reaches repos under home, and needs
13
+ `**/...` globs to match inside them. Discovery is upward-only (cwd->home), so a reviewer in a
14
+ subdirectory below cwd is never consulted.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from pathlib import Path
20
+
21
+ from .ignore import changed_paths
22
+ from .resolve import (
23
+ REVIEWER_FILE,
24
+ ResolveError,
25
+ discover_reviewer_sources,
26
+ parse_reviewer,
27
+ reviewer_scope_path,
28
+ )
29
+
30
+
31
+ def select_auto_reviewers(cwd: Path, root: Path, diff: str) -> list[str]:
32
+ """Reviewer names auto-activated by the diff, in nearest-first discovery order. `root` is the
33
+ repo top-level that makes the repo-root-relative diff paths absolute; `diff` is already
34
+ .aeviewignore-filtered, so an ignored file never triggers activation. `default` runs
35
+ unconditionally and is added by the caller, not here."""
36
+ abs_changed = [root / rel for rel in changed_paths(diff)]
37
+ activated: list[str] = []
38
+ for disc in discover_reviewer_sources(cwd):
39
+ try:
40
+ front, _ = parse_reviewer(disc.source / REVIEWER_FILE)
41
+ except ResolveError:
42
+ continue # unparseable frontmatter can't declare globs, so it never auto-activates
43
+ globs = front.auto_activate_paths
44
+ if globs and _any_match(abs_changed, reviewer_scope_path(disc.source), globs):
45
+ activated.append(disc.name)
46
+ return activated
47
+
48
+
49
+ def _any_match(abs_paths: list[Path], scope_path: Path, globs: list[str]) -> bool:
50
+ """True if any changed file under `scope_path` full-matches any glob (relative to scope_path).
51
+ Literal-glob via PurePath.full_match: `*` stops at `/`, `**` crosses, case-sensitive."""
52
+ for abs_path in abs_paths:
53
+ if not abs_path.is_relative_to(scope_path):
54
+ continue # outside this reviewer's tree -> never its concern (no clamp the other way)
55
+ rel = abs_path.relative_to(scope_path)
56
+ if any(rel.full_match(glob, case_sensitive=True) for glob in globs):
57
+ return True
58
+ return False
aeview/bundle.py ADDED
@@ -0,0 +1,61 @@
1
+ """Adaptive bundling: measure the diff, then inline or self-collect.
2
+
3
+ - diff <= 256 KB -> inline: the frozen diff is embedded in the prompt, so every
4
+ review of the same change sees byte-identical input.
5
+ - diff > 256 KB -> self-collect: the prompt carries a stat summary + the path to
6
+ the frozen diff (which the harness reads selectively) and the
7
+ read-only git command to inspect the live tree — keeping the
8
+ prompt small on huge changes.
9
+ - patch scope -> always inline (there is no git to self-collect from).
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from dataclasses import dataclass, field
15
+
16
+ from .schema import ScopeSpec
17
+ from .scope import ResolvedScope
18
+
19
+ INLINE_MAX_BYTES = 256 * 1024
20
+
21
+
22
+ @dataclass(slots=True)
23
+ class Bundle:
24
+ mode: str # "inline" | "self-collect"
25
+ scope: ScopeSpec
26
+ diff: str
27
+ summary: str
28
+ inspect: list[str] = field(default_factory=list)
29
+ commits: str = ""
30
+ diff_bytes: int = 0
31
+
32
+ @property
33
+ def is_empty(self) -> bool:
34
+ return not self.diff.strip()
35
+
36
+ @property
37
+ def is_inline(self) -> bool:
38
+ return self.mode == "inline"
39
+
40
+ def manifest(self) -> dict:
41
+ return {
42
+ "mode": self.mode,
43
+ "scope": self.scope.model_dump(),
44
+ "diff_bytes": self.diff_bytes,
45
+ "summary": self.summary,
46
+ "inspect": self.inspect,
47
+ }
48
+
49
+
50
+ def build_bundle(resolved: ResolvedScope) -> Bundle:
51
+ diff_bytes = len(resolved.diff.encode("utf-8"))
52
+ inline = resolved.inline_only or diff_bytes <= INLINE_MAX_BYTES
53
+ return Bundle(
54
+ mode="inline" if inline else "self-collect",
55
+ scope=resolved.spec,
56
+ diff=resolved.diff,
57
+ summary=resolved.summary,
58
+ inspect=resolved.inspect,
59
+ commits=resolved.commits,
60
+ diff_bytes=diff_bytes,
61
+ )