Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0cf1284
feat(engine): flag repos with a degraded PR read in the org outputs
cpenaforte-clickbus Oct 5, 2026
3969f6e
fix(engine): make the org PR-read note plural-safe
cpenaforte-clickbus Oct 5, 2026
cad1c6e
fix(engine): omit acceptance and staleness when enrichment failed
cpenaforte-clickbus Oct 7, 2026
3c21f06
fix(engine): treat null GraphQL fields as an incomplete enrichment
cpenaforte-clickbus Oct 7, 2026
2e7c674
fix(engine): pass the GraphQL cursor as a raw field
cpenaforte-clickbus Oct 7, 2026
e757dba
fix(engine): say when the PR read was incomplete on the console
cpenaforte-clickbus Oct 7, 2026
ff3cc9b
refactor(engine): build acceptance groups with one helper
cpenaforte-clickbus Oct 7, 2026
2300c81
fix(engine): skip the origin funnel when enrichment failed
cpenaforte-clickbus Oct 7, 2026
e4de8dc
fix(engine): skip the funnel whenever a degraded read lost acceptance
cpenaforte-clickbus Oct 7, 2026
daf1190
fix(engine): pass string GraphQL variables as raw fields
cpenaforte-clickbus Oct 7, 2026
7353448
fix(engine): skip null GraphQL nodes and commits
cpenaforte-clickbus Oct 7, 2026
874a51b
fix(engine): count all PR states in the console line
cpenaforte-clickbus Oct 7, 2026
0e0bf9d
fix(engine): treat a page without a nodes list as incomplete
cpenaforte-clickbus Oct 8, 2026
0ec130b
fix(engine): retry only transient gh errors, and the reviews pass too
cpenaforte-clickbus Oct 7, 2026
54c7a1d
fix(engine): gate degraded metrics on the PR state that feeds them
cpenaforte-clickbus Oct 7, 2026
bc19f21
fix(engine): omit what a failed PR list feeds, per state
cpenaforte-clickbus Oct 7, 2026
b8284db
fix(engine): anchor transient gh error detection
cpenaforte-clickbus Oct 7, 2026
2716615
docs(engine): align METRICS.md with per-state degradation
cpenaforte-clickbus Oct 7, 2026
8c3703d
fix(engine): name open-PR metrics in the PR-read caveat
cpenaforte-clickbus Oct 7, 2026
312f820
docs(engine): note flow_load omission when a PR list failed
cpenaforte-clickbus Oct 8, 2026
581095b
docs(engine): correct what the PR read covers and what it skews
cpenaforte-clickbus Oct 8, 2026
d0ae092
fix(engine): name PR lifecycle metrics in the PR-read caveat
cpenaforte-clickbus Oct 8, 2026
b935183
Merge pull request #291 from RocketBus/fix/pr-read-degradation-failed…
cpenaforte-clickbus Oct 8, 2026
3a9cdfd
Merge pull request #290 from RocketBus/fix/pr-read-degradation-per-state
cpenaforte-clickbus Oct 8, 2026
0f5d438
Merge pull request #289 from RocketBus/fix/pr-read-degradation-accuracy
cpenaforte-clickbus Oct 8, 2026
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
147 changes: 105 additions & 42 deletions docs/METRICS.md

Large diffs are not rendered by default.

20 changes: 17 additions & 3 deletions iris/analysis/origin_funnel.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
Each stage has a conversion rate. Comparing funnels across origins reveals
where AI-assisted code drops off relative to human code.

