diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml new file mode 100644 index 00000000..f6f35da9 --- /dev/null +++ b/.github/workflows/claude-review.yml @@ -0,0 +1,99 @@ +name: Claude PR Review + +# Thin caller of the reusable in HarperFast/ai-review-prompts. The single +# `uses:` ref pin below controls everything that moves together — workflow +# logic, layer files, bash scripts, auth-gate behavior. Bumping the pin +# is the entire upgrade motion. +# +# Pre-requisites (org-level secrets, configured once on HarperFast): +# - HARPERFAST_AI_CLIENT_ID (the App's Client ID, like Iv23li…) +# - HARPERFAST_AI_APP_PRIVATE_KEY (.pem file contents) +# +# Plus the per-repo / inherited: +# - ANTHROPIC_API_KEY (required) +# - AI_REVIEW_LOG_TOKEN (optional — if set, threads each run +# into a per-PR issue in HarperFast/ai-review-log) + +on: + pull_request: + # `labeled` admits the `claude-review` label gesture for + # bot-authored PRs (renovate, dependabot). See ai-review-prompts#38. + types: [opened, synchronize, reopened, ready_for_review, labeled] + +# No concurrency block: cancellation is owned by the reusable's review +# job (job-level group, engaged only after authorization), so an +# unauthorized or skipped event can never cancel a legitimate review. + +jobs: + review: + # Always-on toggle — see ai-review-prompts USAGE.md "Reviewers & the + # always-on toggle". CLAUDE_ALWAYS_ON=true (repo/org variable) → auto- + # review trusted-author PRs; unset → opt-in via the claude-review + # label. The reusable's authorize job still owns WHO is admitted. + # Note: the `claude-review` label name is deliberately duplicated — + # the caller gate names it (below) AND the reusable's authorize + # matches it; renaming the label means changing both. + # `ready_for_review` is admitted when the PR still carries the + # opt-in label, so a draft opted in via `claude-review` resumes + # review when it flips ready even with CLAUDE_ALWAYS_ON unset. + if: ${{ (github.event.action == 'labeled' && github.event.label.name == 'claude-review') || (github.event.action != 'labeled' && vars.CLAUDE_ALWAYS_ON == 'true') || (github.event.action == 'ready_for_review' && contains(github.event.pull_request.labels.*.name, 'claude-review')) }} + uses: HarperFast/ai-review-prompts/.github/workflows/_claude-review.yml@af81872e56dd63da1160adb5eae82d359a456dde # main 2026-09-01 (#94 freshness-gated admission; #91 job-level cancellation; #90 cost gates; #89 defaults; #88 lenses) + # Caller-side permissions at the calling-job level (NOT workflow- + # level — that placement caps the reusable's per-job grants below + # what they need and breaks the workflow at startup; see + # ai-review-prompts#39/#40). Union of what the reusable's authorize + # (`contents: read`) and review (`contents: read` + `pull-requests: + # write` + `id-token: write`) jobs declare. Without this block the + # grants are inherited from the repo's default-workflow-permissions + # setting instead (GitHub intersects the reusable's requests with + # the caller's ceiling — un-grantable scopes are silently dropped, + # and `pull-requests: write` survives only while the repo default + # is "write"). Explicit grants keep the caller independent of that + # setting, and match gemini-review.yml in this repo. + permissions: + contents: read + pull-requests: write + id-token: write + with: + # Same SHA as the `uses:` ref above. The reusable uses this to + # check out HarperFast/ai-review-prompts (layer files + bash + # scripts) at the same ref as the workflow logic itself — keeps + # the upgrade motion atomic. + # + # The duplication is unavoidable: reusable workflows can't + # introspect their own ref (`github.workflow_ref` resolves to the + # CALLER's ref in `workflow_call` context), and `uses: …@` + # is parsed literally so we can't interpolate a variable. + ai-review-prompts-ref: af81872e56dd63da1160adb5eae82d359a456dde + review-layers: | + universal + repo-specific-checks: | + ## Repo-specific checks (Harper documentation) + + Docusaurus site, MDX + TypeScript config. The product here is PROSE that + readers act on, so a wrong sentence is a defect with the same standing as a + wrong line of code. + + - **Doc-to-doc contradiction is the top measured defect class in this repo.** + When a page changes a documented default, option name, flag, endpoint or + version, the SIBLING pages that state the old value are now wrong. Check + whether the same fact is asserted elsewhere and left stale; that single class + accounted for 12 findings across 6 PRs in one post-mortem window. + - **Verify claims against Harper, not against the surrounding prose.** A code + sample, config key, CLI flag or default that no longer exists in the product + is a defect even when the paragraph reads well. Say so when you cannot verify + a claim from this repo alone. + - **Versioned docs**: `docusaurus docs:version` snapshots pages. A fix applied + only to the current version leaves the versioned copies wrong — flag which + copies a correction needs. + - **Redirects**: `historic-redirects.ts` keeps old URLs alive. Renaming or + moving a page without a redirect breaks external links. + - **Tooling**: `npm run typecheck` (tsc) and `format:check` (prettier). + `npm run lint` is a no-op stub (`echo 0`) — do not treat a green lint as + evidence of anything. + - Do not comment on formatting; prettier owns it. + secrets: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + AI_REVIEW_LOG_TOKEN: ${{ secrets.AI_REVIEW_LOG_TOKEN }} + HARPERFAST_AI_CLIENT_ID: ${{ secrets.HARPERFAST_AI_CLIENT_ID }} + HARPERFAST_AI_APP_PRIVATE_KEY: ${{ secrets.HARPERFAST_AI_APP_PRIVATE_KEY }} diff --git a/.github/workflows/gemini-review.yml b/.github/workflows/gemini-review.yml new file mode 100644 index 00000000..db8acbff --- /dev/null +++ b/.github/workflows/gemini-review.yml @@ -0,0 +1,104 @@ +name: Gemini PR Review + +# Thin caller of the Gemini reusable in HarperFast/ai-review-prompts. +# Runs in parallel with claude-review.yml so the two reviewers can be +# compared on the same PRs. +# +# Layer inputs and `repo-specific-checks:` MIRROR claude-review.yml in +# this repo. Output comparability between the two providers depends on +# them seeing the same review scope — keep them in sync when bumping +# the pin or editing the checks block. +# +# Opt-in by default: Gemini runs only when a HarperFast org member +# applies the `gemini-review` label, UNLESS the GEMINI_ALWAYS_ON repo +# variable is set to 'true' (then it auto-reviews trusted-author PRs, +# like claude-review). See ai-review-prompts USAGE.md "Reviewers & the +# always-on toggle". +# +# Pre-requisites: +# - HARPERFAST_AI_CLIENT_ID (org-level App Client ID) +# - HARPERFAST_AI_APP_PRIVATE_KEY (org-level App private key) +# - GEMINI_API_KEY (per-repo; optional — a missing key +# cleanly skips the review with a +# workflow notice, so this is safe to +# merge before the key is set) +# - AI_REVIEW_LOG_TOKEN (optional — threads each run into a +# per-(PR, provider) issue in +# HarperFast/ai-review-log with the +# `provider:gemini` label) + +on: + pull_request: + # `labeled` admits the `gemini-review` opt-in gesture. `vars.*` + # can't be read in `on:` (only in a job `if:`), so the trigger + # lists the union and the `review` job gates on GEMINI_ALWAYS_ON. + types: [opened, synchronize, reopened, ready_for_review, labeled] + +# No concurrency block: cancellation is owned by the reusable's review +# job (job-level group, engaged only after authorization), so an +# unauthorized or skipped event can never cancel a legitimate review. + +jobs: + review: + # Always-on toggle — see ai-review-prompts USAGE.md "Reviewers & the + # always-on toggle". GEMINI_ALWAYS_ON=true (repo/org variable) → auto- + # review trusted-author PRs; unset → opt-in via the gemini-review + # label. The reusable's authorize job still owns WHO is admitted + # (CODEOWNERS trust set; the labeler, not the author, on `labeled`). + # Note: the `gemini-review` label name is deliberately duplicated — + # the caller gate names it (below) AND the reusable's authorize + # matches it; renaming the label means changing both. + # `ready_for_review` is admitted when the PR still carries the + # opt-in label — mirrors claude-review.yml. + if: ${{ (github.event.action == 'labeled' && github.event.label.name == 'gemini-review') || (github.event.action != 'labeled' && vars.GEMINI_ALWAYS_ON == 'true') || (github.event.action == 'ready_for_review' && contains(github.event.pull_request.labels.*.name, 'gemini-review')) }} + uses: HarperFast/ai-review-prompts/.github/workflows/_gemini-review.yml@af81872e56dd63da1160adb5eae82d359a456dde # main 2026-09-01 (#94 freshness-gated admission; #91 job-level cancellation; #90 cost gates; #89 defaults; #88 lenses) + # Caller-side permissions at the calling-job level (NOT workflow- + # level — that placement caps the reusable's per-job grants below + # what they need and breaks the workflow at startup; see + # ai-review-prompts#39/#40). Union of what the reusable's authorize + # (`contents: read`) and review (`contents: read` + `pull-requests: + # write` + `id-token: write`) jobs declare. + permissions: + contents: read + pull-requests: write + id-token: write + with: + # Same SHA as the `uses:` ref above. The reusable uses this to + # check out HarperFast/ai-review-prompts (layer files + bash + # scripts) at the same ref as the workflow logic itself. The + # duplication is unavoidable: reusable workflows can't introspect + # their own ref (`github.workflow_ref` resolves to the CALLER's + # ref in workflow_call context), and `uses: …@` is literal. + ai-review-prompts-ref: af81872e56dd63da1160adb5eae82d359a456dde + review-layers: | + universal + repo-specific-checks: | + ## Repo-specific checks (Harper documentation) + + Docusaurus site, MDX + TypeScript config. The product here is PROSE that + readers act on, so a wrong sentence is a defect with the same standing as a + wrong line of code. + + - **Doc-to-doc contradiction is the top measured defect class in this repo.** + When a page changes a documented default, option name, flag, endpoint or + version, the SIBLING pages that state the old value are now wrong. Check + whether the same fact is asserted elsewhere and left stale; that single class + accounted for 12 findings across 6 PRs in one post-mortem window. + - **Verify claims against Harper, not against the surrounding prose.** A code + sample, config key, CLI flag or default that no longer exists in the product + is a defect even when the paragraph reads well. Say so when you cannot verify + a claim from this repo alone. + - **Versioned docs**: `docusaurus docs:version` snapshots pages. A fix applied + only to the current version leaves the versioned copies wrong — flag which + copies a correction needs. + - **Redirects**: `historic-redirects.ts` keeps old URLs alive. Renaming or + moving a page without a redirect breaks external links. + - **Tooling**: `npm run typecheck` (tsc) and `format:check` (prettier). + `npm run lint` is a no-op stub (`echo 0`) — do not treat a green lint as + evidence of anything. + - Do not comment on formatting; prettier owns it. + secrets: + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + AI_REVIEW_LOG_TOKEN: ${{ secrets.AI_REVIEW_LOG_TOKEN }} + HARPERFAST_AI_CLIENT_ID: ${{ secrets.HARPERFAST_AI_CLIENT_ID }} + HARPERFAST_AI_APP_PRIVATE_KEY: ${{ secrets.HARPERFAST_AI_APP_PRIVATE_KEY }} diff --git a/.github/workflows/validate-caller-workflows.yml b/.github/workflows/validate-caller-workflows.yml new file mode 100644 index 00000000..4849ffa9 --- /dev/null +++ b/.github/workflows/validate-caller-workflows.yml @@ -0,0 +1,35 @@ +name: Validate caller workflows + +# Thin caller of HarperFast/ai-review-prompts' +# `_validate-caller-workflows.yml`. Validates +# `.github/workflows/claude-*.yml` caller files for: +# +# * Shadow jobs — a non-`uses:` job (or a `uses:` outside +# `HarperFast/`) alongside the legit reusable call would run +# with the caller's permissions WITHOUT going through the auth +# gate. Fail-closed. +# * Mutable refs in `uses:` or `with.ai-review-prompts-ref` — +# both must pin to a 40-char SHA. +# +# Runs on every PR and every push to `main` — no `paths:` filter. +# A required status check that only fires on workflow-touching PRs +# stays permanently pending on every other PR (GitHub has no +# "required if it runs" semantic). The validator's runtime is +# trivial (yq-parses a few caller files), so unconditional firing +# is the right trade-off for satisfiability. +# +# Make this `validate` job a REQUIRED status check on `main`. + +on: + pull_request: + push: + branches: [main] + +jobs: + validate: + uses: HarperFast/ai-review-prompts/.github/workflows/_validate-caller-workflows.yml@af81872e56dd63da1160adb5eae82d359a456dde # main 2026-09-01 (#94 freshness-gated admission) + with: + # Same SHA as the `uses:` ref above — the reusable uses this + # to check out the validator script at the matching version. + # Same SHA-twice pattern as the other caller workflows. + ai-review-prompts-ref: af81872e56dd63da1160adb5eae82d359a456dde