- _fake_now now patches triage_with_llm.dt (the alias actually used by
the issue grace check in _seconds_since_latest_marker_comment) instead
of agent_shin_shared.dt, which was a no-op for issues.
- Drop the dead _FakeDt inner class that was never installed.
- Drop unused _DEFAULT_FUTURE_SECONDS constant.
- Update tests to assert against triage_with_llm.dt.
Co-authored-by: Yassin Kortam <yassin@berri.ai>
This is the second-step PR of the Agent Shin rollout: it merges 7 days
after the heads-up (#28759 + this branch) and turns the bot on for real.
Two things happen on merge:
1. A one-shot enactment sweep runs over every open external PR/issue,
driving each through the steady-state triage logic with --close=true.
PRs that fixed their description in the grace week get tagged
`ready for review`; PRs/issues still failing the rubric get the
standard 24h grace warning (or close, if they already had one and
24h elapsed). The sweep uses the same `_agent_shin_actions` dry-run
wrappers as the heads-up so a single boolean toggles real vs. log.
2. The four existing triage workflows (triage_pr_with_llm,
triage_issue_with_llm, close_low_quality_prs, review_gate,
triage_reconsider) flip from "dry-run unless AGENT_SHIN_ENABLED=true"
to "live unless AGENT_SHIN_ENABLED=false". The variable becomes a
kill switch instead of an opt-in. Default semantics: unset means live.
Files
-----
.github/scripts/triage_rollout_enact.py — the enactment sweep.
* Calls review_gate(close=False, now=current_time) for PRs and
triage(close=False) (wrapped in `_fake_now(current_time)`) for
issues, then routes the verdict through the matching maybe_*
wrapper. Two single-page dispatch tables (_apply_pr_result,
_apply_issue_result) make it easy to audit which actions map to
which mutations.
* Time-travel dry-run: --simulate-future-hours N (default 24+1s
when --close is not set) shifts the clock forward N hours so you
can preview what the next daily cron will do. Implemented in
exactly two places: review_gate's `now=` parameter for PRs, and
a `_fake_now` context manager that patches
`agent_shin_shared.dt.datetime.now` for issues. The context
manager restores the original module on exit (and on exception).
* --simulate-now ISO_TS pins the clock to a specific timestamp.
* --close forbids the simulate flags so a real run is always at
wall-clock time.
.github/workflows/triage_rollout_enact.yml — thin wrapper. Fires
--close on push to litellm_internal_staging (the enactment merge)
and offers a workflow_dispatch with dry_run + simulate_future_hours
inputs for safe re-runs.
.github/workflows/{triage_pr_with_llm,triage_issue_with_llm,
close_low_quality_prs,review_gate,triage_reconsider}.yml — inverted:
* `${AGENT_SHIN_ENABLED:-true}` (default live)
* Conditional: `= "false"` enters kill-switch branch
* OPENAI_API_KEY env: exposed unless `vars.AGENT_SHIN_ENABLED == 'false'`
Comments updated to call out the new kill-switch semantics.
tests/test_litellm/test_github_triage_workflows.py — split the
destructive-gate constant into PER_RUN_GATE_ENV (per-input gates
that must still match `= "true"`) and KILL_SWITCH_WORKFLOWS (all
five workflows, must match the inverted `= "false"` / `!= "false"`
pattern). Updates the kill-switch test wording to describe the
new semantics.
tests/test_litellm/test_triage_rollout_enact.py — 22 tests covering:
* _fake_now patches and restores (including on exception)
* Each branch of _apply_pr_result / _apply_issue_result with a
recorder that captures the maybe_* call sequence and dry_run flag
* _process_one skip-not-open / skip-internal-author
* run() end-to-end: dry-run threads dry_run=True through every
wrapper, real run threads False, current_time threads through to
the per-item evaluators, --kind / --only-numbers restrict scope.
Local preview commands
----------------------
# What this script would do right now (no GitHub writes):
python3 .github/scripts/triage_rollout_enact.py --repo BerriAI/litellm
# What the next daily cron will do (24h+1s in the future):
python3 .github/scripts/triage_rollout_enact.py --repo BerriAI/litellm \\
--simulate-future-hours 24
# Pin to a specific moment:
python3 .github/scripts/triage_rollout_enact.py --repo BerriAI/litellm \\
--simulate-now '2026-06-02T09:00:00Z'
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a rollout-day workflow that comments on every open external PR/issue
that the new triage bot WOULD auto-close, giving contributors 7 days to
fix their description before any destructive action runs.
Why now: merging this PR enables Agent Shin in dry-run. The follow-up
"enact" PR (next Monday) flips the destructive paths on. Without this
heads-up, contributors would get a close-comment on day 8 with no prior
warning. The heads-up names the cutoff date, lists the rubric, calls out
each PR/issue's specific missing pieces, and explains the recovery paths
(@agent-shin reconsider for PRs, edit + reopen for issues).
Files
- .github/scripts/_agent_shin_actions.py — thin maybe_post_comment /
maybe_close_* / maybe_add_label / etc. wrappers. Each is a single
`if dry_run: log; return; else: call_through()` so a dry-run preview
differs from the real run in exactly one call site per mutation. The
call-through goes via `triage_with_llm.<name>` (module-qualified) so
monkeypatching the underlying function in tests is reflected here.
- .github/scripts/triage_rollout_heads_up.py — the sweep. Iterates every
open PR + issue via `gh pr list` / `gh issue list`, runs the future
rubric (review_gate for PRs, triage(kind="issue") for issues), and
posts the heads-up on any item that would be auto-closed. Idempotent
via a `<!-- agent-shin:rollout-heads-up -->` marker. Defaults to dry-
run; --close opts in to real posts. --close-on overrides the cutoff
date (defaults to today + 7 days).
- .github/workflows/triage_rollout_heads_up.yml — one-shot workflow.
Triggers on push to litellm_internal_staging filtered to the script
path (fires on rollout merge) plus workflow_dispatch with a dry_run
input that defaults to "true" for safe manual re-runs.
- tests/test_litellm/test_triage_rollout_heads_up.py — 28 unit tests
covering: the dry-run wrappers (each maybe_* gates correctly), the
_would_be_closed predicate for PR vs. issue results, the comment
formatter (cutoff/rubric/marker/recovery wording), per-item dispatch
(skip-not-open, skip-internal-author, skip-already-notified,
skip-passing, would-post/posted), and the sweep loop end-to-end.
Local preview (no GitHub mutations):
python3 .github/scripts/triage_rollout_heads_up.py --repo BerriAI/litellm
Real run (what the workflow does):
python3 .github/scripts/triage_rollout_heads_up.py --repo BerriAI/litellm --close
TODO: replace the placeholder ROLLOUT_BLOG_URL with the canonical
docs URL once the litellm-docs PR ships.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bug 1: `triage_with_llm.py` and `close_low_quality_prs.py` each defined
their own copies of `extract_greptile_score`, `parse_iso8601`,
`GREPTILE_BOT_LOGINS`, `SCORE_PATTERN`, `GRACE_COMMENT_MARKER`,
`GRACE_PERIOD_SECONDS`, `IMMEDIATE_CLOSE_LOGINS`, and
`AGENT_SHIN_DEFAULT_BOT_LOGIN`. The comments explicitly said the two
copies had to stay in sync, but nothing enforced it. A future change to
one (e.g. extending `SCORE_PATTERN` for a new Greptile output format)
would silently diverge from the other and the daily sweep and the LLM
judge would disagree on which PRs have low scores.
Extract these to `.github/scripts/agent_shin_shared.py` and re-export
them from each script so the existing test attribute access
(`triage_module.GRACE_COMMENT_MARKER`, etc.) keeps working without
any test changes.
Bug 2: `review_gate.yml` is a destructive workflow (close PRs, add/remove
labels, post comments) with the same gating philosophy as the others
(`AGENT_SHIN_ENABLED = "true"` + a per-run `CLOSE_FLAG = "true"`),
but it was missing from `DESTRUCTIVE_GATE_ENV` in the guardrail tests.
Add it so a future regression (e.g. flipping to `!= "false"`) is
caught by the same parameterized invariants as every other workflow.
Co-authored-by: Yassin Kortam <yassin@berri.ai>
Adds the "ready for review" label lifecycle (originally PR #28758) on top
of #28147's refactored triage_with_llm.py. The original commit was
authored against an older snapshot of #28117 and could not be applied
cleanly, so the additions were re-applied surgically:
- New constants: READY_FOR_REVIEW_LABEL, DEFAULT_GRACE_DAYS,
DEFAULT_MIN_GREPTILE_SCORE, READY/REGRESSED/WITHIN_GRACE markers,
GREPTILE_BOT_LOGINS, SCORE_PATTERN, AGENT_SHIN_AUTO_CLOSE_MARKER.
- New helpers: add_label, remove_label, extract_greptile_score,
parse_iso8601 (the latter two mirrored from close_low_quality_prs.py
so the daily sweep and the review gate read the score through the
same logic).
- New comment formatters: format_ready_for_review_comment,
format_all_clear_comment, format_regression_comment,
format_within_grace_comment.
- New entry point: review_gate() implementing the pass/regress/recover
state machine, with the label itself acting as persisted state so
transition comments fire only on actual transitions.
- main() learns --review-gate, --grace-days, --min-greptile-score and
dispatches to review_gate() when the flag is set.
Verified via tests/test_litellm/test_github_review_gate.py (18 tests)
and the existing triage suites (144 more) — all 162 pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
IMMEDIATE_CLOSE_LOGINS PRs are closed even when the global --close flag is
not set, but the summary used the global dry-run flag to choose between
'would close' and 'closed'. Split the count so operators can see both
actual closures and dry-run would-be closures.
Co-authored-by: Yassin Kortam <yassin@berri.ai>
For PRs from IMMEDIATE_CLOSE_LOGINS (e.g. swiftwinds), evaluate_pr
returns 'close' immediately without ever posting a grace warning, so
the close comment should not reference a 1-day grace period.
Make close_pr take a grace_period_elapsed flag, default True, and
pass False from the main loop when the close path was the
immediate-close branch.
Co-authored-by: Yassin Kortam <yassin@berri.ai>
- Add a 24-hour grace window between the first low-quality detection
and the actual auto-close. The first detection posts a warning
comment that explicitly says "You have 1 day to address this before
this PR is auto-closed" and points the contributor at:
* `@agent-shin reconsider` to request another look (and re-open)
* `@greptileai` to request a fresh Greptile review — works
even after the PR is closed
- Both `triage_with_llm.py` (LLM judge) and `close_low_quality_prs.py`
(Greptile-score closer) share the same `<!-- agent-shin:grace-warning -->`
HTML marker so a warning posted by either path is recognized by both.
- Add IMMEDIATE_CLOSE_LOGINS = {swiftwinds} to bypass BOTH the grace
period AND the dry-run / AGENT_SHIN_ENABLED gating. SwiftWinds is the
user's personal account (no push permissions to litellm) used to
dogfood the bot; user explicitly asked: "For SwiftWinds, just close
immediately. Faster iteration that way."
- Update the standard close comments to mention that `@greptileai`
works even after the PR is closed.
- Add 23 new tests covering: warn-grace on first detection, skip during
grace window, close after grace expires, SwiftWinds bypass (case
insensitive, with close=False, no random-login false positives), the
grace-warning text invariants, and the SwiftWinds entry in the
IMMEDIATE_CLOSE_LOGINS constant.
Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
Address three Greptile/veria-ai concerns on the @agent-shin reconsider
flow:
1. **Reconsider had no dry-run path.** The previous reconsider mode
ignored `--close` and always posted comments + reopened on a pass.
A local operator running
`python triage_with_llm.py --reconsider --pr N` would silently
take destructive GitHub actions with no way to preview. Reconsider
now honors `close=False` the same way regular triage does and
returns `would-reopen` / `would-reconsider-still-failing` for
step-summary rendering.
2. **Reconsider could reopen maintainer-closed PRs/issues** (Medium
security finding from veria-ai). The workflow only checked that the
commenter was authorized — it did NOT check that the most recent
close was performed by Agent Shin. A contributor could comment
`@agent-shin reconsider` on a PR a maintainer closed for non-rubric
reasons (duplicate, security report, design rejection) and have the
bot reopen it. Add `was_closed_by_agent_shin()` which inspects the
issue events API for the most recent `closed` actor and only
permits reopen when that actor matches the configured bot login
(default `github-actions[bot]`, overridable via env). Fail-closed
on missing events.
3. **No rate-limiting on the reconsider trigger.** Every
`@agent-shin reconsider` comment burns CI minutes + an OpenAI API
call. Add a 10-minute cooldown via
`seconds_since_last_reconsider_verdict()` which greps the issue's
comment list for the bot's own verdict marker
(`<!-- agent-shin:reconsider-verdict -->`). Inside the window the
triage returns `skip-rate-limited` and the LLM never runs.
Workflow update:
- `triage_reconsider.yml` now passes `--close` only when
`AGENT_SHIN_ENABLED=true`, matching the pattern of
`triage_pr_with_llm.yml`. The script runs in both states so the
verdict still appears in the step summary for QA.
Tests:
- Add 5 reconsider safety tests: dry-run for pass / fail / linked-issue
short-circuit, bot-closed-guard refusal on maintainer close,
rate-limit refusal inside the cooldown window, and cooldown-elapsed
acceptance.
- Add unit tests for `was_closed_by_agent_shin` (bot / maintainer /
missing actor / env-override) and
`seconds_since_last_reconsider_verdict` (no marker / multiple
markers / non-bot comment with marker / bot comment without marker).
- Pin the `<!-- agent-shin:reconsider-verdict -->` marker in both
reopen and still-failing comments — dropping it would silently
break the cooldown.
Existing reconsider tests updated to pass `close=True` (the
production path now) + stub the new guards via
`_stub_reconsider_guards`. 112 tests pass (was 93).
Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
Follow-up to PR #28117. Three behavior changes + one new workflow,
addressing the team's concerns on the original review:
1) Apply auto-close to ALL open PRs, not just those over a week old.
- close_low_quality_prs.py: --min-age-days default flipped from 7 to
0. The flag is preserved as an opt-in safety net for one-off
backfill runs that want to spare very-young PRs, but the daily
scheduled sweep now closes external-author PRs as soon as Greptile
scores them <4/5.
- close_low_quality_prs.yml: workflow_dispatch input default also
flipped to 0; doc comments updated.
2) Apply auto-close to draft PRs too.
- close_low_quality_prs.py: removed the skip-draft branch in
evaluate_pr. Drafts are NOT a free pass — the team's intent is
'open PR count == PRs internal collaborators need to action on',
so a draft Greptile scored 2/5 still belongs in the closed bucket.
Authors who genuinely need a long-lived draft can attach the 'wip'
opt-out label, which is unchanged.
- The 'skip-draft' action is gone; the 'wip' label still skips.
3) Address the 'OSS contributors cannot reopen a bot-closed PR' wrinkle.
GitHub does NOT let an external (non-write-access) contributor
reopen a PR that was closed by a bot or maintainer (long-standing
limitation). The original PR's close-comments told contributors to
'Reopen the PR — I'll re-evaluate automatically', which is broken
for the very audience this triage targets. Two changes:
a) Reword every close-comment (Greptile sweep + Agent Shin PR
close + Agent Shin issue close + PR template) to recommend:
- Open a new PR with the updated branch (primary path).
- Or comment '@agent-shin reconsider' on the closed PR for a
re-evaluation that, on pass, reopens the PR via the bot's
GH_TOKEN write access.
b) Add the @agent-shin reconsider workflow:
- .github/workflows/triage_reconsider.yml: new
'issue_comment'-triggered workflow. Authorizes only the
PR/issue author or an internal collaborator
(OWNER/MEMBER/COLLABORATOR), gated via a step output so
unauthorized commenters never reach the destructive steps.
Globally gated on AGENT_SHIN_ENABLED='true' (positive form,
matching the test_github_triage_workflows guardrail
patterns).
- triage_with_llm.py: --reconsider mode. On a closed PR/issue,
re-runs the LLM judge (or linked-issue regex short-circuit)
and:
- on pass: reopens via reopen_pr/reopen_issue + posts a
'Re-evaluated and reopened' comment.
- on fail: leaves closed and posts a 'still missing X'
comment so the contributor can iterate again.
Reconsider-on-open is a no-op ('skip-not-closed').
Internal-author + bot-account skips still take priority over
reconsider.
4) Greptile-on-closed-PRs question: the team asked whether Greptile can
re-review a closed PR. Greptile's docs don't address this and we
shouldn't promise behavior we can't verify, so the new close-comment
wording does NOT instruct contributors to 're-request greptile on
the closed PR'. Instead it points them at the new-PR path (which
Greptile definitely reviews) or the @agent-shin reconsider trigger
(which re-runs the LiteLLM-side rubric judge, not Greptile).
Tests: 93 passing (was 59).
- test_github_close_low_quality_prs.py: replaced 'skip drafts' test
with 'closes drafts when score is low' + 'closes brand-new PR when
min_age=0' + 'no skip when min_age=0'. The 'skip too young'
assertion is preserved as opt-in.
- test_github_triage_with_llm.py: 6 new TestTriageOrchestration cases
for reconsider mode (skip-not-closed on open, reopen on pass,
still-failing comment on fail, linked-issue short-circuit reopen,
skip internal author in reconsider, reopen-issue on pass) + a new
TestCloseCommentText class that pins the user-facing 'open a new
PR' + '@agent-shin reconsider' wording.
- test_github_triage_workflows.py: added triage_reconsider.yml to
the destructive-gate guardrail table; AGENT_SHIN_ENABLED is its
own destructive gate (no separate per-run flag needed).
Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
Three independent bugbot findings against triage_with_llm.py:
1. LINKED_ISSUE_PATTERN included weak keywords (`see`, `ref`,
`addresses`) so casual mentions like "See #1234 for context" were
short-circuited to pass-linked-issue without ever calling the LLM —
contradicting the prompt's own "a bare issue number without a closing
keyword counts only if it's clearly the related issue (not a passing
mention)" rubric. Limit the regex to GitHub's documented PR-closing
keywords (fixes/fix/fixed/closes/close/closed/resolves/resolve/resolved).
2. is_internal_contributor() treated an empty/missing author_association
as external (eligible for the destructive close path), while the sibling
is_external_pr_author() in close_low_quality_prs.py fail-safes the same
case as internal. Align the two so a partial/unknown GitHub response can
never make a PR eligible for auto-close.
3. argparse `default=os.environ.get("TRIAGE_MODEL", DEFAULT_MODEL)` returns
the empty string when GitHub Actions exposes an unset repo variable as
an empty-string env var (the optional vars.TRIAGE_MODEL case in the
workflow). Use `os.environ.get(...) or DEFAULT_MODEL` so empty -> default,
matching the existing OPENAI_BASE_URL pattern.
Tests:
- Casual mentions now must fall through to the LLM (parametrized);
added an orchestration test ensuring "See #1234" reaches the judge.
- Empty/missing author_association now fails safe (parametrized).
- Empty TRIAGE_MODEL env var falls back to DEFAULT_MODEL; explicit
TRIAGE_MODEL is still honored.
Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
- Removed the unused gh_json helper (bugbot low-severity dead code).
- Replaced argparse `action="append", default=[...]` with default=None
+ DEFAULT_OPTOUT_LABELS fallback. The mutable-default + append combo
silently APPENDS to the canonical defaults instead of replacing them,
so --optout-label could not actually scope the opt-out list.
- Added tests covering both the canonical default and the
flag-replaces-defaults behavior.
Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
- Bump DEFAULT_MODEL from gpt-4o-mini to gpt-5.4-mini (more modern;
4M total context window per OpenAI catalog, JSON-schema response
format, function calling all supported).
- For gpt-5.x family models, pass reasoning_effort="none" via
extra_body. gpt-5.x rejects temperature != 1 unless reasoning_effort
is explicitly "none"; setting it lets us keep temperature=0 for
deterministic JSON rubric judgments. extra_body works across openai
SDK versions regardless of whether they natively type the kwarg.
- For non-gpt5 overrides (TRIAGE_MODEL=gpt-4o-mini etc.), reasoning_effort
is not sent.
- 4 new unit tests cover: gpt-5.4-mini -> reasoning_effort=none,
capitalized/dated gpt-5 variants -> reasoning_effort=none,
gpt-4o-mini -> no extra_body, base_url passthrough.
Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
- close_low_quality_prs.py: Treat author_association API lookup failures
as internal (fail-safe) so transient errors don't cause internal
contributors' PRs to be auto-closed.
- triage_with_llm.py: Update summary heading from 'Would post comment:'
to 'Posted comment:' since this branch only runs after the comment
has already been posted.
Co-authored-by: Yassin Kortam <yassin@berri.ai>
- close_low_quality_prs.yml: only workflow_dispatch with close=true (and
AGENT_SHIN_ENABLED=true) actually closes PRs. Scheduled runs are always
dry-run, matching the safety invariant documented for triage_pr/issue.
- triage_with_llm.py: textwrap.dedent on an f-string with multi-line
interpolated bodies fails because the body's 2nd+ lines start at column 0,
making the common-indent zero. Dedent the static template first, then
.format() the title/body in.
Co-authored-by: Yassin Kortam <yassin@berri.ai>
- close_low_quality_prs.py now filters by GitHub author_association via
the REST API: PRs from OWNER / MEMBER / COLLABORATOR (and bot accounts)
are skipped with a new 'skip-internal' summary bucket.
- close_low_quality_prs.yml now defaults workflow_dispatch close=false,
and ignores 'close=true' unless the new repo variable
AGENT_SHIN_ENABLED is set to 'true'. Scheduled runs are dry-run only
until the team flips that switch.
- Updated unit tests: one new test asserting internal authors are
skipped, and an autouse fixture treats unspecified test PRs as
external so the rest of the suite still exercises the close path.
Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
Adds a new triage flow that evaluates external pull requests and issues
against the project's contribution rubric and, when configured to do so,
auto-closes non-conforming ones with an explanatory comment. Contributors
can update + reopen to be re-evaluated.
Scope:
- Internal BerriAI contributors (author_association OWNER/MEMBER/COLLABORATOR)
and bot accounts are skipped entirely.
- 'Fixes #1234' / 'Resolves https://github.com/.../issues/N' in the PR body
short-circuits to PASS without burning LLM tokens.
- LLM judge returns structured JSON (verdict, missing[], explanation);
parser tolerates markdown fences and embedded JSON.
- LLM errors NEVER close PRs/issues — failure surfaces as 'skip-llm-error'.
Safety:
- pull_request_target / issues triggers are FORCED dry-run in the workflow;
only manual workflow_dispatch with close=true (and AGENT_SHIN_ENABLED=true)
takes destructive action.
- Default mode writes verdicts to GITHUB_STEP_SUMMARY only — no public
comments until the team flips the AGENT_SHIN_ENABLED repo variable.
- LLM uses an OpenAI-compatible endpoint (model and base URL configurable
via repo variables; key via OPENAI_API_KEY secret).
Files:
- .github/scripts/triage_with_llm.py - judge orchestrator + CLI
- .github/workflows/triage_pr_with_llm.yml
- .github/workflows/triage_issue_with_llm.yml
- tests/test_litellm/test_github_triage_with_llm.py - 33 unit tests
End-to-end validated against four real PRs (#28117 internal collaborator,
#28108 bot, #28129 'Fixes #28128', #28116 no linked issue) and issue
#28132 with a stubbed LLM judge: each path produces the expected action.
Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
Adds .github/scripts/close_low_quality_prs.py and a daily workflow that
closes PRs which:
- are open for at least 7 days, and
- carry a most-recent greptile-apps review with Confidence Score <4/5,
- and are not drafts or opt-out-labeled ('do not close', 'wip', etc.).
Each closure posts an explanatory comment telling the contributor how to
bring the PR back (rebase, re-request greptile, reopen at 4+/5). The
4/5 bar is already documented in the PR template
(.github/pull_request_template.md), so this just enforces it.
Tested with a dry run against the live BerriAI/litellm backlog of 1000
open PRs: 100 candidates identified, 598 PRs pass the bar (4+/5), 186
are too young, 97 are drafts, 19 lack any Greptile review and are left
alone.
Workflow defaults to closing 25 PRs/run as a safety net and supports
workflow_dispatch with overrides (close=false for a dry run, custom
min_age_days/min_score/limit).
18 unit tests cover score extraction (HTML/markdown/plain text, login
variants, multi-review picks latest) and per-PR evaluation (drafts,
opt-out labels, age, missing/passing/failing scores).
Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
Add a Python script that detects duplicate issues using title similarity
(difflib.SequenceMatcher) and closes them via the gh CLI. Two-tier system:
- 0.6 threshold: informational comment via existing wow-actions step
- 0.85 threshold: auto-close with comment, label, and not_planned reason
Includes a workflow_dispatch workflow for one-time batch scans and
integrates auto-close into the existing check_duplicate_issues workflow
for newly opened issues.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add auto issue labelling for llm_translation
* Add logging
* make the formatting better
* make the formatting better
* make the formatting better
* make the formatting better