Limitation: PR→Merge stage is not measured (Iris only fetches merged PRs).
Future work could add closed/rejected PR data to fill this gap.
Limitation: PR→Merge stage is not measured. Iris reads merged, closed and open
PRs, but the "In PR" stage reads acceptance, which counts only commits in
merged PRs, and no stage follows PRs that never merge. The closed PRs already
read could fill this gap.
"""

# AGGREGATOR_OPT_OUT: consumes a finalized ReportMetrics (post-aggregation composition).
Expand Down Expand Up @@ -56,8 +58,20 @@ def calculate_origin_funnel(metrics: ReportMetrics) -> FunnelResult | None:
metrics: ReportMetrics with origin, acceptance, and durability data.

Returns:
FunnelResult with per-origin funnels, or None if no origin data.
FunnelResult with per-origin funnels, or None if no origin data, or
if the PR read was degraded and acceptance is absent: the "In PR"
stage reads acceptance, and every later conversion chains from it.
"""
# `pr_enrichment_degraded` is the union over PR states, so it alone cannot
# say whether the PRs acceptance reads were lost; acceptance itself can.
# The aggregator omits it when the merged PRs lost their enrichment, and a
# failed list or read leaves no PRs to match commits to: either way "In
# PR" would fall back to "every commit is in a PR". That default is kept
# only for PRs absent by design (no gh, no GitHub remote), where nothing
# is marked degraded.
if metrics.pr_enrichment_degraded and not metrics.acceptance_by_origin:
return None

origin_dist = metrics.commit_origin_distribution
if not origin_dist:
return None
Expand Down
14 changes: 12 additions & 2 deletions iris/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from iris.ingestion.github_reader import read_pull_requests_with_fallback
from iris.metrics.aggregator import aggregate
from iris.models.context import AnalysisContext
from iris.models.pull_request import DEGRADED_BASIC, DEGRADED_FETCH
from iris.reports.narrative import generate_narrative
from iris.reports.writer import write_output

