Gate A+ polish — ergonomics & UX (PR A)#23
Conversation
Four small UX / ergonomics fixes pushing Gate from A- to A. Groundwork for PR B (state snapshots + external check-run integration) but reviewable and mergeable in isolation. A.1 — canonical Finding dataclass --------------------------------- - `gate/schemas.py`: add `Finding` + `FindingLocation` dataclasses. Required: severity/file/message. Optional: line/column/title/ rule_source/suggestion/category/source_stage/introduced_by_pr/ evidence_level/finding_id/locations. `Finding.from_dict` validates required fields, coerces stringy ints, preserves unknown keys in `extra` so future fields don't get silently dropped. - `gate/github.py` `_format_findings`: route every raw dict through `Finding.from_dict`; malformed findings (missing severity/file/ message) surface in a dedicated "Malformed findings" section instead of silently dropping. - `gate/fixer_polish.py`: multi-location findings now include a loc_hint in the single-finding prompt so the senior agent fixes every site in one bootstrap. - `gate/cli.py`: new `gate inspect-pr <N> [--repo] [--stage] [--raw]` subcommand that pretty-prints persisted review state with rich.Table (or raw JSON). Reach-for-this during post-mortems instead of ad-hoc `python -c 'json.load(...)'`. - `docs/finding-schema.md`: documents the canonical shape and the per-stage emitter checklist. A.2 — finding dedup ------------------- - `gate/extract.py`: new `_normalise_dedup_message` (strips coord- like integers, truncates to 80 chars) and `_dedupe_findings`, keyed on `(source_stage, rule_source-or-category, normalised_message)`. Collapses duplicates into a single finding with a `locations[]` array sorted by `(file, line)`; keeps top-level file/line as the primary location so every existing consumer keeps working. Worst severity wins (critical > error > warning > info). Single- occurrence findings also gain a `locations` array for uniform downstream handling. - `gate/orchestrator.py`: dedup runs after verdict but BEFORE `compute_finding_id` stamping so the existing hash scheme stays stable across reviews (hash input unchanged). A.3 — dev-install guard ----------------------- - `tests/conftest.py`: module-level try-import of `pytest_asyncio` that raises a clear, actionable error at collection time (`install with pip install -e '.[dev]'`) instead of producing 25 opaque async-test failures when the dev extras aren't installed. - `README.md`: new Development section documents the canonical dev install (`pip install -e '.[dev]'`) and typical workflow commands. A.4 — `gate status` UX ---------------------- - `gate/cli.py` `cmd_status`: call `run_health_check()` in-process (same path as `cmd_health`) instead of `get_health(socket_path)`. Root cause: nothing ever pushes health into the server so the cache was permanently empty. Always print a Health section now; latched `quota_auth` surfaces as a dedicated loud ⛔ top-line alert because silent quota/auth drift is the failure mode that hurts most. - `gate/cli.py` `cmd_health`: new `--since-restart` flag reads the `auth_drift_alert_latched_at` marker mtime and reports how long the drift has been unresolved. Tests ----- - `tests/test_schemas.py`: `Finding.from_dict` required-field validation, type coercion, extra-key preservation, locations parsing, primary_location/iter_locations, introduced_by_pr coercion, severity ordering. - `tests/test_extract.py`: `_normalise_dedup_message` coord-strip and truncation; `_dedupe_findings` collapse on same rule+msg + different files, non-collapse on different rules or messages, worst-severity selection, single-occurrence locations handling, non-dict passthrough. - `tests/test_github.py`: `_format_findings` renders "Also at:" list for multi-location findings and a Malformed findings section for invalid entries. - `tests/test_cli.py`: `TestInspectPrCommand` (raw JSON + rich-table output), `TestStatusCommand` (loud auth-drift warning path), `TestHealthSinceRestart` (elapsed marker reporting). Validation ---------- - `pytest -q`: 1031 passed, 6 skipped - `ruff check .`: clean - No schema breaking changes; every existing finding consumer keeps working on today's state (fallback paths preserved). Made-with: Cursor
Gate Review ✅Approved with notes — Build is clean (1031/1037 tests pass, 6 skipped, lint/typecheck pass). Security stage found no new vulnerabilities. All nine verifiable postconditions pass. The single actionable warning introduced by this PR is in the new Warnings
Notes
Build Results
1 warning, 4 notes across 6 stages (991s, confidence: high) |
cmd_inspect_pr previously called get_pr_state_dir(), which mkdirs the per-PR state directory before returning. That made the `No persisted state` guard unreachable and silently created a ghost state/prN/ on every `gate inspect-pr` for a PR Gate never processed. Extend get_pr_state_dir with an optional `create: bool = True` parameter (mkdir is now gated on the flag) so read-only callers like inspect-pr can obtain the path without side-effects. All existing callers keep the default and are unchanged. cmd_inspect_pr now calls get_pr_state_dir(pr, repo, create=False) and requires verdict.json to exist before rendering — so the guard actually fires for unknown PRs without touching disk, while state.get_pr_state_dir remains the single source of truth for PR state paths (no layer bypass). Also tighten test_missing_pr_returns_1 from `result in (0, 1)` to `result == 1` plus a `No persisted state` output assertion, now that the behavior is deterministic.
Gate Auto-Fix AppliedFixed 1/1 findings in 2 iteration(s) (8d13173). Fixed:
|
|
Gate (error) — review failed: Command 'gh' returned non-zero exit status 1.. Auto-approving. |
Gate's fix-senior auto-fix in 8d13173 accidentally committed two scratch log files (resume-stdout.log, resume-stderr.log) that Codex resume-mode bootstraps write alongside their temp state. These are per-machine build artifacts and have no business in the repo. - Remove both files from the tree. - Add the pattern to .gitignore so the next auto-fix iteration does not re-stage them. (Follow-up: the stray-commit behaviour is a Gate fix-senior issue — worktree hygiene should filter these before `git add -A`. Tracked as a PR B candidate; not blocking this PR.) Made-with: Cursor
Gate Review ✅Approved — Re-review after fix commit 8d13173. The sole prior warning (fdc4f13fb3 — ghost-dir guard unreachable in cmd_inspect_pr) is resolved: get_pr_state_dir now takes create=False, confirmed by a discriminating mutation-checked test. All 1031 tests pass, lint and typecheck are clean. The fix introduced a minor UX trade-off (the added verdict.json guard causes mid-review crash state to print "No persisted state" rather than a more informative message), but this is explicitly an info-level operator ergonomics issue, not a correctness bug, and does not block graduation. All remaining findings are info-level. Per re-review graduation rules, with all prior warnings resolved and only info findings remaining, this PR is approved. Notes
Resolved since last review
Build Results
6 notes across 6 stages (396s, confidence: high) |
Summary
Four small UX / ergonomics fixes pushing Gate from A- to A (first half
of the Gate A+ polish plan
— state-history + external-check integration land in PR B). Reviewable
and mergeable in isolation.
A.1 — Canonical
Findingdataclassgate.schemas.Finding+FindingLocationformalise the review-finding contract with
severity/file/messagerequired andeverything else optional.
Finding.from_dictvalidates requiredfields, coerces stringy integers, and preserves unknown keys in an
extrabucket so future fields don't get silently dropped.github._format_findingsandfixer_polishroute throughFinding.from_dict; malformed findings surface in a dedicated"Malformed findings" section instead of being silently dropped.
gate inspect-pr <N> [--repo] [--stage] [--raw]CLI forpost-mortem inspection of persisted state (rich.Table + JSON modes).
docs/finding-schema.mddocuments the canonical shape andper-stage emitter checklist.
A.2 — Finding dedup
extract._dedupe_findingscollapses findings with the same(source_stage, rule_source-or-category, normalised_message)intoone finding with a
locations[]array. Polish loop now runs onceper finding class instead of once per site.
compute_finding_idstamping in theorchestrator so the existing hash scheme stays stable — no
cross-review ID churn.
(file, line)sort.A.3 — Dev-install guard
tests/conftest.pymodule-level try-import ofpytest_asynciothat fails loudly at collection time with the correct install
command (
pip install -e '.[dev]') instead of 25 opaque async-test failures.
A.4 —
gate statusUXcache was permanently empty.
cmd_statusnow callsrun_health_check()in-process (same path ascmd_health) andalways prints a Health section.
quota_authdrift surfaces as a dedicated loud ⛔top-line alert — silent quota/auth drift is the failure mode
that hurts most.
gate health --since-restartflag reports how long thedrift has been unresolved (reads the existing
auth_drift_alert_latched_atmarker).Test plan
pytest -q→ 1031 passed, 6 skippedruff check .→ cleanpip install -e .(no extras) thenpytest --collect-onlyshows the conftest guard error with the right install command
gate inspect-pr 22round-trips persisted state and showslegible multi-field output
gate statusprints the currently-latchedquota_authwarning without needing
gate healthfiles collapse; diff rule same msg don't)
approveon post-fixre-review (the first review may surface findings since dedup
changes what the verdict agent sees — acceptable and expected)
Notes for review
working on today's state (fallback paths preserved).
finding_idhash input is deliberately unchanged — dedup runsbefore stamping so primary-location-based hashes stay stable
across reviews. Edge case: when a fix deletes the primary site but
leaves others, next review picks a new primary and the ID changes
(treated as new+resolved). Documented trade-off in the plan.
follow-up. The direct
run_health_check()call is sufficient forthe CLI symptom today.
Made with Cursor