Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
tests/test_spec_drift_eval.bats \
tests/token_report.bats \
tests/reviewer_report.bats \
tests/comment_noise.bats \
tests/auto_rebase_health.bats \
tests/model_pricing.bats \
tests/test_review_cycle.bats \
Expand Down Expand Up @@ -191,7 +192,7 @@
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# NOTE: persist-credentials is intentionally left at the default (true) —

Check warning on line 195 in .github/workflows/lint.yml

View workflow job for this annotation

GitHub Actions / Lint

195:9 [comments-indentation] comment not indented like content
# vci_resolve_reusable needs `git fetch origin` to authenticate against this
# private repo when resolving same-repo channel tags. Setting
# persist-credentials: false would silently push every such caller into the
Expand Down
89 changes: 89 additions & 0 deletions docs/metrics-baseline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Success Metrics — Definitions & Pre-Rollout Baseline

This document is the falsifiability contract for epic
[#1402](https://github.com/petry-projects/.github-private/issues/1402). It fixes the **three
success metrics** — convergence, redundancy, and noise — with unambiguous definitions, and
records a **dated pre-rollout baseline** so the epic's claims can be checked with before-and-after
evidence rather than asserted.

It gates the timer/behaviour changes in
[#1407](https://github.com/petry-projects/.github-private/issues/1407) and
[#1408](https://github.com/petry-projects/.github-private/issues/1408): the baseline below must be
captured **before** those changes alter the behaviour being measured, or the "before" is
contaminated.

## The substantive vs. trivial-stub-sync split

The PR population is **bimodal** and averaging across it hides the effect this initiative targets:

- **Substantive PRs** — a PR that changes non-generated source: any file **outside** the verbatim
stub/baseline set (`.github/workflows/*.yml` caller stubs, `repo-template` seeds, generated
READMEs) **and** whose net diff is non-empty. These are the PRs the convergence/redundancy claims
are about.
- **Trivial stub-sync PRs** — verbatim propagation of a canonical stub/baseline (fleet stub-drift
remediation, template/readme refresh). They merge in minutes yet still draw 5–8 bot comments, so
they inflate the comment counts while telling us nothing about review convergence.

**Every metric below is reported for the substantive population.** Where a figure is stated for all
PRs it is labelled *(all PRs)*.

---

## 1. Convergence — cycles-to-merge and time-to-converge

| Field | Definition |
|---|---|
| **What is counted** | Two numbers per merged PR: **cycles-to-merge** = the number of automated review/fix cycles (commits that triggered a re-review) between open and merge; **time-to-converge** = wall-clock from PR creation to merge. |
| **Window** | Rolling `LOOKBACK_DAYS` (report default 7 for the after-run; the baseline uses the pre-change window stated below). |
| **PR scope** | Merged **substantive** PRs. Trivial stub-sync PRs are reported separately and never averaged in. |
| **Attribution** | Per PR, from GitHub's own commit/merge timestamps and the pr-review re-review events. Latency percentiles for the review workflow itself are surfaced by `scripts/pr_review_health.sh` ("Convergence latency" — p50/p95 of run duration). |

## 2. Redundancy — workflow runs per hour and runs per PR

| Field | Definition |
|---|---|
| **What is counted** | **Runs per hour** = review-related workflow runs ÷ hours in the window; **runs per PR** = review-related workflow runs ÷ distinct PRs touched in the window. |
| **Window** | Same rolling window. Runs-per-hour is normalised by the window length so baseline and after are comparable regardless of window size. |
| **PR scope** | Runs-per-PR is measured over all PRs (redundancy is a fan-out property, not a substance property), with the substantive subset reported alongside. |
| **Attribution** | Reuses the existing review-event accounting: `reviewer_report.sh` counts reviews as **events** ("a PR re-reviewed across N commits contributes N reviews") and reports **coverage overlap** (PRs reviewed by ≥2 bots) — the closest existing redundancy signal. Cost fan-out reuses `scripts/token_report.sh`'s "Most expensive PRs (top 10)" pipeline; no parallel accounting is invented. |

## 3. Noise — no-action agent comments per PR

| Field | Definition |
|---|---|
| **What is counted** | **No-action agent comments** as a count, as a **share** of all agent comments, and **per PR**. An *agent comment* is any comment/review carrying one of our automation markers; a *no-action* comment asks nothing of a human. Full definition and the marker/body list live in [`docs/reviewer-report.md` → Agent comment noise](./reviewer-report.md#agent-comment-noise-our-own-automation). |
| **Window** | Same rolling window and the same all-non-archived-repo sweep as the reviewer scorecard. |
| **PR scope** | Every PR active in the window (attribution is by marker, not author). |
| **Implementation** | Net-new — it did **not** exist before #1411. Pure classifier `scripts/lib/comment-noise.sh`, unit-tested in `tests/comment_noise.bats`, applied inside `reviewer_report.sh`'s existing collection pass so baseline and after share one code path. |

---

## Pre-rollout baseline snapshot

- **Captured (dated):** 2026-08-02
- **Pre-change window:** the 7-day rolling window ending on the capture date, over all non-archived
`petry-projects` repos.
- **Sample:** the known-working figures below are the measured pre-change references cited on epic
#1402; they are reproduced (or corrected, with the discrepancy explained) by the same code path
that produces every after-run — `scripts/reviewer_report.sh` (noise, redundancy overlap),
`scripts/pr_review_health.sh` (convergence latency), and `scripts/token_report.sh` (cost fan-out).

| Metric | Baseline (substantive) | Baseline (all PRs) | Source of the after-measurement |
|---|---|---:|---|
| Convergence — time-to-converge | **~20–48 h** | — | `pr_review_health.sh` "Convergence latency" (p50/p95) |
| Convergence — cycles-to-merge | **~7–13 commits** | trivial stub-sync merges in minutes | pr-review re-review events |
| Redundancy — workflow runs/hr | — | **~52 runs/hr** | `pr_review_health.sh` run telemetry / `reviewer_report.sh` |
| Redundancy — coverage overlap | — | PRs reviewed by ≥2 bots (see scorecard) | `reviewer_report.sh` "Coverage overlap" |
| Noise — no-action comment share | — | **~12%** of all agent comments | `reviewer_report.sh` "Agent comment noise" |

Comment thread
coderabbitai[bot] marked this conversation as resolved.
### Reconciliation notes

- **Convergence** and **redundancy** baselines are quoted as ranges because the pre-change data is
bimodal (see the split above); the substantive band (~20–48 h, ~7–13 commits) is the target of the
initiative, while trivial stub-sync PRs sit far below it and are reported, not averaged in.
- **Noise ~12%** is the pre-rollout no-action share. Because the classifier is net-new, this figure
had no prior deterministic source; the first scheduled `reviewer-report.yml` run reproduces it via
`cn_render_noise_section`. Any material divergence from ~12% on that first run is a measurement
correction to record **here** (append a dated row), not a silent overwrite of this baseline.
- **No new scheduled workload:** every after-measurement rides an existing report/cron. This
document is the fixed "before" the timer changes in #1407/#1408 are measured against.
25 changes: 23 additions & 2 deletions docs/reviewer-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,24 @@ Per-PR bucket counts (`reviewed_prs` / `refused_prs` / `no_response_prs`, which
eligible PRs), plus thread-resolution and reaction counts, are still computed and stored in the
snapshot for downstream use — they're just kept out of the headline table to keep it legible.

### Agent comment noise (our own automation)

Distinct from the third-party scorecard above, the report also measures the **no-action share of
our own automation's comments** — the net-new noise metric from [#1411](https://github.com/petry-projects/.github-private/issues/1411).
This is deterministic (`scripts/lib/comment-noise.sh`), computed from the automation markers each
comment already carries — **no LLM**.

| Term | Definition |
|---|---|
| **Agent comment** | A PR comment or review whose body carries one of OUR automation markers: `<!-- pr-review-agent … -->`, `<!-- dev-lead… -->` (fix-reviews / fix-ci / noop-guard / issue / rate-limit), `<!-- persona:… -->`, or `<!-- dependency-advisory -->`. This is the **denominator**. Third-party reviewer bots are excluded (they emit no such marker and are scored in the table above). |
| **No-action comment** | An agent comment that asks nothing of a human. Two signals, both stable: known no-action **bodies** — "No actionable items found." (`post_no_changes`), "Engine ran but made no changes." (fix-ci), "No action required." (dependency advisory all-LOW) — and terminal no-op **marker fields** — `status=no-changes` (dev-lead) and `decision=approved` (a clean / repeat pr-review approval). |
| **Actionable comment** | Every other agent comment — an applied fix (`status=applied`), an escalated finding (`decision=escalated`), or a human-attention flag (no-op guard). |
| **Noise metric** | No-action comments as a **count** and as a **share** of all agent comments, plus **no-action comments per PR**. |

- **Window / scope:** the same rolling `LOOKBACK_DAYS` window and the same all-non-archived-repo sweep as the scorecard; every PR active in the window (agent comments on draft PRs are vanishingly rare and not separately excluded).
- **Attribution:** by marker, not by author — our agents commit/comment as human logins (`don-petry`, `donpetry-bot`), so identity is taken from the marker embedded in the body, never the login.
- **One code path:** the classifier is applied during the report's existing per-PR collection pass, so the pre-rollout **baseline** and every **after** measurement are produced by the same code (see [`docs/metrics-baseline.md`](./metrics-baseline.md)).

### Week-over-week deltas

The report has no external time-series store. Instead each run writes a compact per-bot
Expand All @@ -87,8 +105,10 @@ First-ever runs (or an expired artifact) simply omit the deltas — never an err
- **Cost is not measured.** These reviewers are external SaaS GitHub Apps and emit no
token-usage artifacts, so per-review `$` cost is not observable from our side. For our
own Claude reviewer's spend, see the [Token Cost Observatory](./token-report.md).
- **Comment usefulness / false-positive rate** requires judgment and is intentionally out
of this deterministic report (see roadmap).
- **Comment usefulness / false-positive rate** *for the third-party reviewers* requires judgment
and is intentionally out of this deterministic report (see roadmap). The **Agent comment noise**
metric above is different and deterministic — it scores only OUR automation's no-action share
from the markers each comment already carries.
- **Latency baseline** is PR-creation time in v1; a refinement to last-human-push is
tracked for v2.

Expand Down Expand Up @@ -136,6 +156,7 @@ ORG=petry-projects LOOKBACK_DAYS=7 GH_TOKEN="$(gh auth token)" \
| File | Role |
|---|---|
| `scripts/lib/advisory-review-gate.sh` | Single source of truth for bot logins + rate-limit body patterns (reused, not forked). |
| `scripts/lib/comment-noise.sh` | Pure no-action agent-comment classifier + `cn_render_noise_section` (the noise metric). Unit-tested in `tests/comment_noise.bats`. |
| `scripts/reviewer_report.sh` | Org-wide GraphQL collection (`main` / `collect_org_reviews`) + pure normalization, aggregation, and rendering (`aggregate_snapshot`, `render_reviewer_report`). |
| `tests/reviewer_report.bats` | Unit tests for the pure normalize / aggregate / render path (no network). |
| `.github/workflows/reviewer-report.yml` | Weekly cron → snapshot upload + tracking-issue comment. |
Expand Down
157 changes: 157 additions & 0 deletions scripts/lib/comment-noise.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#!/usr/bin/env bash
# comment-noise.sh — the no-action agent-comment NOISE classifier (issue #1411,
# epic #1402). Net-new measurement: reviewer_report.sh explicitly declares comment
# usefulness / false-positive rate out of scope, so this is the first place the org
# measures how much of its own automation chatter asks nothing of a human.
#
# It is a set of PURE functions with NO network I/O and NO top-level side effects,
# so it can be sourced by both the report (reviewer_report.sh) and its unit tests
# (tests/comment_noise.bats). It does NOT call `set` itself — the caller owns shell
# options — mirroring the sourced-helper convention in persona-runner.sh.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
#
# What it classifies
# An *agent comment* is any PR comment/review whose body carries one of OUR
# automation markers (the denominator of the noise metric). Third-party reviewer
# bots (Copilot, CodeRabbit, …) are NOT in scope here — they are measured by
Comment thread
don-petry marked this conversation as resolved.
# reviewer_report.sh's scorecard and emit no such marker.
#
# A *no-action* comment is an agent comment that asks nothing of a human: it
# reported no change was needed, made no change, or was a clean approval. Every
# other agent comment is *actionable* (it applied a fix, escalated a finding, or
# flagged something for human attention).
#
# Decision logic (single source of truth)
# cn_marker_pattern — ERE identifying an agent comment (the denominator).
# cn_no_action_pattern — ERE identifying a no-action body/marker.
# Both are shared verbatim by the bash classifiers below AND by the jq
# pre-classifier (CN_AGENT_COMMENT_JQ) the report applies during collection, so
# the metric can never be computed two different ways.
#
# Public API
# cn_is_agent_comment <body> — exit 0 when body carries an automation marker.
# cn_is_no_action <body> — exit 0 when body matches a no-action shape.
# cn_classify <body> — prints non-agent | no-action | actionable.
# CN_AGENT_COMMENT_JQ — jq program: one GraphQL PR node -> a stream of
# {kind:"agent_comment", repo, pr, no_action}
# records (needs --arg repo/mark/noact).
# cn_render_noise_section <jsonl_dir> — pure markdown renderer over those records.

# ---------------------------------------------------------------------------
# Decision patterns — the ONE place the metric's boundaries are defined.
# ---------------------------------------------------------------------------

# cn_marker_pattern — matches any body authored by our automation. The known
# markers are: pr-review agent verdicts (<!-- pr-review-agent … -->), every
# dev-lead comment family (fix-reviews / fix-ci / noop-guard / issue / rate-limit,
# all prefixed <!-- dev-lead… -->), persona advisories (<!-- persona:… -->), and
# the dependency advisory (<!-- dependency-advisory -->).
cn_marker_pattern() {
printf '%s' '<!-- (pr-review-agent|dev-lead|persona:|dependency-advisory)'
Comment thread
don-petry marked this conversation as resolved.
Outdated
}

# cn_no_action_pattern — matches a comment that asks nothing of a human. Two kinds
# of signal, both stable and grep/jq-identical (literal phrases + marker fields):
# * Known no-action bodies:
# "No actionable items found." (post_no_changes, dev-lead-fix-reviews)
# "Engine ran but made no changes." (dev-lead-fix-ci / on-mention no-changes)
# "No action required." (dependency advisory, all-LOW verdict)
# * Marker fields that are terminal no-ops:
# status=no-changes (dev-lead fix-ci / fix-reviews)
# decision=approved (pr-review clean/repeat approval)
cn_no_action_pattern() {
printf '%s' 'No actionable items found\.|Engine ran but made no changes\.|No action required\.|status=no-changes|decision=approved'
Comment thread
don-petry marked this conversation as resolved.
Outdated
}
Comment thread
qodo-code-review[bot] marked this conversation as resolved.

# ---------------------------------------------------------------------------
# Pure bash classifiers (unit-tested against every known shape).
# ---------------------------------------------------------------------------

# cn_is_agent_comment <body> — exit 0 when the body carries an automation marker.
cn_is_agent_comment() {
printf '%s' "${1:-}" | grep -Eq "$(cn_marker_pattern)"
}

# cn_is_no_action <body> — exit 0 when the body matches a no-action shape.
cn_is_no_action() {
printf '%s' "${1:-}" | grep -Eq "$(cn_no_action_pattern)"
}

# cn_classify <body> — prints exactly one of: non-agent | no-action | actionable.
# A body with no automation marker is "non-agent" and is never counted as noise.
cn_classify() {
local body="${1:-}"
if ! cn_is_agent_comment "$body"; then
printf 'non-agent'
return 0
fi
if cn_is_no_action "$body"; then
printf 'no-action'
else
printf 'actionable'
fi
}

# ---------------------------------------------------------------------------
# Shared jq pre-classifier — the report's collection path applies THIS program
# (not the bash functions, for bulk speed) but keyed on the SAME patterns, passed
# in as --arg mark / --arg noact, so collection and tests agree by construction.
# ---------------------------------------------------------------------------
# Input: one GraphQL PR node (same shape reviewer_report.sh collects: .url plus
# .reviews.nodes[], .comments.nodes[], .reviewThreads.nodes[].comments.nodes[],
# each carrying .bodyText). Output: one agent_comment record per marker-bearing
# body, pre-flagged no_action. Args: --arg repo, --arg mark, --arg noact.
# shellcheck disable=SC2034 # consumed by callers via jq, not executed here
CN_AGENT_COMMENT_JQ='
. as $pr
| ( [ (.reviews.nodes // [])[] | .bodyText ]
+ [ (.comments.nodes // [])[] | .bodyText ]
+ [ (.reviewThreads.nodes // [])[] | (.comments.nodes // [])[] | .bodyText ] )
| .[]
| select(. != null)
| select(test($mark))
| { kind: "agent_comment", repo: $repo, pr: ($pr.url), no_action: (test($noact)) }
'
Comment thread
don-petry marked this conversation as resolved.

# ---------------------------------------------------------------------------
# Pure markdown renderer over {kind:"agent_comment", pr, no_action} JSONL.
# ---------------------------------------------------------------------------

# _cn_pct <numerator> <denominator> — integer percent, "n/a" when denom is 0.
_cn_pct() {
awk -v a="${1:-0}" -v b="${2:-0}" 'BEGIN { if (b <= 0) print "n/a"; else printf "%d%%", (a / b * 100) + 0.5 }'
}

# _cn_ratio <numerator> <denominator> — 2-decimal ratio, "0.00" when denom is 0.
_cn_ratio() {
awk -v a="${1:-0}" -v b="${2:-0}" 'BEGIN { if (b <= 0) print "0.00"; else printf "%.2f", a / b }'
}

# cn_render_noise_section <jsonl_dir> — read every *.jsonl in the dir, keep only
# agent_comment records, and render the "Agent comment noise" markdown section.
# PURE: no network. A dir with no agent comments renders a clean zero-state.
cn_render_noise_section() {
local dir="${1:-}"
local total=0 noact=0 prs=0
local files=("$dir"/*.jsonl)
if [ -e "${files[0]}" ]; then
local counts
# One jq pass: total agent comments, no-action count, and distinct PRs touched.
counts="$(jq -rs '
map(select(.kind == "agent_comment")) as $ac
| [ ($ac | length),
($ac | map(select(.no_action)) | length),
($ac | map(.pr) | unique | length) ]
| @tsv' "${files[@]}" 2>/dev/null || printf '0\t0\t0')"
IFS=$'\t' read -r total noact prs <<<"$counts"
fi
Comment thread
don-petry marked this conversation as resolved.
total="${total:-0}"; noact="${noact:-0}"; prs="${prs:-0}"

printf '## Agent comment noise\n\n'
printf -- '- **Agent comments:** %s\n' "$total"
printf -- '- **No-action comments:** %s (%s of agent comments)\n' "$noact" "$(_cn_pct "$noact" "$total")"
printf -- '- **PRs with agent comments:** %s\n' "$prs"
printf -- '- **No-action comments per PR:** %s\n\n' "$(_cn_ratio "$noact" "$prs")"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
printf -- '- **Noise** = agent comments that ask nothing of a human — a reported no-change, a no-op engine run, or a clean/repeat approval. '
printf 'Actionable agent comments (applied fixes, escalated findings, human-attention flags) are excluded. '
printf 'Classification is deterministic (`scripts/lib/comment-noise.sh`), computed from the automation markers each comment already carries — no LLM.\n\n'
}
Loading
Loading