Expand Down Expand Up @@ -498,10 +499,15 @@ def _tick(label: str) -> None:
with span("ingestion.pull_requests", {"repo": repo_name}):
pr_fetch = read_pull_requests_with_fallback(repo, days=args.days)
prs = pr_fetch.prs
if prs:
steps = ", ".join(pr_fetch.degraded)
if prs and pr_fetch.degraded:
print(s["cli_prs_found_degraded"].format(count=len(prs), steps=steps))
elif prs:
print(s["cli_prs_found"].format(count=len(prs)))
elif DEGRADED_BASIC in pr_fetch.degraded or DEGRADED_FETCH in pr_fetch.degraded:
print(s["cli_prs_failed"].format(steps=steps))
elif pr_fetch.degraded:
print(s["cli_prs_failed"].format(steps=", ".join(pr_fetch.degraded)))
print(s["cli_prs_none_degraded"].format(steps=steps))
else:
print(s["cli_prs_skipped"])
_tick("pull requests fetched")
Expand All @@ -523,6 +529,7 @@ def _tick(label: str) -> None:
prs=prs or None,
external_data=external_data,
pr_fetch_degraded=pr_fetch.degraded,
pr_fetch_degraded_by_state=pr_fetch.degraded_by_state,
)
print(s["cli_classified"].format(count=len(commits)))
_tick("aggregate analysis done")
Expand Down Expand Up @@ -553,6 +560,7 @@ def _tick(label: str) -> None:
recent_metrics = aggregate(
recent_commits, churn_days=args.churn_days, prs=recent_prs,
pr_fetch_degraded=pr_fetch.degraded,
pr_fetch_degraded_by_state=pr_fetch.degraded_by_state,
)
trend = compute_trend_delta(
baseline=metrics,
Expand Down Expand Up @@ -686,10 +694,12 @@ def _tick(label: str) -> None:
pre_metrics = aggregate(
pre_commits, churn_days=args.churn_days, prs=pre_prs,
pr_fetch_degraded=pr_fetch.degraded,
pr_fetch_degraded_by_state=pr_fetch.degraded_by_state,
)
post_metrics = aggregate(
post_commits, churn_days=args.churn_days, prs=post_prs,
pr_fetch_degraded=pr_fetch.degraded,
pr_fetch_degraded_by_state=pr_fetch.degraded_by_state,
)

# Compute day spans from commit date ranges
Expand Down
36 changes: 26 additions & 10 deletions iris/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@
),
"pr_enrichment_degraded_caveat": (
"PR data is incomplete for this run — failed steps: {steps}. Merge "
"strategy is reported as unknown when enrichment failed, and flow, "
"review and in-PR metrics may be missing or skewed. A transient "
"GitHub error usually clears on the next run; if every run fails, "
"check that gh is authenticated (gh auth status)."
"strategy is reported as unknown when enrichment failed for merged "
"PRs, and PR lifecycle (counts, time to merge, cycle time), flow, "
"review, in-PR and open-PR metrics may be missing or skewed. A "
"transient GitHub error usually clears on the next run; if every run "
"fails, check that gh is authenticated (gh auth status)."
),
"system_disclaimer": (
"Engineering outcomes emerge from system dynamics, domain complexity, "
Expand Down Expand Up @@ -338,9 +339,11 @@

# CLI
"cli_reading_prs": "Fetching pull requests...",
"cli_prs_found": "{count} merged PRs found.",
"cli_prs_found": "{count} PRs found.",
"cli_prs_skipped": "skipped (no GitHub remote or gh CLI).",
"cli_prs_failed": "failed ({steps}) — continuing without PR data.",
"cli_prs_found_degraded": "{count} PRs found — PR read incomplete ({steps}).",
"cli_prs_none_degraded": "no PRs in the window — PR read incomplete ({steps}).",
"cli_classifying": "Classifying commits...",
"cli_classified": "{count} commits classified.",
"cli_non_code_repo": (
Expand Down Expand Up @@ -527,6 +530,10 @@
"project manifest, so their metrics describe documentation rather than "
"delivery: {repos}."
),
"org_pr_read_degraded": (
"PR data is incomplete this run for {count} repository(ies) ({repos}): "
"their PR counts and PR-derived metrics in this report may be missing or skewed."
),
"org_ai_impact_title": "AI Impact Across Organization",
"org_ai_impact_body": (
"Of {total_repos} repositories, {ai_repos} contain AI-assisted commits. "
Expand Down Expand Up @@ -922,10 +929,12 @@
"pr_enrichment_degraded_caveat": (
"Os dados de PR estão incompletos nesta execução — passos com falha: "
"{steps}. A estratégia de merge sai como desconhecida quando o "
"enriquecimento falha, e métricas de flow, de review e de commits em "
"PR podem faltar ou sair distorcidas. Um erro transitório do GitHub "
"costuma sumir na próxima execução; se toda execução falhar, "
"verifique se o gh está autenticado (gh auth status)."
"enriquecimento dos PRs mergeados falha, e métricas de ciclo de vida "
"de PR (contagens, tempo até o merge, cycle time), de flow, de "
"review, de commits em PR e de PRs abertos podem faltar ou sair "
"distorcidas. Um erro transitório do GitHub costuma sumir na próxima "
"execução; se toda execução falhar, verifique se o gh está "
"autenticado (gh auth status)."
),
"system_disclaimer": (
"Resultados de engenharia emergem de dinâmicas sistêmicas, complexidade do domínio "
Expand Down Expand Up @@ -1227,9 +1236,11 @@

# CLI
"cli_reading_prs": "Buscando pull requests...",
"cli_prs_found": "{count} PRs merged encontrados.",
"cli_prs_found": "{count} PRs encontrados.",
"cli_prs_skipped": "pulado (sem remote GitHub ou gh CLI).",
"cli_prs_failed": "falhou ({steps}) — seguindo sem dados de PR.",
"cli_prs_found_degraded": "{count} PRs encontrados — leitura de PR incompleta ({steps}).",
"cli_prs_none_degraded": "nenhum PR na janela — leitura de PR incompleta ({steps}).",
"cli_classifying": "Classificando commits...",
"cli_classified": "{count} commits classificados.",
"cli_non_code_repo": (
Expand Down Expand Up @@ -1417,6 +1428,11 @@
"de projeto, então suas métricas descrevem documentação e não "
"entrega: {repos}."
),
"org_pr_read_degraded": (
"Os dados de PR estão incompletos nesta execução para {count} "
"repositório(s) ({repos}): as contagens de PR e as métricas derivadas "
"de PR neste relatório podem faltar ou sair distorcidas."
),
"org_ai_impact_title": "Impacto de IA na Organização",
"org_ai_impact_body": (
"De {total_repos} repositórios, {ai_repos} contêm commits assistidos por IA. "
Expand Down
Loading
Loading