diff --git a/docs/Corbits Triage User Manual/Corbits Triage User Manual.md b/docs/Corbits Triage User Manual/Corbits Triage User Manual.md index a29ca05e1..6ec6390a1 100644 --- a/docs/Corbits Triage User Manual/Corbits Triage User Manual.md +++ b/docs/Corbits Triage User Manual/Corbits Triage User Manual.md @@ -171,7 +171,7 @@ rules: A file written before rules carried reasons keeps working as it is. -A coding agent can write the file for you with the skill in [`skills/corbits-triage-setup`](../../skills/corbits-triage-setup/SKILL.md). It looks at the repository, asks only what it can't tell, suggests the starter rules and any that fit, writes `.corbits-triage.yml` and checks it the same way Triage reads it. To use it, copy the `corbits-triage-setup` folder into your coding agent's skills folder, such as `.claude/skills/` in the repository for Claude Code or `.agents/skills/` for agents that read that path, then ask the agent to set up Corbits Triage. +A coding agent can write the file for you with the skill in [`skills/corbits-triage-setup`](../../skills/corbits-triage-setup/SKILL.md). It reads your last 50 merged pull requests on GitHub and suggests rules from what the repository already does, each with the number behind it, such as size limits at the 90th percentile or a linked-issue rule when most pull requests name an issue. You pick which to keep; it suggests and never decides a stricter policy for you. On Bitbucket Data Center, or with no merged pull requests, the size limits stay at the starter's 30 files and 3,000 lines. It then writes or edits `.corbits-triage.yml` and checks it the same way Triage reads it. When nobody is there to answer, as in an agent run, it writes its suggestions and lists each one under **Waiting on you** in the pull request. To use it, copy the `corbits-triage-setup` folder into your coding agent's skills folder, such as `.claude/skills/` in the repository for Claude Code or `.agents/skills/` for agents that read that path, then ask the agent to set up Corbits Triage. A file can also start from another repository's policy with `extends: owner/name` (on Bitbucket, `PROJECT/slug`), and then set only what differs for this repository. This is useful when many repositories should follow the same rules: keep them in one repository's `.corbits-triage.yml` and extend it everywhere else. That repository must be connected to the same workspace, and its own file must not use `extends`. A change merged into it applies to every repository extending it from their next verdict. @@ -200,21 +200,21 @@ On GitHub, a mistake in the file can be caught before it reaches the default bra ```yaml name: Corbits Triage policy on: pull_request -permissions: - checks: read - contents: read - pull-requests: read jobs: - check: + corbits-triage-policy: runs-on: ubuntu-latest + permissions: + checks: read + contents: read + pull-requests: read steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: persist-credentials: false - - uses: corbitsdev/corbits-triage/packages/policy-check-action@0aa2ba2ebeadc02530e15c222306c7baa9b4330c + - uses: corbitsdev/corbits-triage/packages/policy-check-action@844696731de693ac70876ddf37cca016b065f25f ``` -Each pull request then gets a **Corbits Triage policy / check** check. It reads the file exactly as Triage does and fails if Triage could not read it. Each problem is marked on its line in the pull request's **Files changed**, or on the file as a whole when it has no line. +Each pull request then gets a **Corbits Triage policy / corbits-triage-policy** check, named after the workflow and the job. To run it from a workflow you already have, add the `corbits-triage-policy` job to it; the check is then named after that workflow. A workflow written with the earlier job id, `check`, keeps reporting **Corbits Triage policy / check** until you edit it; when you replace that job with this one, change a branch protection rule or ruleset that requires the old name to the new one. It reads the file exactly as Triage does and fails if Triage could not read it. Each problem is marked on its line in the pull request's **Files changed**, or on the file as a whole when it has no line. It also runs the rules that need no model, from the policy on the branch the pull request merges into, such as size, linked issue, merge conflicts and forbidden paths, against the pull request. Each failed rule shows as an error when its failure means Fail, and as a warning otherwise, and the run's summary lists every rule with its result and how many passed. Rules a model answers, such as **focused** or your own written rules, show as skipped, and so does **CI must pass before merge**, since the check is itself still running. A policy that uses `extends` is not checked, and one Triage cannot read shows a warning instead. The check fails when a rule whose failure means Fail fails. It only reads the pull request, never posts to it, and works on pull requests from forks. It needs no Triage account and no secrets, and sets up its own Bun for the job. Both actions are pinned to a full commit SHA, so the job only runs code you chose; to update the check, replace its SHA with a newer commit from `main`. @@ -258,7 +258,7 @@ Each rule's `onFail` says what happens when it fails: - `fail`, the default for every rule but the size rules, regression risk, the judged duplicate rule, sensitive paths and risk: the verdict is **Fail** and the comment asks the author to fix it. - `ask`, the default for the judged duplicate rule, sensitive paths and risk: the verdict is **Unsure** and the comment lists the rule under **Asking a maintainer about:**, so the pull request waits for you in **Needs you** instead of on its author. -- `label`, the default for the size rules and regression risk: the verdict does not change. When the rule fails, or the decision model can't tell, Triage adds its label to the pull request and a line under **Notes:** in its comment. Triage's comment and the reading pane leave a `label` rule out of their rule list; it shows only as a note and a label (on Bitbucket Data Center, only the note). The browser extension, the **Corbits Triage policy / check** check and the local check still list it. The label follows the rule: once the rule passes, such as a pull request cut back under its size limit, Triage takes the label off. The level labels of sensitive paths and risk (`sensitive: medium`, `sensitive: high`, `risk: medium` and `risk: high`) follow the level: when it changes or drops to low, Triage takes the old one off. A label the policy names for a rule follows that rule, even if a person added it; a label the policy does not name for a rule is never taken off. A label a `labels` step in the policy also names is left alone. The label is `size: large` for the size rules, `docs: check` for the docs rule, `sensitive: high` and `risk: high` for sensitive paths and risk, and the rule's id followed by `: check` for any other, such as `regression-risk: check`; `label:` names a different one, but not one starting `Triage:` or `rule: `, a verdict label, or a level label of sensitive paths or risk other than the rule's own, which Triage takes back itself; a `labels` step may not name a level label either. `onFail: label` suits rules that are hygiene rather than a gate, such as size limits an atomic change has to cross. A top-level `onFail` sets it for every rule in the file that does not set its own. The Policy section shows a rule that does not fail the pull request with what it does instead, such as "(on fail, labels size: large)". +- `label`, the default for the size rules and regression risk: the verdict does not change. When the rule fails, or the decision model can't tell, Triage adds its label to the pull request and a line under **Notes:** in its comment. Triage's comment and the reading pane leave a `label` rule out of their rule list; it shows only as a note and a label (on Bitbucket Data Center, only the note). The browser extension, the **Corbits Triage policy / corbits-triage-policy** check and the local check still list it. The label follows the rule: once the rule passes, such as a pull request cut back under its size limit, Triage takes the label off. The level labels of sensitive paths and risk (`sensitive: medium`, `sensitive: high`, `risk: medium` and `risk: high`) follow the level: when it changes or drops to low, Triage takes the old one off. A label the policy names for a rule follows that rule, even if a person added it; a label the policy does not name for a rule is never taken off. A label a `labels` step in the policy also names is left alone. The label is `size: large` for the size rules, `docs: check` for the docs rule, `sensitive: high` and `risk: high` for sensitive paths and risk, and the rule's id followed by `: check` for any other, such as `regression-risk: check`; `label:` names a different one, but not one starting `Triage:` or `rule: `, a verdict label, or a level label of sensitive paths or risk other than the rule's own, which Triage takes back itself; a `labels` step may not name a level label either. `onFail: label` suits rules that are hygiene rather than a gate, such as size limits an atomic change has to cross. A top-level `onFail` sets it for every rule in the file that does not set its own. The Policy section shows a rule that does not fail the pull request with what it does instead, such as "(on fail, labels size: large)". To make a size limit block, give it `onFail: fail` or `onFail: ask`: diff --git a/packages/policy-check-action/README.md b/packages/policy-check-action/README.md index d495729c8..e381f295b 100644 --- a/packages/policy-check-action/README.md +++ b/packages/policy-check-action/README.md @@ -14,21 +14,21 @@ It needs no Triage account and no secrets, and works on pull requests from forks ```yaml on: pull_request -permissions: - checks: read - contents: read - pull-requests: read jobs: - check: + corbits-triage-policy: runs-on: ubuntu-latest + permissions: + checks: read + contents: read + pull-requests: read steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: persist-credentials: false - - uses: corbitsdev/corbits-triage/packages/policy-check-action@0aa2ba2ebeadc02530e15c222306c7baa9b4330c + - uses: corbitsdev/corbits-triage/packages/policy-check-action@844696731de693ac70876ddf37cca016b065f25f ``` -Pin both actions to a full commit SHA; for this Action, a commit from `main`. `github-token` defaults to the job's token. Locally, from here: `bun --conditions=intx-src src/main.ts `; +Pin both actions to a full commit SHA; for this Action, a commit from `main`. The check is named ` / corbits-triage-policy`; a workflow written with the earlier job id, `check`, keeps reporting `Corbits Triage policy / check` until it is edited, and when that job is replaced, a branch protection rule or ruleset that requires the old name must be changed to the new one. `github-token` defaults to the job's token. Locally, from here: `bun --conditions=intx-src src/main.ts `; with `GITHUB_EVENT_NAME=pull_request`, `GITHUB_EVENT_PATH`, `GITHUB_API_URL`, `GITHUB_TOKEN` and `GITHUB_STEP_SUMMARY` set it also runs the rules. ## Check locally diff --git a/packages/triage-contracts/src/setup-skill.test.ts b/packages/triage-contracts/src/setup-skill.test.ts index 234e0e77f..0fb42c51c 100644 --- a/packages/triage-contracts/src/setup-skill.test.ts +++ b/packages/triage-contracts/src/setup-skill.test.ts @@ -8,19 +8,26 @@ import { checkPolicyFile } from "./policy-file.ts"; const REPO = "acme/widgets"; const REFERENCE = readFileSync(join(import.meta.dir, "../../../skills/corbits-triage-setup/reference.md"), "utf8"); -/** The first yaml block under a `## heading` of the setup skill's reference. */ -function yamlUnder(heading: string): string { - const section = REFERENCE.split(`\n## ${heading}\n`)[1]; - const block = section?.match(/```yaml\n([\s\S]*?)```/)?.[1]; - if (block === undefined) throw new Error(`No yaml block under ${heading}.`); - return block; +/** Every yaml block under a `## heading` of the setup skill's reference. */ +function yamlUnder(heading: string): string[] { + const section = REFERENCE.split(`\n## ${heading}\n`)[1]?.split("\n## ")[0]; + if (section === undefined) throw new Error(`No ${heading} section.`); + const blocks = Array.from(section.matchAll(/```yaml\n([\s\S]*?)```/g), (match) => match[1] as string); + if (blocks.length === 0) throw new Error(`No yaml block under ${heading}.`); + return blocks; } test("the setup skill's starter file is the starter policy, and every other example is valid with it", () => { - const starter = yamlUnder("Starter file"); + const [starter] = yamlUnder("Starter file") as [string]; const exported = checkPolicyFile(exportPolicyFile(starterPack(REPO)).text, REPO); expect(checkPolicyFile(starter, REPO)).toEqual(exported); + const [groups] = yamlUnder("Rules") as [string]; + const withGroups = starter.replace(/ {2}- check: sensitive-paths\n(?: {4}.*\n)+/, groups); + expect(withGroups).not.toBe(starter); + expect(checkPolicyFile(withGroups, REPO)).toMatchObject({ ok: true }); for (const heading of ["Extras, written out", "Actions", "Labels", "Blocks and decide"]) { - expect(checkPolicyFile(`${starter}${yamlUnder(heading)}`, REPO)).toMatchObject({ ok: true }); + for (const block of yamlUnder(heading)) { + expect(checkPolicyFile(`${starter}${block.replaceAll("", "octocat")}`, REPO)).toMatchObject({ ok: true }); + } } }); diff --git a/skills/corbits-triage-setup/SKILL.md b/skills/corbits-triage-setup/SKILL.md index 3f0d400f4..a21efb30c 100644 --- a/skills/corbits-triage-setup/SKILL.md +++ b/skills/corbits-triage-setup/SKILL.md @@ -7,42 +7,125 @@ description: Sets up or edits a repository's Corbits Triage policy file, .corbit Write a `.corbits-triage.yml` at the repository root that Corbits Triage accepts, then prove it with the same check the hub runs. -Use only the rules, fields and values in [reference.md](reference.md). Never invent a rule, key or value. If the user asks for something it does not list, say Triage can't do it. +Use only the rules, fields and values in [reference.md](reference.md), which lists every option the file accepts. Never invent a rule, key or value. If the user asks for something it does not list, say Triage can't do it. -## 1. Look at the repository +The skill suggests from what the repository already does, with the numbers it measured, and the maintainer picks; a stricter house policy is the maintainer's choice, not the skill's. -Find out, without asking: +## 1. Analyze the repository + +Find out, without asking. On GitHub, the `gh` commands read the last 50 merged pull requests; run each from the repository root. Each signal says what it suggests. - **Host and name** from `git remote get-url origin`. GitHub (`github.com`): `owner/name`. Bitbucket Data Center (`https://host/scm/PROJECT/repo.git` or `ssh://git@host:7999/PROJECT/repo.git`): `PROJECT/repo`. No remote: the host is unknown. -- **CI**: `.github/workflows/*.yml`, or another CI config (`.gitlab-ci.yml`, `Jenkinsfile`, `.circleci/`, `azure-pipelines.yml`, `bitbucket-pipelines.yml`). -- **CODEOWNERS**: `CODEOWNERS`, `.github/CODEOWNERS` or `docs/CODEOWNERS`. -- **Issue references**: issue links, `Fixes #123`, or keys like `ABC-123` in full commit messages (`git log --format=%B -n 50`) and branch names (`git branch -a`). A `(#123)` at the end of a subject is usually the pull request number from a squash merge, not an issue. -- **Commit style**: whether `git log --format=%s -n 50` mostly reads `feat: …`, `fix(scope): …`, and which types. -- **Vendored or generated code** checked in: `vendor/`, `third_party/`, `dist/`, `generated/`. -- **Existing file**: if `.corbits-triage.yml` or `.corbits-triage.yaml` exists, read it and edit it instead of starting over. If both exist, Triage reads only `.yml`; tell the user and suggest deleting the other. +- **Size**: files and lines at the 90th percentile by nearest rank, each with the pull request at that rank, with lockfiles left out as the size rules leave them out. Suggest `max-files-changed` and `max-lines-changed` at those numbers rounded up, files to a multiple of 5 and lines to a multiple of 100, never below 1 and never above 30 files and 3,000 lines: "p90 is 23 files (#412), suggest 25". -## 2. Ask only what you can't tell + ```sh + gh pr list --state merged --limit 50 --json number,changedFiles,additions,deletions,files --jq 'if length == 0 then "no merged pull requests" else map((.files | map(select(.path | test("(^|/)(bun\\.lockb?|package-lock\\.json|npm-shrinkwrap\\.json|yarn\\.lock|pnpm-lock\\.yaml|Cargo\\.lock|go\\.sum|poetry\\.lock|uv\\.lock|Gemfile\\.lock|composer\\.lock)$")))) as $locks | {number, files: (.changedFiles - ($locks | length)), lines: (.additions + .deletions - ($locks | map(.additions + .deletions) | add // 0))}) | (length * 0.9 | ceil - 1) as $i | sort_by(.files)[$i] as $f | sort_by(.lines)[$i] as $l | "files \($f.files) (#\($f.number)), lines \($l.lines) (#\($l.number)); read \(map("#\(.number)") | join(" "))" end' + ``` -Ask in one message together with the proposal from step 3, giving your suggested answer for each. Ask: +- **Issue references**: how many link an issue. GitHub counts what the rule with tracker `github` accepts: a `#12` at the start or after a space or `(` (`Closes #12`, `see #12`), a GitHub issue link, or a branch named like `123-title`. It reads titles, descriptions and branches; the rule also reads commit messages, which `gh pr list` can't return for 50 pull requests, so the count can be lower than what the rule accepts, never higher. Linear counts a title that starts with a key followed by a colon or in brackets (`ABC-123: …`, `[ABC-123] …`), a closing or `Refs` keyword, in any case, before an uppercase key (`Closes ABC-123`, `refs ABC-123`, not `fixes utf-8`), or a `linear.app` issue link. A branch named like a Linear issue (a 2 to 6 letter key, a number, then a slug of at least two words, such as `abc-123-fix-login`) is a hint, not a count: it is printed apart, and only when its key also starts a title in the Linear shape above or appears in a `linear.app` link among these pull requests, since `fix-123-login-bug` has the same shape. When most link one kind, suggest `linked-issue` with that tracker, `either` when both are common, and none when few do; the branch hint alone suggests nothing: "50 of 50 name a Linear key, suggest linked-issue with tracker linear". -- What should Triage do on Pass, Fail and Unsure beyond its comment and verdict label (a build status on Bitbucket Data Center)? Suggest nothing, or asking code owners to review on Unsure when the repository has CODEOWNERS or is on Bitbucket Data Center (where code owners are its default reviewers). -- Should every pull request link an issue? Only when step 1 found issue references; suggest yes, with the tracker from "When to add". -- Should pull requests never change these paths, even from maintainers? Only when step 1 found vendored or generated code; suggest yes. -- Is a decision model set up in Corbits Triage (Settings → Triage → Decision model)? Only before adding `focused`, `docs` or a `model` rule. -- Is the repository on GitHub or Bitbucket Data Center? Only when there is no remote. -- On GitHub, add a pull request check that checks this file and runs its rules that need no model on every pull request? Suggest yes. + ```sh + gh pr list --state merged --limit 50 --json number,title,body,headRefName --jq 'map({n: .number, h: .title, t: (.title + " " + .body), b: .headRefName}) | ([.[].h | scan("^(?:([A-Z]{2,6})-[0-9]+:|\\[([A-Z]{2,6})-[0-9]+\\])") | map(select(. != null)) | .[0]] + [.[].t | scan("linear\\.app/[^/ ]+/issue/([A-Za-z]{2,6})-[0-9]+") | .[0] | ascii_upcase] | unique) as $keys | map(select((.t | test("(^|[\\s(])#[0-9]+\\b|github\\.com/[^\\s/]+/[^\\s/]+/issues/[0-9]+")) or (.b | test("^[0-9]+-")))) as $github | map(select((.h | test("^([A-Z]{2,6}-[0-9]+:|\\[[A-Z]{2,6}-[0-9]+\\])")) or (.t | test("\\b(?i:close[sd]?|fix(?:es|ed)?|resolve[sd]?|refs?)\\s+[A-Z]{2,6}-[0-9]+\\b|linear\\.app/[^/ ]+/issue/")))) as $linear | map(select(.b | test("^[a-z]{2,6}-[0-9]+-[a-z0-9]+(-[a-z0-9]+)+$"; "i")) | select((.b | capture("^(?[A-Za-z]+)-").k | ascii_upcase) as $k | any($keys[]; . == $k))) as $branch | "of \(length): github \($github | length) (\($github | map("#\(.n)") | join(" "))), linear \($linear | length) (\($linear | map("#\(.n)") | join(" "))); hint: \($branch | length) branches named like a Linear issue with a key from a Linear title or link"' + ``` + +- **Merge style**: how many of the last 50 subjects on the default branch end in `(#N)`, read from `origin` after a fetch that deepens a shallow clone. When most do, pull requests are squash-merged and their commits never reach the branch: suggest `fields: [title]` on `conventional-commits`. The block prints how many commits it read and the pull request numbers it found; when it read fewer than 50, the history is cut short, so say so with the number and suggest nothing from it. + + ```sh + git remote set-head origin --auto >/dev/null + branch="$(git symbolic-ref --short refs/remotes/origin/HEAD | sed 's|^origin/||')" + git fetch --unshallow origin 2>/dev/null || git fetch origin "$branch" + squashed="$(git log --first-parent --format=%s -n 50 "origin/$branch" | grep -oE '\(#[0-9]+\)$' | tr -d '()')" + echo "$(printf '%s' "$squashed" | grep -c .) of $(git rev-list --first-parent --count --max-count=50 "origin/$branch"): $(echo $squashed)" + ``` + +- **Commit style**: how many titles start `type: `, `type(scope): ` or `type!: ` with a type from the rule's default list (`feat`, `fix`, `refactor`, `perf`, `docs`, `test`, `chore`, `ci`, `build`, `style`, `revert`), in any case, as the rule reads them. When most do, suggest `conventional-commits`; when about half do, suggest it with `onFail: label`, or leaving it out: "29 of 50 titles are conventional, suggest conventional-commits off or onFail label". + + ```sh + gh pr list --state merged --limit 50 --json number,title --jq 'map(select(.title | test("^(feat|fix|refactor|perf|docs|test|chore|ci|build|style|revert)(\\([^)]*\\))?!?: "; "i"))) as $c | "\($c | length) of \(length): \($c | map("#\(.number)") | join(" "))"' + ``` + + This prints every word used in that shape and how often. When a change type outside the default list, such as `deps`, starts more than one title, suggest `types` listing the whole default list plus each such type, since `types` replaces the default list; a word that is not a change type, such as `WIP`, stays out. With no type outside the default list, suggest no `types`. + + ```sh + gh pr list --state merged --limit 50 --json title --jq 'map(.title | capture("^(?[A-Za-z]+)(\\([^)]*\\))?!?: ").t | ascii_downcase) | group_by(.) | map("\(.[0]) \(length)") | join(", ")' + ``` + + When the repository is not squash-merged, the rule also reads each commit, so count conventional subjects among the last 200 non-merge commits on the default branch, with the same types, as a stand-in for the pull requests' own commits. When the titles are conventional but most commits are not, suggest `fields: [title]`: "48 of 50 titles but 61 of 200 commits are conventional, suggest fields: [title]". As with merge style, a count of commits read below 200 means a cut-short history: say so with the number. + + ```sh + git remote set-head origin --auto >/dev/null + branch="$(git symbolic-ref --short refs/remotes/origin/HEAD | sed 's|^origin/||')" + git fetch --unshallow origin 2>/dev/null || git fetch origin "$branch" + echo "$(git log --no-merges --format=%s -n 200 "origin/$branch" | grep -ciE '^(feat|fix|refactor|perf|docs|test|chore|ci|build|style|revert)(\([^)]*\))?!?: ') of $(git rev-list --no-merges --count --max-count=200 "origin/$branch")" + ``` + +- **Authors**: who opened them. The people who open most are the candidate maintainers for actions, alongside CODEOWNERS. + + ```sh + gh pr list --state merged --limit 50 --json author --jq 'group_by(.author.login) | map("\(.[0].author.login) \(length)") | join(", ")' + ``` + +- **Vendored or generated code**: which `vendor/`, `third_party/`, `dist/` or `generated/` folders are checked in, and which pull requests changed them. Checked in and changed in some: suggest a `sensitive-paths` group, since the team changes it on purpose. Checked in and changed in none: list `no-forbidden-paths` as an option only, for a person to confirm; it fails maintainers too, so it is never written without one. + + ```sh + git ls-files | awk -F/ '{p=""; for(i=1;i/dev/null +branch="$(git symbolic-ref --short refs/remotes/origin/HEAD | sed 's|^origin/||')" +git fetch --unshallow origin 2>/dev/null || git fetch origin "$branch" +echo "$(git rev-list --count --max-count=50 "origin/$branch") commits read from origin/$branch" +git log --format=%B -n 50 "origin/$branch" +git branch -r +``` + +## 2. Suggest, and let the maintainer pick + +Present the suggestions in one message, one line each with its number and reason, starting from the six starter rules with the measured limits, then the options step 1 found a signal for, then actions. Ask the maintainer to pick, change or drop each. Also ask what step 1 can't tell: + +- Who are the maintainers, for actions that name them? Suggest the candidates from step 1. +- What should Triage do on Pass, Fail and Unsure beyond its comment and verdict label (a build status on Bitbucket Data Center)? Suggest nothing, or requesting code owners' review on Unsure when step 1 found CODEOWNERS or the repository is on Bitbucket Data Center (where code owners are its default reviewers). reference.md's "Actions" lists every step. +- Is a decision model set up in Corbits Triage (Settings → Triage → Decision model)? Only before suggesting `focused`, `docs`, `duplicates-open-pull-request` or a `model` rule. +- Is the repository on GitHub or Bitbucket Data Center? Only when there is no remote. +- On GitHub, unless a workflow already runs the policy check, add a pull request check that checks this file and runs its rules that need no model on every pull request? + +When nobody can answer, as in a run with no person to reply, write the suggested values, add no actions beyond what a suggestion names, add no rule that needs a model or that step 1 lists only as an option, and list every suggestion you wrote under a `Waiting on you` heading in the pull request body (step 6), each with its number. + +## 3. Choose the rules + +Write only what the maintainer picked, or, with nobody to answer, what step 1 suggested. Each option and its settings is in [reference.md](reference.md), with one line on when it fits. For TypeScript or JavaScript, name `conventions`, and for TypeScript `no-any`, as options the maintainer may want; offer `invented-fallbacks` only when `conventions` is not picked, since the `convention` preset already flags invented fallbacks. Write them only when the maintainer picks them. With no CI, `ci-green` passes until CI is added; keep it and say so. ## 4. Write the file -Write `.corbits-triage.yml` at the repository root in the shape of [reference.md](reference.md). Every rule needs `check`, `text`, `reason` and `evidence`. Use `evidence: [maintainer-stated]` unless the user gives pull request numbers or links. Keep `text` and `reason` to one plain sentence each, and update `text` when you change a setting such as `max`. Copy rule blocks from reference.md's "Starter file" and "Extras, written out". Leave out `actions` when there are none, and leave out a verdict that has no steps. +Write `.corbits-triage.yml` at the repository root in the shape of [reference.md](reference.md). Every rule needs `check`, `text`, `reason` and `evidence`. Keep `text` and `reason` to one plain sentence each, and update `text` when you change a setting such as `max`. Write each rule picked in step 3 from the block or table row reference.md documents for it: the "Starter file", "Extras, written out", the `sensitive-paths` `groups` example under "Rules", and the "Built-in rules" table for `hidden-content`, `regression-risk`, `focused`, `docs` and the other rules with no block, each written as `check`, `text`, `reason`, `evidence` and the settings its row lists. Write nothing reference.md does not document. Leave out `actions` when there are none, and leave out a verdict that has no steps. + +A rule the maintainer confirms has `evidence: [maintainer-stated]`, with their pull request numbers or links if they give any. A rule written from a suggestion nobody confirmed cites what was measured: its `evidence` lists up to 20 of the pull request numbers its step 1 command printed (`"#123"`), such as the one at the 90th percentile, and its `reason` says in one line what was measured, such as `90% of the last 50 merged pull requests change at most 25 files.` The file accepts only pull request references, `https://` links and `maintainer-stated` as evidence; any other word, such as `history`, is refused. A starter rule with nothing measured keeps `maintainer-stated` and is listed under `Waiting on you`. + +When the file already exists, edit it in place rather than rewriting it: + +- Keep every rule whose id still fits, with its id, `text` and `evidence`, so its history and labels carry on. Remove one that no longer fits, such as `no-forbidden-paths` for a folder that is gone, and name it with the reason in the pull request. +- Add a picked rule only when the file has no rule with its id or `check`. +- A measured size limit is a suggestion like any other; when it changes the file's limit, the pull request names the old and new number. +- A file with `decide` keeps it and gets no `actions`. An existing `actions` block is kept, and the pull request names any step you change. +- The file holds at most 8 rules of its own, counting the ones it has. When the picked rules would take it past that, keep every existing rule and drop new ones in this order until it fits: `model` rules, `no-any`, `invented-fallbacks`, `conventions`, `no-ai-attribution`, then other `text` rules. Name each dropped rule in the pull request. ## 5. Validate and fix @@ -78,4 +161,8 @@ Never report the file as done before it prints valid. With `extends`, valid does ## 6. Finish -If the user said yes to the pull request check, write the workflow from [reference.md](reference.md) to `.github/workflows/corbits-triage-policy.yml`. It needs no secrets. There is none for Bitbucket Data Center. Then tell the user, in two lines, which rules are on and that the policy applies once the file is merged to the default branch. +If a workflow already uses `corbitsdev/corbits-triage/packages/policy-check-action`, under any job id, leave it as it is and tell the user it already runs; a branch protection rule may require its name. Otherwise, if the user said yes to the pull request check, add the job from reference.md's "Pull request check (GitHub)", pinned to the commit it gives. When a workflow's only event is `pull_request` and nothing could skip it for a pull request changing only `.corbits-triage.yml` (no `paths`, `paths-ignore`, `branches` or `types` filter, no workflow-level or job-level `if`, no other condition), add the job to it; otherwise, or when in doubt, write the workflow to `.github/workflows/corbits-triage-policy.yml`. It needs no secrets. There is none for Bitbucket Data Center. + +When the change goes up as a pull request, its description names each rule removed from an existing file and why, each changed limit and action step, and each rule dropped for the cap. If nobody could answer, it ends with a `Waiting on you` heading that lists every suggestion written, with its number, so a maintainer can change it before merging. + +Then tell the user, in two lines, which rules are on and that the policy applies once the file is merged to the default branch. diff --git a/skills/corbits-triage-setup/reference.md b/skills/corbits-triage-setup/reference.md index dd131dfe5..9a3c24db6 100644 --- a/skills/corbits-triage-setup/reference.md +++ b/skills/corbits-triage-setup/reference.md @@ -28,19 +28,21 @@ A built-in rule is named only by `check`, never `id`, and each built-in may appe | `max-deletions` | `max:` whole number ≥ 1 (required) | no | Fails over `max` lines deleted. | | `no-merge-conflicts` | none | no | Fails when the pull request conflicts with its base. | | `ci-green` | none | no | Fails when checks fail; unconfirmed while they run or when one was cancelled or went stale; a skipped check passes; passes when there are no checks. Only the newest run of each check counts. | -| `linked-issue` | `tracker:` `github`, `linear` or `either` (optional, default `either`) | no | Fails unless the title, description, a commit or the branch name references an issue. `github` reads `#123`, GitHub issue links and branches like `123-title`; `linear` reads any key like `ABC-123` (so Jira keys match too), Linear links and branches like `abc-123-title` in any case. | +| `linked-issue` | `tracker:` `github`, `linear` or `either` (optional, default `either`); `off` is refused in the file, so leave the rule out or set `enabled: false` instead | no | Fails unless the title, description, a commit or the branch name references an issue. `github` reads `#123`, GitHub issue links and branches like `123-title`; `linear` reads any key like `ABC-123` (so Jira keys match too), Linear links and branches like `abc-123-title` in any case. | | `no-forbidden-paths` | `paths:` list of 1–50 globs (required) | no | Fails when any changed file matches; for every author, maintainers too. `*` stays within a folder, `**` crosses folders, and `**/` also matches no folder. | | `conventional-commits` | `types:` list of words (optional, default `feat`, `fix`, `refactor`, `perf`, `docs`, `test`, `chore`, `ci`, `build`, `style`, `revert`); `fields:` `title`, `commits` or both (optional, default both) | no | Fails unless the title and each commit's first line (first 100 commits) start `type: `, `type(scope): ` or `type!: ` with a listed type, in any case. Merges and `Revert "…"` commits are skipped. | | `hidden-content` | none | no | Fails when the title, description, a commit message or a changed path hides text: invisible, tag or bidi override characters, or a link with no visible content. On GitHub, also hidden markup in the description that speaks to a model, other than the repository's pull request template. | | `regression-risk` | `hotFileFixes:` whole number ≥ 1 (optional, default 4); `migrations:` list of 1–50 globs (optional, default `**/migrations/**`, `**/*.sql`) | no | Fails when a changed file (not a test, Markdown doc or lockfile) has at least `hotFileFixes` commits in the last 30 days whose subject starts with a `fix`, `bugfix`, `hotfix` or `revert` type (`fix:`, `fix(scope):`, `Revert "…"`); merges and body text don't count. Also fails when a changed file matches `migrations`. A history that can't be read in full, or a pull request changing more than 300 such files, leaves the rule unconfirmed. Default `onFail` is `label`. | -| `sensitive-paths` | `groups:` map of group name (lowercase words joined by hyphens) to `{ level: high or medium, paths: list of 1–50 globs }` (optional, default `schema`, `auth` high; `ci`, `locks` medium); `failAt:`, `labelAt:` `medium` or `high` (optional, default `high` and `medium`) | no | Fails when a changed file matches a group at `failAt` or above, globs as in `no-forbidden-paths`; below that, from `labelAt`, passes and labels `sensitive: `. Evidence names each group with up to five files. Defaults: `schema` `**/migrations/**`, `**/*.sql`; `auth` `**/auth/**`, `**/secrets/**`; `ci` `.github/**`, `**/Dockerfile`, `**/railway.json`, `**/railway.toml`; `locks` `**/bun.lock`, `**/package-lock.json`, `**/yarn.lock`, `**/pnpm-lock.yaml`. Default `onFail` is `ask`. | +| `sensitive-paths` | `groups:` map of group name (lowercase words joined by hyphens) to `{ level: high or medium, paths: list of 1–50 globs }` (optional, default `schema`, `auth` high; `ci`, `locks` medium); `failAt:`, `labelAt:` `medium` or `high` (optional, default `high` and `medium`) | no | Fails when a changed file matches a group at `failAt` or above, globs as in `no-forbidden-paths`; below that, from `labelAt`, passes and labels `sensitive: `. Evidence names each group with up to five files. Defaults: `schema` `**/migrations/**`, `**/*.sql`; `auth` `**/auth/**`, `**/secrets/**`; `ci` `.github/**`, `**/Dockerfile`, `**/railway.json`, `**/railway.toml`; `locks` `**/bun.lock`, `**/package-lock.json`, `**/yarn.lock`, `**/pnpm-lock.yaml`; writing `groups` replaces all four, so list each one to keep. Default `onFail` is `ask`. | | `risk` | `medium:`, `high:` a number of signals from 1 to 5, or a list of signals where a nested list needs all of its own (optional, default `1` and `[[size, no-tests]]`); `failAt:`, `labelAt:` `medium` or `high` (optional, default `high` and `medium`) | no | Rates the pull request low, medium or high from five signals: `sensitive` (a medium `sensitive-paths` group, default groups if unset; a high group makes it high on its own unless `sensitive-paths` is on), `size` (over the `max-files-changed` or `max-lines-changed` limit, default 30 and 3,000), `no-tests` (source code in a common language changed, not `*.d.ts`, `docs/` or `examples/`, and no test file, `e2e/`, `cypress/` or `__snapshots__/` file), `hot-files` (a file `regression-risk` counts as hot) and `first-time` (a first-time contributor; never on Bitbucket Data Center). Fails at `failAt`; below it, from `labelAt`, passes and labels `risk: `. Unconfirmed when unread history could make a file hot and the rule fail. Default `onFail` is `ask`. | +| `no-duplicate` | none | no | Fails when an earlier open pull request on the same base branch, drafts left out, has the same diff or the same commits, and names it. | +| `duplicates-open-pull-request` | none | yes | The decision model checks the change is not already made by another open pull request on the same base branch that shares a changed path or title words with it. Default `onFail` is `ask`. | | `focused` | none | yes | The decision model checks the change is focused on one thing. | | `docs` | none | yes | The decision model checks docs are updated when behaviour changes. | ### Rules of your own -At most 8 in total. `id` is lowercase letters, digits and single hyphens, at most 64 characters; it must not be a rule id Triage uses (the check names a clash), start with `docs-` or `focused-`, or end in `-file`, `-hunk` or `-slice` followed by numbers. +At most 8 in total. `id` is lowercase letters, digits and single hyphens, at most 64 characters; it must not be a rule id Triage uses (the check names a clash), start with `docs-`, `focused-` or `duplicates-open-pull-request-`, or end in `-file`, `-hunk` or `-slice` followed by numbers. - `check: model` (needs a model): `text` is a statement the decision model answers yes or no about the whole pull request; the rule fails on no. Example `text`: `The title says what changed.` - `check: text` (no model): fails on a phrase match, case ignored. A phrase matches anywhere in the field; there is no prefix or pattern match. A `contains` rule on `description` also reads the description's hidden parts (HTML comments and link addresses); `not-contains` reads only what a reader sees. @@ -51,27 +53,48 @@ At most 8 in total. `id` is lowercase letters, digits and single hyphens, at mos - `presets: [convention]` (TypeScript and JavaScript): invented `?? 0`/`?? []` fallbacks outside tests, nested ternaries, immediately invoked functions, `.then`/`.catch`/`.finally` chains, empty `catch` blocks, suppression comments. - `patterns`: ast-grep rules with `id`, `language` (`typescript` or `javascript`), `message`, `rule` (an ast-grep rule object), and optional `files` (only these paths) and `ignores` (not these). Globs are strings from the repository root, by ast-grep's rules: `*` crosses folders; write `[^a]`, never `[!a]`. Patterns may share an `id` across languages. A pattern replaces a preset's with the same `id` in both languages; to keep checking JavaScript, add a `language: javascript` pattern under the same id. -A model rule costs one model request per pull request and needs a decision model set up in Corbits Triage (Settings → Triage → Decision model). +A model rule, like the built-in `focused`, `docs` and `duplicates-open-pull-request`, costs model requests on every pull request and needs a decision model set up in Corbits Triage (Settings → Triage → Decision model). ### When to add -Start from the four starter rules (SKILL.md step 3). Add an extra only when the repository shows a reason: +The starter file is the baseline. Each extra fits when the repository shows its signal; SKILL.md step 1 measures most of them: | Signal | Extra rule | | --- | --- | -| Issue links or `ABC-123` keys in most commits or branches (not squash-merge `(#123)` suffixes) | `linked-issue`: `github` for `#123`, `linear` for `ABC-123` keys from any tracker, `either` for both | -| Vendored or generated folders that pull requests should never touch | `no-forbidden-paths` listing them | -| Commits mostly follow conventional commits | `conventional-commits`; add `types` only when the history uses a type outside the default list, and `fields: [title]` when pull requests are squash-merged | +| Most merged pull requests link an issue with a closing keyword (`Closes #12`), an issue link or a key like `ABC-123` | `linked-issue`, with `tracker` set to the kind they use: `github`, `linear`, or `either` for both | +| Most pull request titles follow conventional commits | `conventional-commits`; about half suggests `onFail: label` or leaving it out | +| Vendored or generated folders that are checked in and that no merged pull request changed, and a person confirms | `no-forbidden-paths` listing them; when pull requests do change them, a `sensitive-paths` group instead | +| The history uses a commit type outside the default list | `types` on `conventional-commits`, listing the whole default list plus each extra type the history uses, since `types` replaces the default list | +| Pull requests are squash-merged, or their titles are conventional but most commits are not | `fields: [title]` on `conventional-commits` | | The user wants to hear when a pull request hides text from reviewers | `hidden-content` with `onFail: ask` | -| The user bars tool attribution | `no-tool-attribution` below. Add the user's tool trailers to its phrases; never a bare `Co-Authored-By:`, which also fails human co-authors. | -| The user names TypeScript or JavaScript code conventions | `code-patterns`: `presets: [convention]`, plus patterns for what the preset lacks | +| Some merged pull requests carry AI attribution (SKILL.md step 1 counts them), or the maintainer bars it | `no-ai-attribution`, with any other tool trailers they name added to its phrases. Its phrases name AI tools only, so a person's `Co-Authored-By:` line passes, and its `onFail: ask` makes a match Unsure for a maintainer rather than failing it. | +| The maintainer wants added TypeScript or JavaScript kept to the convention preset; never suggested from language alone | `conventions` (the `convention` preset), with `onFail: label` so it flags rather than fails; or, for invented fallbacks alone, `invented-fallbacks` instead, never both, since the preset already flags them | +| The user names TypeScript or JavaScript code conventions the preset lacks | A `code-patterns` rule of patterns, in the shape of `invented-fallbacks` | +| Pull requests for the same change are opened more than once | `no-duplicate` | +| The maintainer wants added TypeScript to use no `any` type; never suggested from language alone | `no-any` below; its `onFail: ask` makes such a pull request Unsure for a maintainer to accept, rather than failing it | | A decision model is set up and pull requests often mix changes | `focused` | | A decision model is set up and the repository keeps user docs beside its code | `docs` | +| A decision model is set up and contributors often open overlapping pull requests | `duplicates-open-pull-request` | `no-forbidden-paths` fails maintainers too, so never list `.github/**` or another path the team edits on purpose unless the user asks. When the user wants a rule to flag pull requests without failing them, such as a docs reminder, add `onFail: label` to it; size rules do this already. When they want a person to decide instead of the author fixing it, use `onFail: ask`. When they want a size limit to block, give it `onFail: fail` or `onFail: ask`. +To add a group to `sensitive-paths`, change the starter's rule. Writing `groups` replaces the default groups, `schema`, `auth`, `ci` and `locks`, so list each one to keep: + +```yaml + - check: sensitive-paths + text: "Flag changes to sensitive paths: schema, auth, infra, ci, locks." + groups: + schema: { level: high, paths: ["**/migrations/**", "**/*.sql"] } + auth: { level: high, paths: ["**/auth/**", "**/secrets/**"] } + infra: { level: high, paths: ["terraform/**"] } + ci: { level: medium, paths: [".github/**", "**/Dockerfile", "**/railway.json", "**/railway.toml"] } + locks: { level: medium, paths: ["**/bun.lock", "**/package-lock.json", "**/yarn.lock", "**/pnpm-lock.yaml"] } + reason: Schema, auth, infrastructure, CI and lockfile changes reach beyond what the diff shows. + evidence: [maintainer-stated] +``` + ## Actions Optional. Without `actions`, Triage only posts its comment and verdict label (a build status on Bitbucket Data Center). Each verdict has an ordered list of at most 20 steps; a verdict left out has none. @@ -79,11 +102,32 @@ Optional. Without `actions`, Triage only posts its comment and verdict label (a ```yaml actions: on: [opened, updated, checks, ready] # optional; this is the default + pass: + - do: approve + when: { author: [{ users: [] }] } + - do: request-review + target: { to: users, users: [] } + when: { author: [external] } + fail: + - do: request-changes unsure: - do: request-review target: { to: codeowners } ``` +`` stands for a maintainer's login. + +Only when `linked-issue` is on, and the maintainer wants it, Fail can close a pull request that names no issue and ask for changes on the rest. Its `fail` list replaces the one above: + +```yaml +actions: + fail: + - do: close + when: { linkedIssue: false } + - do: request-changes + when: { linkedIssue: true } +``` + `on` takes any of `opened`, `updated`, `checks`, `ready`. | `do` | `target` | @@ -192,63 +236,235 @@ rules: ```yaml - check: linked-issue - text: Require a linked issue from GitHub. - tracker: github + text: Require a linked issue from Linear. + tracker: linear reason: Every change is tracked in an issue. evidence: [maintainer-stated] + - check: conventional-commits + text: Titles follow Conventional Commits. + fields: [title] + reason: Pull requests are squash-merged, so the title becomes the commit. + evidence: [maintainer-stated] - check: no-forbidden-paths text: Never accept changes to vendor/**. paths: ["vendor/**"] reason: Generated or vendored code is changed by its own process. evidence: [maintainer-stated] - - check: conventional-commits - text: Require a conventional commit title and messages. - reason: The repository's commits follow conventional commits. + - check: no-duplicate + text: Flag a pull request whose diff or commits match another open one. + reason: Two pull requests for the same change waste a review; the earlier one stays. + evidence: [maintainer-stated] + - check: duplicates-open-pull-request + text: Ask when a pull request overlaps another open one. + reason: A near duplicate needs a person to pick which one goes on. evidence: [maintainer-stated] - - id: no-tool-attribution + - id: no-ai-attribution check: text - text: Descriptions and commits carry no tool attribution. - fields: [description, commits] + text: Titles, descriptions and commits carry no AI attribution. + fields: [title, description, commits] failWhen: contains - phrases: ["Generated with"] - reason: Tool-generated trailers don't belong in descriptions or commits. - evidence: [maintainer-stated] - - id: title-says-what-changed - check: model - text: The title says what changed. - reason: Reviewers pick pull requests from their titles. + phrases: + - "noreply@anthropic.com" + - "Co-Authored-By: Claude" + - "Co-Authored-By: GitHub Copilot" + - "Co-Authored-By: Copilot" + - "Co-Authored-By: ChatGPT" + - "Co-Authored-By: Cursor" + - "Co-Authored-By: Codex" + - "Co-Authored-By: Gemini" + - "Claude-Session:" + - "claude.ai/code/session" + - "Generated with Claude" + - "Generated with [Claude" + onFail: ask + reason: No AI co-author lines, session trailers or links, or generated-with footers. evidence: [maintainer-stated] - id: conventions check: code-patterns - text: Added code follows our conventions. + text: Added code follows the repository's conventions. presets: [convention] + onFail: label + reason: Keeps added code free of invented fallbacks, nested ternaries, IIFEs and promise chains. + evidence: [maintainer-stated] + - id: invented-fallbacks + check: code-patterns + text: Added code handles a missing value instead of inventing an empty one. patterns: - - id: no-console + - id: more-invented-fallbacks language: typescript - message: Remove console logging. - ignores: ["scripts/**"] - rule: { pattern: "console.log($$$ARGS)" } - reason: Review time goes to design, not style. - evidence: [maintainer-stated] + message: Invented fallback (an empty string, || [] or || 0); handle the missing value instead. + ignores: + [ + "**/*.{test,spec}.*", + "**/{test,tests,Tests,__tests__}/**", + "**/*.*{Test,Tests}/**", + ] + rule: + any: + - pattern: $X || [] + - all: + - any: + - pattern: $X ?? "" + - pattern: $X ?? '' + - pattern: $X ?? `` + - pattern: $X || "" + - pattern: $X || '' + - pattern: $X || `` + - not: + any: + - inside: { kind: template_substitution } + - inside: + { + kind: binary_expression, + has: { field: operator, regex: '^\+$' }, + } + - inside: + { + kind: parenthesized_expression, + inside: + { + kind: binary_expression, + has: { field: operator, regex: '^\+$' }, + }, + } + - has: { field: left, kind: subscript_expression } + - has: + { + field: left, + kind: call_expression, + has: + { + field: function, + regex: '\.(at|get|getAttribute)$', + }, + } + - all: + - pattern: $X || 0 + - not: + any: + - inside: + { + kind: binary_expression, + has: { field: operator, regex: "^[-+*/%]$" }, + } + - inside: + { + kind: parenthesized_expression, + inside: + { + kind: binary_expression, + has: { field: operator, regex: "^[-+*/%]$" }, + }, + } + - inside: { kind: augmented_assignment_expression } + - id: more-invented-fallbacks + language: javascript + message: Invented fallback (an empty string, || [] or || 0); handle the missing value instead. + ignores: + [ + "**/*.{test,spec}.*", + "**/{test,tests,Tests,__tests__}/**", + "**/*.*{Test,Tests}/**", + ] + rule: + any: + - pattern: $X || [] + - all: + - any: + - pattern: $X ?? "" + - pattern: $X ?? '' + - pattern: $X ?? `` + - pattern: $X || "" + - pattern: $X || '' + - pattern: $X || `` + - not: + any: + - inside: { kind: template_substitution } + - inside: + { + kind: binary_expression, + has: { field: operator, regex: '^\+$' }, + } + - inside: + { + kind: parenthesized_expression, + inside: + { + kind: binary_expression, + has: { field: operator, regex: '^\+$' }, + }, + } + - has: { field: left, kind: subscript_expression } + - has: + { + field: left, + kind: call_expression, + has: + { + field: function, + regex: '\.(at|get|getAttribute)$', + }, + } + - all: + - pattern: $X || 0 + - not: + any: + - inside: + { + kind: binary_expression, + has: { field: operator, regex: "^[-+*/%]$" }, + } + - inside: + { + kind: parenthesized_expression, + inside: + { + kind: binary_expression, + has: { field: operator, regex: "^[-+*/%]$" }, + }, + } + - inside: { kind: augmented_assignment_expression } + onFail: label + reason: A fallback belongs only where a real default exists. + evidence: + - maintainer-stated + - id: no-any + check: code-patterns + text: Added code uses no any type. + patterns: + - id: no-any + language: typescript + message: "`any` turns type checking off; name the type, or take unknown and narrow it." + ignores: + [ + "**/*.{test,spec}.*", + "**/{test,tests,Tests,__tests__}/**", + "**/*.*{Test,Tests}/**", + ] + rule: { kind: predefined_type, regex: "^any$" } + onFail: ask + reason: An any type hides the mistakes type checking is there to catch. + evidence: + - maintainer-stated ``` ## Pull request check (GitHub) -`.github/workflows/corbits-triage-policy.yml`: +Pin the policy-check action to the commit below. When a workflow's only event is `pull_request` and nothing could skip it for a pull request changing only `.corbits-triage.yml` (no `paths`, `paths-ignore`, `branches` or `types` filter, no workflow-level or job-level `if`, no other condition), add the `corbits-triage-policy` job to its `jobs`; otherwise, or when in doubt, write the whole file as `.github/workflows/corbits-triage-policy.yml`. When a workflow already uses this action, under any job id, leave it as it is. The check appears on each pull request as ` / corbits-triage-policy`. A workflow written with the earlier job id, `check`, keeps reporting `Corbits Triage policy / check` until someone edits it; when that job is replaced with this one, change a branch protection rule or ruleset that requires the old name to the new one: ```yaml name: Corbits Triage policy on: pull_request -permissions: - checks: read - contents: read - pull-requests: read jobs: - check: + corbits-triage-policy: runs-on: ubuntu-latest + permissions: + checks: read + contents: read + pull-requests: read steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: persist-credentials: false - - uses: corbitsdev/corbits-triage/packages/policy-check-action@0aa2ba2ebeadc02530e15c222306c7baa9b4330c + - uses: corbitsdev/corbits-triage/packages/policy-check-action@844696731de693ac70876ddf37cca016b065f25f ```