Skip to content

Develop - #1

Merged
ShenethFd merged 16 commits into
mainfrom
develop
Sep 23, 2026
Merged

ShenethFd merged 16 commits into
mainfrom
develop

Conversation

@ShenethFd

Copy link
Copy Markdown
Member

No description provided.

ShenethFd and others added 16 commits August 9, 2026 11:57
…ne, results API, and Postgres persistence

Ports the analysis pipeline from an in-progress working state that was
previously uncommitted: real analyzer implementations (Strategy pattern),
finding normalization/fingerprinting, secure workspace preparation
(allowlisted hosts, validated commit/branch, no shell=True), a read-only
results API, and the Postgres schema + repository backing it.
Removes the Pylint/Radon/Cppcheck analyzers and the Reviewdog aggregation
layer entirely, and replaces them with a purpose-built ESLint (JS/TS)
analyzer that parses ESLint's own --format json output directly.

Adds a new metrics/ package that computes AnalysisMetrics (issue density,
cyclomatic complexity, cognitive complexity via eslint-plugin-sonarjs,
code size, unused code) plus per-rule and per-file statistics from real
ESLint violation output and an independent workspace LOC scan — stored
alongside findings on AnalysisResult (Postgres JSONB, migration-safe) so
main-backend/web-interface can display them without recalculating
anything themselves.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extends the engine beyond JS/TS with a PythonAnalyzer composite that runs
all three tools concurrently behind the existing Analyzer interface, so
AnalyzerFactory and AnalysisOrchestrator treat Python exactly like ESLint
and neither needed to change.

Each tool's own exit-code convention is handled explicitly rather than
assumed — Pylint's bitmask (only bit 32 means the tool itself failed),
Bandit's exit 1 for "issues found", and Radon's always-0 with per-file
errors embedded in its JSON. All three were verified against the pinned
versions rather than taken from documentation. A missing binary, a
timeout, or malformed output is reported as that tool's own
AnalyzerRunStatus, so one failing tool never discards the other two's
results; only an all-three failure fails the job.

Metrics (issue density, cyclomatic complexity, maintainability index,
Halstead, security counts) are computed once in metrics/python/ and
persisted as JSONB. LOC comes from Radon's own raw output rather than a
second line counter.

Adds a fixture Python repository and 61 tests covering output parsing,
severity/category mapping, metric aggregation, failure isolation and the
Postgres round-trip, plus a Dockerfile so both toolchains run
reproducibly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
First step toward review that can reason about a change in context. Every
analyzer currently sees one file at a time, so nothing can answer "who
called the function this PR just deleted?" — the question most real review
comments depend on.

CodeIndexer walks a checked-out workspace and builds a symbol/call graph:
classes, functions and methods with their line spans, plus calls, imports
and contains edges. symbols_touching_lines() is the bridge from a diff to
that graph, and callers_of()/neighborhood() give the blast radius of a
change.

Deliberately AST-derived rather than model-extracted. On this service's
own source that is 159 symbols and 937 edges across 50 files in 0.03s,
with no tokens spent and no run-to-run variation — an LLM-built graph
would cost money per pull request and can assert edges that don't exist.

Call resolution is by unique name only, which resolves 32% of call edges
here. The remainder is almost entirely stdlib (len, sum, Path). Where a
name genuinely is ambiguous — _relative_path, defined in three analyzer
classes — it stays unresolved rather than guessing: a missing edge makes
a reviewer say less, a wrong one makes it say something false.

Languages are data (Python, JS, TS, TSX), so adding one is a table entry
rather than a new code path, matching how AnalyzerFactory handles tools.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- New diffing/ package: fetches the target branch, finds the merge base
  (deepening 50 -> 250 -> 1000 commits if needed), and diffs against it,
  so commits merged into the target branch since aren't counted as the
  PR's work. numstat -z lists files; --unified=0 gives exact changed
  lines. Lockfiles, build output and minified bundles are left out.
- Every finding gets metadata.on_changed_line; changed lines are mapped
  to the innermost function/method/class, with its known caller count.
- Runs alongside the analyzers and never fails the job: problems end as
  changes.status = "unavailable" with a reason.
- AnalysisJob accepts optional targetBranch/title/description;
  AnalysisResult.changes is stored in a new pull_request_changes column.
