Repository navigation
Add advisory review and risk checks for the shadow week - #13
Conversation
The review job ran claude -p on pull_request with ANTHROPIC, OAuth and TypeSafe secrets, so a same-repo branch could read them or fake a clean review. Nothing in the workflow is left without those secrets. Closes gabe4coding/puredori#12 Co-authored-by: Gabe <gpavanello@thefork.com>
Port the risk-gate hardening. The tier job runs on pull_request_target with the workflow, rules and classifier from the base branch. The PR head is fetched as data and never executed. Python runs with -I and uv with --no-config. The classifier tests job has contents: read only and no stored git credentials. Paths come from git diff --name-status -z. The diff uses --text and --no-textconv, and lines split at newlines only. A test-only change that edits an assertion or adds skip, only or todo is T1. A file list that disagrees with the diff is T2-hard. The reviewer signal is only the Greptile Review check from greptile-apps on the current head SHA. A failed, missing, pending or stale check, or a check from another app, has not passed and raises T0 to T1. A pass never lowers a tier. greptile.json, .greptile/** and .coderabbit.yaml are T2-hard. Co-authored-by: Gabe <gpavanello@thefork.com>
|
@greptileai review |
[Critical impact] Adds the risk classification system that gates all future merges. Fix the missing-line risk checks and satisfy the repository's reviewer requirements before merging.
|
| elif line.startswith("+++ ") or line.startswith("--- ") or line.startswith("@@"): | ||
| continue |
There was a problem hiding this comment.
Changed lines escape risk checks
parse_diff drops real changed lines that look like file headers. Adding content beginning with ++ produces a diff line beginning with +++ , which this branch discards. Removing content beginning with -- has the same problem.
Those lines disappear from the forbidden-name scan and size limits, so a change that should be HOLD or T2-hard can receive T1. Track whether the parser is inside a hunk before treating these prefixes as headers.
| on: | ||
| pull_request: | ||
| types: [opened, synchronize, reopened, ready_for_review, edited] | ||
| pull_request_target: | ||
| types: [opened, synchronize, reopened, ready_for_review, edited] |
There was a problem hiding this comment.
The workflow reads the Greptile result once, but never runs again when that review finishes. If a docs-only PR is checked while Greptile is pending, _with_reviewer raises it to T1. A later successful review leaves that tier and label unchanged until someone edits, pushes, or reopens the PR.
This makes the advisory shadow-week results depend on timing. Refresh the tier when the current head's Greptile review completes.
| blocks = blocking(result, diff) | ||
| (out / "general.json").write_text(json.dumps(result, indent=2), encoding="utf-8") | ||
| text = render(result, blocks) |
There was a problem hiding this comment.
Unchecked findings reach comments
render publishes every model finding, including findings whose file, line, or evidence does not exist in the diff. blocking checks evidence only to choose the “would block” marker; it does not filter the report.
The repository's JTOOL-0002.2 rule requires findings to pass validate_findings before reaching a report or comment. Add that check before saving or rendering findings. This repository requirement must be satisfied before merging.
Context Used: AGENTS.md (source)
| def blocking(result: dict, diff: str) -> list: | ||
| lines = {l[1:].strip() for l in diff.splitlines() if l[:1] in "+-" and not l.startswith(("+++", "---"))} | ||
| return [f for f in result.get("findings", []) | ||
| if f.get("severity") == "high" and f.get("kind") in ("bug", "irreversible") and verified(f, lines)] |
There was a problem hiding this comment.
blocking uses the model's severity and a matching diff line to mark a finding “verified, would block” and return exit code 2. It never gets a verifier verdict or derives severity from the corpus. A copied line alone does not establish that the claimed bug is real.
JTOOL-0002.5 requires blocking decisions to come from the corpus and verification. JTOOL-0002.6 requires a verifier to confirm the problem from seen evidence. Apply those checks before marking findings verified; these repository requirements must be satisfied before merging.
Context Used: AGENTS.md (source)
| ap.add_argument("--event", required=True) | ||
| ap.add_argument("--model", default="claude-sonnet-5") | ||
| ap.add_argument("--out", default=".review-out") | ||
| ap.add_argument("--post", action="store_true") |
There was a problem hiding this comment.
The new command can create or update GitHub comments with --post, but it has no --dry-run option. JTOOL-0002.4 requires commands that write to external services to offer --dry-run without writing.
Add the option and make it preview the intended comment without sending the POST or PATCH. This repository requirement must be satisfied before merging.
Context Used: AGENTS.md (source)
A successful Greptile Review check is not a pass on its own. claude-code-mode#47 concluded success at 5075dbc with confidence 0/5 and three P1 comments. A pass now needs all three on the current head SHA: that check from greptile-apps, the latest greptile-apps[bot] summary scored at least 4/5 for that SHA, and no P0 or P1 inline comment from that bot on that SHA. Anything else raises the tier one step and never lowers it. The tier job reads the check and both comment lists with github.token, GET only. Co-authored-by: Gabe <gpavanello@thefork.com>
Intent
Start the shadow week of the validation loop on this repo, the same setup as gabe4coding/puredori#10 and gabe4coding/retroagent#58. Two new checks run on every PR. Both are advisory: they are not required, they never fail a PR, and nothing here merges, approves or changes repo settings.
review(.github/workflows/review.yml): two passes in one job.ci/github/shall-review.yml..github/scripts/general_review.py): one tool-less, isolatedclaude -pcall that checks for bugs, missing or weaker tests, silent behavior changes, PR body vs diff drift and irreversible changes. The PR text is passed as untrusted data. It also records the risk-judgment JSON from the design, which does not change the tier yet.::noticethat names the secret.risk(.github/workflows/risk.yml,.github/scripts/risk_tier.py,.github/risk.yml): the deterministic tiers HOLD / T2-hard / T2-soft / T1 / T0.risk tiercheck run (success for T0/T1, neutral for T2/HOLD, never failure) and as arisk:*label.AUTOMERGEisoff, every PR becomes T2-hard. It is read only.review.ymluses this checkout's own shall (plugins/shall/bin) and this repo's.shall/workspace (JTOOL rules on top of the organisation corpus inshall.yaml). There is no external pinned copy and noSHALL_EXTENDS. The PR's own tooling runs, which is fine for an advisory check.ci.ymlstays deterministic, with no model calls and no secrets. The model calls live only in the separatereviewworkflow.plugins/shall/bin/shall-hook,hooks.py,claude_judge.py,publish.pyandci/github/.plugins/,corpus/,.shall/,examples/andeval/are product text, not docs.corpus/index/and.shall/index/are generated, so they don't count toward the size limits..github/scripts/test_risk_tier.pyandtest_general_review.py, run by therisk (classifier tests)job.Secrets to add (Settings > Secrets and variables > Actions):
TYPESAFE_API_KEY: needed for shall's Jev rule selection.ANTHROPIC_API_KEYorCLAUDE_CODE_OAUTH_TOKEN: needed forclaude -pin both passes.This PR is T2-hard by its own rules, because it adds
.github/risk.ymland the review and risk workflows. Gabe reviews and merges it.Evidence
uv run --no-project --python 3.12 --with pyyaml==6.0.2 --with pytest -- python -m pytest -q .github/scriptspasses. actionlint is clean on both workflows.maketargets run in CI (ci.yml). This box has nomake, and none of the tooling changes.T2-hard(hard paths:.github/risk.yml, the scripts and both workflows)..github/workflows/ci.yml:on:; size: 2119 changed code lines.claude/hillclimb/scan/_state.jsonplugins/shall/tooling/src/shall/hooks.pyplugins/shall/tooling/src/shall/claude_judge.py; sensitive-path: touchesplugins/shall/tooling/src/shall/hooks.py.jrfc/.gitignore; deleted-source: deletesexamples/booking-service/.jrfc/.gitignoreLICENSEeval/scan/cases.yamladds a forbidden nameTotals: T0 1, T1 1, T2-soft 2, T2-hard 7, HOLD 1.
Things to compare by hand this week: whether each T2-soft really needed you, and whether any T1 should have been T2.