From 78c6d82e998816b64e9cc3988a947fb7a0d47b3d Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Tue, 27 Oct 2026 14:27:10 -0700 Subject: [PATCH 1/4] ci(policy): run the corbits-triage rules and actions --- .corbits-triage.yml | 251 +++++++++++++++++++++++++++++++++--- .github/workflows/check.yml | 14 ++ CONTRIBUTING.md | 1 + 3 files changed, 248 insertions(+), 18 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/.corbits-triage.yml b/.corbits-triage.yml index 7e516c3..29058cb 100644 --- a/.corbits-triage.yml +++ b/.corbits-triage.yml @@ -1,49 +1,260 @@ # Corbits Triage policy. rules: - check: max-files-changed - text: Flag pull requests over 30 files changed. - max: 30 - reason: Set in the portal before the policy file. + text: Flag pull requests over 25 files changed. + max: 25 + reason: Merged pull requests here touch 3 files at the median and 21 at the 90th percentile. evidence: - maintainer-stated - check: max-lines-changed - text: Flag pull requests over 5,000 lines changed. - max: 5000 - reason: Set in the portal before the policy file. + text: Flag pull requests over 800 lines changed. + max: 800 + reason: Merged pull requests here change 151 lines at the median and 747 at the 90th percentile. evidence: - maintainer-stated - check: focused text: Check the change is focused on one thing. - reason: Set in the portal before the policy file. + reason: A change that does several things is harder to review and to revert. evidence: - maintainer-stated - check: docs text: Check docs are updated when behavior changes. - reason: Set in the portal before the policy file. + reason: AGENTS.md asks for docs and tests to change alongside code. evidence: - maintainer-stated - enabled: false - check: linked-issue text: Require a linked issue from GitHub or Linear. tracker: either - reason: Set in the portal before the policy file. + reason: CONTRIBUTING.md says a pull request without a linked issue is closed. evidence: - maintainer-stated - check: no-merge-conflicts text: Flag merge conflicts with the base branch. - reason: Set in the portal before the policy file. + reason: A pull request with conflicts can't be merged as it is. evidence: - maintainer-stated - check: ci-green text: CI must pass before merge. - reason: Set in the portal before the policy file. + reason: A change is not ready until CI passes. evidence: - maintainer-stated - - check: no-forbidden-paths - text: Never accept changes to node_modules/**. - paths: - - node_modules/** - reason: Set in the portal before the policy file. + - check: sensitive-paths + text: Note changes to CI configuration and the lockfile. + groups: + ci: + level: medium + paths: + - .github/** + locks: + level: medium + paths: + - "**/bun.lock" + reason: CI and lockfile changes reach beyond the diff they show. + evidence: + - maintainer-stated + - check: risk + text: Ask about high-risk pull requests and label medium-risk ones. + reason: A large change with no test touched needs a maintainer's eye. + evidence: + - maintainer-stated + - check: hidden-content + text: Flag text hidden in the pull request. + reason: Hidden text can steer the rules a model judges. + evidence: + - maintainer-stated + - check: no-duplicate + text: Flag a pull request whose diff or commits match another open one. + reason: Two pull requests for the same change split one review. + evidence: + - maintainer-stated + - check: duplicates-open-pull-request + text: Ask when a pull request overlaps another open one. + reason: An overlapping pull request needs a person to pick which one goes in. + evidence: + - maintainer-stated + - check: conventional-commits + text: Titles and commits follow Conventional Commits. + reason: Every non-merge commit on main in the last 50 uses one of these types. + evidence: + - maintainer-stated + - id: no-ai-attribution + check: text + text: Titles, descriptions and commits carry no AI attribution. + fields: [title, description, commits] + failWhen: contains + phrases: + - "Co-Authored-By: Claude" + - "Claude-Session:" + - "claude.ai/code/session" + - "noreply@anthropic.com" + - "Generated with Claude" + - "Generated with [Claude" + onFail: ask + reason: Tool-generated trailers and footers don't belong in commits or descriptions. + evidence: + - maintainer-stated + - id: conventions + check: code-patterns + 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: more-invented-fallbacks + language: typescript + 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}.*"] + rule: { kind: predefined_type, regex: "^any$" } + onFail: ask + reason: Callers of the eval API rely on its types, and any turns them off. evidence: - maintainer-stated actions: @@ -53,13 +264,17 @@ actions: author: - users: - thegreataxios + - brianjfox - do: request-review target: - to: codeowners + to: users + users: + - thegreataxios when: author: - external fail: + - do: close - do: request-changes unsure: - do: assign diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index b5f54b6..ac7099f 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -19,3 +19,17 @@ jobs: - run: bun install --frozen-lockfile - run: bun run check - run: bun run build + + policy-check: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 5 + 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@28a902e9a6e2f7ec53c581425b72d97c6800fc89 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5275230 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +A pull request without a linked issue is closed. From a853799617a1abfaabf57e58ee239e6409cd09ab Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Tue, 27 Oct 2026 16:34:53 -0700 Subject: [PATCH 2/4] ci(policy): make size limits a risk signal --- .corbits-triage.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.corbits-triage.yml b/.corbits-triage.yml index 29058cb..d7cdba5 100644 --- a/.corbits-triage.yml +++ b/.corbits-triage.yml @@ -3,12 +3,14 @@ rules: - check: max-files-changed text: Flag pull requests over 25 files changed. max: 25 + onFail: label reason: Merged pull requests here touch 3 files at the median and 21 at the 90th percentile. evidence: - maintainer-stated - check: max-lines-changed text: Flag pull requests over 800 lines changed. max: 800 + onFail: label reason: Merged pull requests here change 151 lines at the median and 747 at the 90th percentile. evidence: - maintainer-stated From 61f9c6e046776109fd764191a2e9ae12a9899474 Mon Sep 17 00:00:00 2001 From: "user.email" Date: Fri, 30 Oct 2026 12:18:01 -0700 Subject: [PATCH 3/4] ci(policy): add convention checks without code review --- .corbits-triage.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.corbits-triage.yml b/.corbits-triage.yml index d7cdba5..73cde5f 100644 --- a/.corbits-triage.yml +++ b/.corbits-triage.yml @@ -276,7 +276,6 @@ actions: author: - external fail: - - do: close - do: request-changes unsure: - do: assign From bf1d577a2b9df5cbd5e00e8a169e5f555094314e Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 31 Oct 2026 16:28:08 -0700 Subject: [PATCH 4/4] style: format .corbits-triage.yml --- .corbits-triage.yml | 56 ++++++++------------------------------------- 1 file changed, 10 insertions(+), 46 deletions(-) diff --git a/.corbits-triage.yml b/.corbits-triage.yml index 73cde5f..88d0698 100644 --- a/.corbits-triage.yml +++ b/.corbits-triage.yml @@ -112,11 +112,7 @@ rules: language: typescript message: Invented fallback (an empty string, || [] or || 0); handle the missing value instead. ignores: - [ - "**/*.{test,spec}.*", - "**/{test,tests,Tests,__tests__}/**", - "**/*.*{Test,Tests}/**", - ] + ["**/*.{test,spec}.*", "**/{test,tests,Tests,__tests__}/**", "**/*.*{Test,Tests}/**"] rule: any: - pattern: $X || [] @@ -131,40 +127,26 @@ rules: - not: any: - inside: { kind: template_substitution } - - inside: - { - kind: binary_expression, - has: { field: operator, regex: '^$' }, - } + - inside: { kind: binary_expression, has: { field: operator, regex: "^$" } } - inside: { kind: parenthesized_expression, inside: - { - kind: binary_expression, - has: { field: operator, regex: '^$' }, - }, + { 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)$', - }, + has: { field: function, regex: '\.(at|get|getAttribute)$' }, } - all: - pattern: $X || 0 - not: any: - inside: - { - kind: binary_expression, - has: { field: operator, regex: "^[-+*/%]$" }, - } + { kind: binary_expression, has: { field: operator, regex: "^[-+*/%]$" } } - inside: { kind: parenthesized_expression, @@ -179,11 +161,7 @@ rules: language: javascript message: Invented fallback (an empty string, || [] or || 0); handle the missing value instead. ignores: - [ - "**/*.{test,spec}.*", - "**/{test,tests,Tests,__tests__}/**", - "**/*.*{Test,Tests}/**", - ] + ["**/*.{test,spec}.*", "**/{test,tests,Tests,__tests__}/**", "**/*.*{Test,Tests}/**"] rule: any: - pattern: $X || [] @@ -198,40 +176,26 @@ rules: - not: any: - inside: { kind: template_substitution } - - inside: - { - kind: binary_expression, - has: { field: operator, regex: '^$' }, - } + - inside: { kind: binary_expression, has: { field: operator, regex: "^$" } } - inside: { kind: parenthesized_expression, inside: - { - kind: binary_expression, - has: { field: operator, regex: '^$' }, - }, + { 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)$', - }, + has: { field: function, regex: '\.(at|get|getAttribute)$' }, } - all: - pattern: $X || 0 - not: any: - inside: - { - kind: binary_expression, - has: { field: operator, regex: "^[-+*/%]$" }, - } + { kind: binary_expression, has: { field: operator, regex: "^[-+*/%]$" } } - inside: { kind: parenthesized_expression,