- docs/agent-architecture.md: the agent design, now with OpenAI as the
  provider and cost-first model defaults.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- retrieval/: eight read-only, size-limited tools for agents (read_file,
  get_symbol_source, find_symbol, callers_of, callees_of, list_tests_for,
  search_code, get_linter_findings), with OpenAI strict-mode tool specs
  and an executor that turns every error into text the model can fix.
- Safety: paths are resolved (following symlinks) and confined to the
  checkout, .git/ is refused, likely credentials are redacted from
  everything an agent reads, and search is literal-only via git grep.
- The indexer no longer follows symlinks, which could have pulled files
  from elsewhere on the host into the index.
- context/: the bounded bundle the Reviewer starts from — PR intent,
  diff, full source of edited (not newly added) functions, callers,
  findings on changed lines, repo map — each section budgeted and
  announcing what it cut.
- Findings are referenced by 10-char fingerprint prefixes to save tokens.
- scripts/preview_context_pack.py shows a real PR's pack and its cost
  without calling any model; OpenAI prices live in agents/runtime/pricing.py.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- agents/runtime: provider-neutral LLMProvider, an OpenAI Responses API
  adapter (stateless store=False, encrypted reasoning passed back, strict
  tools, stable prefix for prompt caching), and an agent loop with hard
  limits on rounds, output tokens, time and dollars. Near any limit the
  model is forced (tool_choice) to submit what it has.
- agents/reviewer: instructions, a strict ReviewerOutput schema delivered
  via a submit_review tool call, and validation that drops any claim whose
  quote isn't at the cited location, whose file/line doesn't exist, or
  that cites a non-existent linter finding. Dropped items are recorded.
- review/: decides whether a PR is reviewed, runs the Reviewer, ranks and
  caps findings at 7. Never raises; failures are recorded with their cost.
- The orchestrator now keeps the checkout and index alive for the review
  and hands the linter result to the caller before the review starts; the
  consumer saves it first, then the review (agent_reviews table, upserted).
- Pricing includes GPT-5.6 cache-write charges; finding refs are accepted
  with display brackets (found in the first real review).
- scripts/review_pr.py runs a real review and prints its exact cost.
- Tests run against a scripted fake provider: no API cost.

First real review with gpt-5.6-luna: $0.00073 with a warm prompt cache.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- evaluation/: 18 PRs (12 with a planted bug linters can't see, 6 clean
  including two decoys), each built as a real git repository and run
  through the real pipeline; only the clone is replaced.
- Scoring: recall, precision, false alarms per clean PR, p95 duration,
  cost per PR, against the targets in the architecture doc. Defects are
  located by snippet, with alternative locations (e.g. a changed
  signature or the caller it breaks).
- --dry-run exercises everything except the model, for free; real runs
  stop starting cases once --max-cost is reached. Results are saved to
  evaluation-results/ (git-ignored) and compared with the previous run.

Baseline on gpt-5.6-luna: 12/12 found, every reported issue real, no
false alarms on clean PRs, $0.0006 per PR. Two "clean" cases were
initially mislabelled; the Reviewer's reports on them were correct, so
the cases were fixed. The set is still too easy; harder cases next.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…se 4)

- 10 harder cases: bugs among legitimate multi-file changes, two bugs in
  one PR, a changed contract breaking untouched callers, idempotency, JWT
  verification, timezones, a threshold on the wrong total, a default
  flipped inside a large refactor, and larger clean PRs.
- Verifier (Agent 2): one fresh-context call per reported issue that
  tries to refute it. Its verdict is applied under rules in code: only an
  explicit "drop" removes an issue, a drop citing code that isn't there
  is rejected, severity can only go down, an unconfirmable issue is
  dropped but a provider outage keeps issues (unverified).
- The first Verifier version cut recall from 0.95 to 0.71: it labelled
  "was this intended? - no" as refuting, and any refuting check could
  override a keep. Checks are now an explicit refutes_issue boolean.
- Harness: drop reasons recorded per case, runs compared only against the
  same case set, --no-verifier flag.

28 PRs on gpt-5.6-luna with the Verifier: recall 0.95, precision 0.95,
0.12 false alarms per clean PR, $0.0016 per PR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…re, phase 5)

- Rules come from .codepulse/rules.yml read from the PR's target branch
  (a PR can't weaken the rules it's reviewed against), plus rules stored
  per repository (business_rules table): added in the dashboard, or
  suggested by the Rule Miner and accepted by a person. One malformed rule
  is reported without disabling the rest; yaml.safe_load only.
