From 955cad5239126ad1d4cc2b65a164d8c5526bb144 Mon Sep 17 00:00:00 2001 From: Matthew Ballou Date: Mon, 28 Sep 2026 21:22:06 -0400 Subject: [PATCH 1/8] Gate CI to ready, up-to-date pull requests --- .github/workflows/ci.yml | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c803f9..a93b289 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,12 +1,33 @@ name: CI on: - push: - branches: [main] pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true jobs: + gate: + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + run: ${{ steps.base.outputs.run }} + steps: + - id: base + env: + GH_TOKEN: ${{ github.token }} + run: | + behind=$(gh api "repos/${{ github.repository }}/compare/${{ github.base_ref }}...${{ github.event.pull_request.head.sha }}" --jq .behind_by) + if [ "$behind" = "0" ]; then echo "run=true" >> "$GITHUB_OUTPUT" + else echo "::notice::This branch is $behind commits behind ${{ github.base_ref }}. Update it to run CI."; echo "run=false" >> "$GITHUB_OUTPUT"; fi + check: + needs: gate + if: needs.gate.outputs.run == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 1781a5ff9189842290dacd076b84d2c350ae1d9a Mon Sep 17 00:00:00 2001 From: Matthew Ballou Date: Fri, 2 Oct 2026 13:23:09 -0400 Subject: [PATCH 2/8] Carry the shared delegation block in CLAUDE.md and AGENTS.md Installed from laila's docs/agent-delegation.md by scripts/repo_rules.py. Edit it there, not here. --- AGENTS.md | 14 ++++++++++++++ CLAUDE.md | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index cafdbbc..fdf8a40 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,4 +16,18 @@ You are not alone. Two skills reach the desk from any repo: `/herdr` to see and talk to the other agents running, and `/asking-laila` to pin a note or a question for Laila. Use them when you are stuck, cannot get further, or would otherwise stop to ask Matthew. She wakes on a pin and answers. + +## Linear + +This repo's Linear project: **Dread Majesty**. Its tickets are the roadmap +Matthew reads. Claude and Codex both reach Linear through its MCP. + +- When a brief names a ticket (`Linear: MAT-12`), read that ticket before you + start. +- Post progress and the outcome as comments on it, with a link to what you made. +- Move it to `In Progress` when you start and `In Review` when you finish. He + moves it to `Done`. If this repo's own rules say how its tickets move, follow + them instead. +- Never touch another project's tickets. Never put client or work content in + Linear. diff --git a/CLAUDE.md b/CLAUDE.md index 91cf2b6..03bd426 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -203,4 +203,18 @@ You are not alone. Two skills reach the desk from any repo: `/herdr` to see and talk to the other agents running, and `/asking-laila` to pin a note or a question for Laila. Use them when you are stuck, cannot get further, or would otherwise stop to ask Matthew. She wakes on a pin and answers. + +## Linear + +This repo's Linear project: **Dread Majesty**. Its tickets are the roadmap +Matthew reads. Claude and Codex both reach Linear through its MCP. + +- When a brief names a ticket (`Linear: MAT-12`), read that ticket before you + start. +- Post progress and the outcome as comments on it, with a link to what you made. +- Move it to `In Progress` when you start and `In Review` when you finish. He + moves it to `Done`. If this repo's own rules say how its tickets move, follow + them instead. +- Never touch another project's tickets. Never put client or work content in + Linear. From dccfc90a6c42be1f63667d0dcc01eed28ae768e5 Mon Sep 17 00:00:00 2001 From: Matthew Ballou Date: Fri, 2 Oct 2026 14:01:05 -0400 Subject: [PATCH 3/8] Format the Linear block the way Prettier wants it --- AGENTS.md | 1 + CLAUDE.md | 1 + 2 files changed, 2 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index fdf8a40..946d3e8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,4 +30,5 @@ Matthew reads. Claude and Codex both reach Linear through its MCP. them instead. - Never touch another project's tickets. Never put client or work content in Linear. + diff --git a/CLAUDE.md b/CLAUDE.md index 03bd426..dd4feaf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -217,4 +217,5 @@ Matthew reads. Claude and Codex both reach Linear through its MCP. them instead. - Never touch another project's tickets. Never put client or work content in Linear. + From 00859ade361629fd7267cfc2d6c5d48bf87f53d0 Mon Sep 17 00:00:00 2001 From: Matthew Ballou Date: Fri, 2 Oct 2026 14:01:06 -0400 Subject: [PATCH 4/8] Keep the CI gate from failing a PR and cap job run time --- .github/workflows/ci.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a93b289..0a22576 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,7 @@ jobs: gate: if: github.event.pull_request.draft == false runs-on: ubuntu-latest + timeout-minutes: 5 permissions: contents: read outputs: @@ -20,15 +21,20 @@ jobs: - id: base env: GH_TOKEN: ${{ github.token }} + # The gate never fails a PR. A behind branch skips the check; an API error + # runs it, since a skipped check would read as a pass. run: | - behind=$(gh api "repos/${{ github.repository }}/compare/${{ github.base_ref }}...${{ github.event.pull_request.head.sha }}" --jq .behind_by) - if [ "$behind" = "0" ]; then echo "run=true" >> "$GITHUB_OUTPUT" + behind=$(gh api "repos/${{ github.repository }}/compare/${{ github.base_ref }}...${{ github.event.pull_request.head.sha }}" --jq .behind_by) || behind="" + if [ -z "$behind" ]; then echo "::warning::Could not compare with ${{ github.base_ref }}. Running CI anyway."; echo "run=true" >> "$GITHUB_OUTPUT" + elif [ "$behind" = "0" ]; then echo "run=true" >> "$GITHUB_OUTPUT" else echo "::notice::This branch is $behind commits behind ${{ github.base_ref }}. Update it to run CI."; echo "run=false" >> "$GITHUB_OUTPUT"; fi check: needs: gate if: needs.gate.outputs.run == 'true' runs-on: ubuntu-latest + # The whole job takes under a minute. A hang should cost minutes, not hours. + timeout-minutes: 15 steps: - uses: actions/checkout@v4 From 94de747d524730172b4d712c25b4fd25a8edf3c1 Mon Sep 17 00:00:00 2001 From: Matthew Ballou Date: Sat, 3 Oct 2026 09:55:01 -0400 Subject: [PATCH 5/8] Set read-only permissions, a 3-minute gate, and a ref fallback in the CI group --- .github/workflows/ci.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a22576..2ce985b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,17 +4,18 @@ on: pull_request: types: [opened, synchronize, reopened, ready_for_review] +permissions: + contents: read + concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: gate: if: github.event.pull_request.draft == false runs-on: ubuntu-latest - timeout-minutes: 5 - permissions: - contents: read + timeout-minutes: 3 outputs: run: ${{ steps.base.outputs.run }} steps: From 41fc0186f53ea0385c5183f89d6a0316234f8fa8 Mon Sep 17 00:00:00 2001 From: Matthew Ballou Date: Sat, 3 Oct 2026 09:59:59 -0400 Subject: [PATCH 6/8] Fail the CI gate on a branch behind its base instead of skipping the check --- .github/workflows/ci.yml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ce985b..b6f972c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,23 +16,24 @@ jobs: if: github.event.pull_request.draft == false runs-on: ubuntu-latest timeout-minutes: 3 - outputs: - run: ${{ steps.base.outputs.run }} steps: - - id: base + - name: Require the branch to be current with its base env: GH_TOKEN: ${{ github.token }} - # The gate never fails a PR. A behind branch skips the check; an API error - # runs it, since a skipped check would read as a pass. + REPO: ${{ github.repository }} + BASE: ${{ github.base_ref }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + # Fails, rather than skips, a PR behind its base: a skipped check reads + # as a pass with no tests run. run: | - behind=$(gh api "repos/${{ github.repository }}/compare/${{ github.base_ref }}...${{ github.event.pull_request.head.sha }}" --jq .behind_by) || behind="" - if [ -z "$behind" ]; then echo "::warning::Could not compare with ${{ github.base_ref }}. Running CI anyway."; echo "run=true" >> "$GITHUB_OUTPUT" - elif [ "$behind" = "0" ]; then echo "run=true" >> "$GITHUB_OUTPUT" - else echo "::notice::This branch is $behind commits behind ${{ github.base_ref }}. Update it to run CI."; echo "run=false" >> "$GITHUB_OUTPUT"; fi + behind=$(gh api "repos/$REPO/compare/$BASE...$HEAD_SHA" --jq .behind_by) + if [ "$behind" != "0" ]; then + echo "::error::This branch is $behind commits behind $BASE. Update it to run CI." + exit 1 + fi check: needs: gate - if: needs.gate.outputs.run == 'true' runs-on: ubuntu-latest # The whole job takes under a minute. A hang should cost minutes, not hours. timeout-minutes: 15 From b7ace00e92e25f81bb6bfb7c2b17a61b05cccd7c Mon Sep 17 00:00:00 2001 From: Matthew Ballou Date: Sat, 3 Oct 2026 10:21:33 -0400 Subject: [PATCH 7/8] Add draft state to the CI concurrency group --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b6f972c..e85d427 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event.pull_request.draft }} cancel-in-progress: true jobs: From 3b8b65c4b3edca66f3a52977c287a8121947ed86 Mon Sep 17 00:00:00 2001 From: Matthew Ballou Date: Sat, 3 Oct 2026 10:28:27 -0400 Subject: [PATCH 8/8] Pass the CI gate with a notice on a branch behind its base --- .github/workflows/ci.yml | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e85d427..7cdb1b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,24 +16,32 @@ jobs: if: github.event.pull_request.draft == false runs-on: ubuntu-latest timeout-minutes: 3 + outputs: + run: ${{ steps.base.outputs.run }} steps: - - name: Require the branch to be current with its base + - id: base env: GH_TOKEN: ${{ github.token }} REPO: ${{ github.repository }} BASE: ${{ github.base_ref }} HEAD_SHA: ${{ github.event.pull_request.head.sha }} - # Fails, rather than skips, a PR behind its base: a skipped check reads - # as a pass with no tests run. + # The gate never fails a PR. A behind branch skips the check with a notice; + # an API error runs it, since a skipped check would read as a pass. run: | - behind=$(gh api "repos/$REPO/compare/$BASE...$HEAD_SHA" --jq .behind_by) - if [ "$behind" != "0" ]; then - echo "::error::This branch is $behind commits behind $BASE. Update it to run CI." - exit 1 + behind=$(gh api "repos/$REPO/compare/$BASE...$HEAD_SHA" --jq .behind_by) || behind="" + if [ -z "$behind" ]; then + echo "::warning::Could not compare with $BASE. Running CI anyway." + echo "run=true" >> "$GITHUB_OUTPUT" + elif [ "$behind" = "0" ]; then + echo "run=true" >> "$GITHUB_OUTPUT" + else + echo "::notice::This branch is $behind commits behind $BASE. Update it to run CI." + echo "run=false" >> "$GITHUB_OUTPUT" fi check: needs: gate + if: needs.gate.outputs.run == 'true' runs-on: ubuntu-latest # The whole job takes under a minute. A hang should cost minutes, not hours. timeout-minutes: 15