- The context pack lists applicable rules; a get_rule tool. The Reviewer
  records a verdict per rule, and rule violations must cite a real rule
  id plus code evidence. A rule is "violated" only when an issue citing it
  survives the evidence checks and the Verifier; a rule's severity caps
  the issue's.
- Rule Miner (Agent 3): suggests rules from a repository's docs, tests and
  constraint statements. Every suggestion's quoted source is verified,
  scopes must match real files, ids must be new; stored as "suggested".
- Rules API: list, add, edit/accept/reject, delete, and background mining.
- Files that hold credentials (.env, keys, credential files) are never
  shown to any agent: refused by read_file, filtered from search, left out
  of diffs and mining. Redaction alone doesn't catch plain values.
- Fixed: applies_to cleanup used lstrip("./"), silently turning
  "../secrets/**" into "secrets/**" instead of rejecting it.
- Fixed: a malformed answer on an agent's last round now gets its repair.
- Evaluation: 7 business-rule cases. 35 PRs: 25/25 found, $0.0014 per PR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Private repositories:
- A clone token per job from integration-service's internal API, passed
  to git only through GIT_CONFIG_COUNT/KEY/VALUE as an extraheader scoped
  to the provider's host: never on the command line (ps), never in
  .git/config, never in os.environ (linters and agent tools can't inherit
  it), never logged. Used for the clone, the target-branch fetch and the
  Rule Miner's ls-remote. GIT_TERMINAL_PROMPT=0.
- No INTERNAL_SERVICE_TOKEN, an unconnected repository, or an unreachable
  integration-service all mean an anonymous clone.
- Tested against a local fake git host: header sent to the right host,
  not to another, and absent from .git/ and errors.

Phase 6:
- PUT .../review/findings/{fingerprint}/feedback: useful / not_useful /
  wrong, one verdict per user (X-User-Id, set by main-backend from the
  verified session). Feedback is attached when a review is read.
- An issue a developer marked wrong isn't reported again on later
  reviews (dropped with stage "feedback").
- GET .../review-usage?days=30: reviews, cost, issues, verdicts and the
  real-world false-alarm rate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The evaluation harness printed its results and forgot them, so comparing a
prompt change against last week's numbers meant scrolling back through a
terminal. --save writes each case's result and review to the database,
where they can be queried and compared.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Runs on every branch and pull request: install, then the whole suite,
then build the image.

Two things the workflow has to supply that a developer's machine gets from
.env, which is not in the repository:

  * The database settings. Settings deliberately has no defaults for them
    — the service refuses to start half-configured — and that check runs at
    import time, so the suite needs them even though most tests open no
    connection. A throwaway Postgres service container covers the tests
    marked `integration`, which store a real result through the real schema
    because the JSONB columns are exactly where a silent coercion would
    hide.
  * ESLint's own toolchain, installed from tools/eslint, since part of the
    suite runs the real linter rather than a mock.

No OPENAI_API_KEY is set, deliberately: the agent tests stub the provider,
so a test that ever reached the real API will fail here rather than quietly
spend money on every push.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The dependency layer copied pyproject.toml on its own and then ran an
editable install, which setuptools cannot resolve: packages.find looks in
src/, and at that point in the build there is no src/ at all. Every build
of this image has failed there — "Getting requirements to build editable
did not run successfully" — which CI made visible the moment it started
building images.

A placeholder package satisfies the resolution; COPY . . replaces it with
the real source that the editable install already points to, so the
dependency layer still only rebuilds when dependencies change.

Verified in the built image: the app imports and ESLint resolves to
/app/tools/eslint/node_modules/.bin/eslint.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Runs only after CI passes on main, so a red build cannot reach the running
platform. It builds the image, pushes it to ECR, and asks the application
host to pull and restart just this service.

No AWS access key is stored anywhere. GitHub signs a short-lived OIDC
token, and the role that trusts it is restricted to this organization's
repositories on main — a pull request from a fork cannot assume it, and
the credentials expire with the job.

The rollout goes through SSM rather than SSH: the host has no SSH port
open and no key pair, so there is no long-lived credential to leak or
lose. It fails the job when the rollout fails, because a broken deploy
that reports green is worse than one that reports red.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ShenethFd
ShenethFd merged commit b7818a8 into main Sep 23, 2026
3 checks passed
ShenethFd added a commit that referenced this pull request Oct 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant