From e0489a25976d54197be848b01f818f20425bc9f3 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sat, 6 Jun 2026 12:52:01 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=94=A7=20(ci):=20Add=20reusable=20ci-?= =?UTF-8?q?success=20aggregate-gate=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci-success.yml | 53 ++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/ci-success.yml diff --git a/.github/workflows/ci-success.yml b/.github/workflows/ci-success.yml new file mode 100644 index 0000000..31b1135 --- /dev/null +++ b/.github/workflows/ci-success.yml @@ -0,0 +1,53 @@ +# Reusable aggregate "CI Success" gate. +# +# Branch-protection rules should require a single check named "CI Success" +# instead of every individual job. Each repo's CI workflow calls this once, +# passing the `needs.*.result` values of its real jobs as a JSON string, and +# this workflow fails unless every upstream job succeeded. +# +# Usage from a caller workflow: +# +# ci-success: +# name: CI Success +# if: always() +# needs: [lint, test, build] +# uses: ai-agent-assembly/.github/.github/workflows/ci-success.yml@master +# with: +# needs-json: ${{ toJSON(needs) }} +# +# The gate passes only when no upstream job has a result of `failure` or +# `cancelled`. Skipped jobs are treated as non-blocking. +name: CI Success + +on: + workflow_call: + inputs: + needs-json: + description: >- + JSON object of the caller's `needs` context, i.e. ${{ toJSON(needs) }}. + The gate inspects each job's `result` field. + required: true + type: string + +permissions: + contents: read + +jobs: + ci-success: + name: CI Success + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Verify no upstream job failed or was cancelled + env: + NEEDS_JSON: ${{ inputs.needs-json }} + run: | + echo "Upstream job results:" + echo "$NEEDS_JSON" | jq -r 'to_entries[] | " \(.key): \(.value.result)"' + failed=$(echo "$NEEDS_JSON" | jq -r \ + '[to_entries[] | select(.value.result == "failure" or .value.result == "cancelled") | .key] | join(", ")') + if [ -n "$failed" ]; then + echo "::error::CI Success gate failed. Blocking jobs: ${failed}" + exit 1 + fi + echo "All upstream jobs succeeded or were skipped. CI Success." From 3c463439f2f5a84e23130046b1d092ed0fa67316 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sat, 6 Jun 2026 12:52:01 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E2=9E=95=20(ci):=20Add=20rust-ci=20org=20s?= =?UTF-8?q?tarter=20workflow=20template?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- workflow-templates/rust-ci.properties.json | 6 ++ workflow-templates/rust-ci.yml | 71 ++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 workflow-templates/rust-ci.properties.json create mode 100644 workflow-templates/rust-ci.yml diff --git a/workflow-templates/rust-ci.properties.json b/workflow-templates/rust-ci.properties.json new file mode 100644 index 0000000..89cc68c --- /dev/null +++ b/workflow-templates/rust-ci.properties.json @@ -0,0 +1,6 @@ +{ + "name": "Rust CI (Agent Assembly playbook)", + "description": "Cargo fmt + clippy + test with path-scoped triggers, PR-only concurrency cancel, Linux-default matrix (multi-OS on default branch / tags), least-privilege permissions, job timeouts, and a reusable CI Success gate.", + "iconName": "rust", + "categories": ["Rust", "Continuous integration"] +} diff --git a/workflow-templates/rust-ci.yml b/workflow-templates/rust-ci.yml new file mode 100644 index 0000000..ac016f7 --- /dev/null +++ b/workflow-templates/rust-ci.yml @@ -0,0 +1,71 @@ +# Org starter: Rust CI encoding the 8-principle playbook. +# See the org CONTRIBUTING.md "CI/CD playbook" section for the rationale and +# the measured-result evidence in agent-assembly's +# docs/src/benchmarks/ci-cd-pipeline-performance.md. +name: CI + +on: + push: + branches: [$default-branch] + tags: ["v*"] + paths: + - "**/*.rs" + - "**/Cargo.toml" + - "**/Cargo.lock" + - ".github/workflows/**" + pull_request: + paths: + - "**/*.rs" + - "**/Cargo.toml" + - "**/Cargo.lock" + - ".github/workflows/**" + +# Principle: cancel superseded runs — but only on PRs, never on +# $default-branch / tag pushes (those must each complete for release safety). +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +# Principle: least privilege at the top level; widen per-job only when needed. +permissions: + contents: read + +jobs: + lint: + name: fmt + clippy + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + - run: cargo fmt --all --check + - run: cargo clippy --all-targets -- -D warnings + + test: + name: test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + # Principle: Linux-default. Multi-OS only on $default-branch / tag + # pushes; PRs run Linux only to save minutes. + os: ${{ fromJSON((github.event_name == 'push' && (github.ref == 'refs/heads/$default-branch' || startsWith(github.ref, 'refs/tags/'))) && '["ubuntu-latest", "macos-latest", "windows-latest"]' || '["ubuntu-latest"]') }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - run: cargo test --workspace --all-features + + # Principle: single aggregate gate. Make branch protection require only + # "CI Success"; it fails if any job above failed or was cancelled. + ci-success: + name: CI Success + if: always() + needs: [lint, test] + uses: ai-agent-assembly/.github/.github/workflows/ci-success.yml@master + with: + needs-json: ${{ toJSON(needs) }} From 56d0a1496743bde0c69a2c6a63be25dd1b59fd5f Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sat, 6 Jun 2026 12:52:01 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E2=9E=95=20(ci):=20Add=20node-ci=20org=20s?= =?UTF-8?q?tarter=20workflow=20template?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- workflow-templates/node-ci.properties.json | 6 ++ workflow-templates/node-ci.yml | 83 ++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 workflow-templates/node-ci.properties.json create mode 100644 workflow-templates/node-ci.yml diff --git a/workflow-templates/node-ci.properties.json b/workflow-templates/node-ci.properties.json new file mode 100644 index 0000000..c216496 --- /dev/null +++ b/workflow-templates/node-ci.properties.json @@ -0,0 +1,6 @@ +{ + "name": "Node / TypeScript CI (Agent Assembly playbook)", + "description": "pnpm lint + typecheck + test with path-scoped triggers, PR-only concurrency cancel, Linux-default matrix (multi-OS on default branch / tags), least-privilege permissions, job timeouts, and a reusable CI Success gate.", + "iconName": "nodejs", + "categories": ["Node", "TypeScript", "JavaScript", "Continuous integration"] +} diff --git a/workflow-templates/node-ci.yml b/workflow-templates/node-ci.yml new file mode 100644 index 0000000..d39300c --- /dev/null +++ b/workflow-templates/node-ci.yml @@ -0,0 +1,83 @@ +# Org starter: Node / TypeScript CI encoding the 8-principle playbook. +# See the org CONTRIBUTING.md "CI/CD playbook" section for the rationale and +# the measured-result evidence in agent-assembly's +# docs/src/benchmarks/ci-cd-pipeline-performance.md. +name: CI + +on: + push: + branches: [$default-branch] + tags: ["v*"] + paths: + - "**/*.ts" + - "**/*.tsx" + - "**/*.js" + - "**/*.jsx" + - "**/package.json" + - "**/pnpm-lock.yaml" + - ".github/workflows/**" + pull_request: + paths: + - "**/*.ts" + - "**/*.tsx" + - "**/*.js" + - "**/*.jsx" + - "**/package.json" + - "**/pnpm-lock.yaml" + - ".github/workflows/**" + +# Principle: cancel superseded runs — but only on PRs, never on +# $default-branch / tag pushes. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +# Principle: least privilege at the top level. +permissions: + contents: read + +jobs: + lint: + name: lint + typecheck + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm lint + - run: pnpm typecheck + + test: + name: test (node ${{ matrix.node }}, ${{ matrix.os }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + # Principle: Linux-default. Multi-OS only on $default-branch / tag + # pushes; PRs run Linux only. + os: ${{ fromJSON((github.event_name == 'push' && (github.ref == 'refs/heads/$default-branch' || startsWith(github.ref, 'refs/tags/'))) && '["ubuntu-latest", "macos-latest", "windows-latest"]' || '["ubuntu-latest"]') }} + node: [20] + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm test + + # Principle: single aggregate gate. + ci-success: + name: CI Success + if: always() + needs: [lint, test] + uses: ai-agent-assembly/.github/.github/workflows/ci-success.yml@master + with: + needs-json: ${{ toJSON(needs) }} From f1431d847b90c6a9521e1f97b48551e52e49885a Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sat, 6 Jun 2026 12:52:01 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E2=9E=95=20(ci):=20Add=20python-ci=20org?= =?UTF-8?q?=20starter=20workflow=20template?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- workflow-templates/python-ci.properties.json | 6 ++ workflow-templates/python-ci.yml | 72 ++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 workflow-templates/python-ci.properties.json create mode 100644 workflow-templates/python-ci.yml diff --git a/workflow-templates/python-ci.properties.json b/workflow-templates/python-ci.properties.json new file mode 100644 index 0000000..3e8833a --- /dev/null +++ b/workflow-templates/python-ci.properties.json @@ -0,0 +1,6 @@ +{ + "name": "Python CI (Agent Assembly playbook)", + "description": "uv-based ruff + mypy + pytest with path-scoped triggers, PR-only concurrency cancel, Linux-default matrix (multi-OS on default branch / tags), least-privilege permissions, job timeouts, and a reusable CI Success gate.", + "iconName": "python", + "categories": ["Python", "Continuous integration"] +} diff --git a/workflow-templates/python-ci.yml b/workflow-templates/python-ci.yml new file mode 100644 index 0000000..8f9be8f --- /dev/null +++ b/workflow-templates/python-ci.yml @@ -0,0 +1,72 @@ +# Org starter: Python CI encoding the 8-principle playbook. +# See the org CONTRIBUTING.md "CI/CD playbook" section for the rationale and +# the measured-result evidence in agent-assembly's +# docs/src/benchmarks/ci-cd-pipeline-performance.md. +name: CI + +on: + push: + branches: [$default-branch] + tags: ["v*"] + paths: + - "**/*.py" + - "**/pyproject.toml" + - "**/uv.lock" + - ".github/workflows/**" + pull_request: + paths: + - "**/*.py" + - "**/pyproject.toml" + - "**/uv.lock" + - ".github/workflows/**" + +# Principle: cancel superseded runs — but only on PRs, never on +# $default-branch / tag pushes. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +# Principle: least privilege at the top level. +permissions: + contents: read + +jobs: + lint: + name: ruff + mypy + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - run: uv sync + - run: uv run ruff check . + - run: uv run ruff format --check . + - run: uv run mypy . + + test: + name: test (py ${{ matrix.python }}, ${{ matrix.os }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 25 + strategy: + fail-fast: false + matrix: + # Principle: Linux-default. Multi-OS only on $default-branch / tag + # pushes; PRs run Linux only. + os: ${{ fromJSON((github.event_name == 'push' && (github.ref == 'refs/heads/$default-branch' || startsWith(github.ref, 'refs/tags/'))) && '["ubuntu-latest", "macos-latest", "windows-latest"]' || '["ubuntu-latest"]') }} + python: ["3.12"] + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + with: + python-version: ${{ matrix.python }} + - run: uv sync + - run: uv run pytest + + # Principle: single aggregate gate. + ci-success: + name: CI Success + if: always() + needs: [lint, test] + uses: ai-agent-assembly/.github/.github/workflows/ci-success.yml@master + with: + needs-json: ${{ toJSON(needs) }} From 94057209b61c5e201e126076278b41d08c7b1714 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sat, 6 Jun 2026 12:52:01 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E2=9E=95=20(ci):=20Add=20go-ci=20org=20sta?= =?UTF-8?q?rter=20workflow=20template?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- workflow-templates/go-ci.properties.json | 6 +++ workflow-templates/go-ci.yml | 69 ++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 workflow-templates/go-ci.properties.json create mode 100644 workflow-templates/go-ci.yml diff --git a/workflow-templates/go-ci.properties.json b/workflow-templates/go-ci.properties.json new file mode 100644 index 0000000..f4ed5f0 --- /dev/null +++ b/workflow-templates/go-ci.properties.json @@ -0,0 +1,6 @@ +{ + "name": "Go CI (Agent Assembly playbook)", + "description": "golangci-lint + go test with path-scoped triggers, PR-only concurrency cancel, Linux-default matrix (multi-OS on default branch / tags), least-privilege permissions, job timeouts, and a reusable CI Success gate.", + "iconName": "go", + "categories": ["Go", "Continuous integration"] +} diff --git a/workflow-templates/go-ci.yml b/workflow-templates/go-ci.yml new file mode 100644 index 0000000..d39c102 --- /dev/null +++ b/workflow-templates/go-ci.yml @@ -0,0 +1,69 @@ +# Org starter: Go CI encoding the 8-principle playbook. +# See the org CONTRIBUTING.md "CI/CD playbook" section for the rationale and +# the measured-result evidence in agent-assembly's +# docs/src/benchmarks/ci-cd-pipeline-performance.md. +name: CI + +on: + push: + branches: [$default-branch] + tags: ["v*"] + paths: + - "**/*.go" + - "**/go.mod" + - "**/go.sum" + - ".github/workflows/**" + pull_request: + paths: + - "**/*.go" + - "**/go.mod" + - "**/go.sum" + - ".github/workflows/**" + +# Principle: cancel superseded runs — but only on PRs, never on +# $default-branch / tag pushes. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +# Principle: least privilege at the top level. +permissions: + contents: read + +jobs: + lint: + name: golangci-lint + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: stable + - uses: golangci/golangci-lint-action@v6 + + test: + name: test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + # Principle: Linux-default. Multi-OS only on $default-branch / tag + # pushes; PRs run Linux only. + os: ${{ fromJSON((github.event_name == 'push' && (github.ref == 'refs/heads/$default-branch' || startsWith(github.ref, 'refs/tags/'))) && '["ubuntu-latest", "macos-latest", "windows-latest"]' || '["ubuntu-latest"]') }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: stable + - run: go test ./... + + # Principle: single aggregate gate. + ci-success: + name: CI Success + if: always() + needs: [lint, test] + uses: ai-agent-assembly/.github/.github/workflows/ci-success.yml@master + with: + needs-json: ${{ toJSON(needs) }} From 884cc770f1743973a99629d73f57aef0c78b017d Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sat, 6 Jun 2026 12:52:01 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E2=9E=95=20(ci):=20Add=20docs-ci=20org=20s?= =?UTF-8?q?tarter=20workflow=20template?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- workflow-templates/docs-ci.properties.json | 6 +++ workflow-templates/docs-ci.yml | 63 ++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 workflow-templates/docs-ci.properties.json create mode 100644 workflow-templates/docs-ci.yml diff --git a/workflow-templates/docs-ci.properties.json b/workflow-templates/docs-ci.properties.json new file mode 100644 index 0000000..3bdc21e --- /dev/null +++ b/workflow-templates/docs-ci.properties.json @@ -0,0 +1,6 @@ +{ + "name": "Docs CI (Agent Assembly playbook)", + "description": "markdownlint + lychee link check with path-scoped triggers, PR-only concurrency cancel, least-privilege permissions, job timeouts, and a reusable CI Success gate (docs build Linux-only, so no OS matrix).", + "iconName": "book", + "categories": ["Documentation", "Continuous integration"] +} diff --git a/workflow-templates/docs-ci.yml b/workflow-templates/docs-ci.yml new file mode 100644 index 0000000..fe40d7a --- /dev/null +++ b/workflow-templates/docs-ci.yml @@ -0,0 +1,63 @@ +# Org starter: Docs CI encoding the 8-principle playbook. +# Documentation builds are Linux-only by nature, so there is no OS matrix; +# the remaining seven principles still apply. See the org CONTRIBUTING.md +# "CI/CD playbook" section and the measured-result evidence in +# agent-assembly's docs/src/benchmarks/ci-cd-pipeline-performance.md. +name: CI + +on: + push: + branches: [$default-branch] + tags: ["v*"] + paths: + - "**/*.md" + - "**/*.mdx" + - "docs/**" + - ".github/workflows/**" + pull_request: + paths: + - "**/*.md" + - "**/*.mdx" + - "docs/**" + - ".github/workflows/**" + +# Principle: cancel superseded runs — but only on PRs, never on +# $default-branch / tag pushes. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +# Principle: least privilege at the top level. +permissions: + contents: read + +jobs: + lint: + name: markdownlint + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - uses: DavidAnson/markdownlint-cli2-action@v18 + with: + globs: "**/*.md" + + links: + name: link check + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - uses: lycheeverse/lychee-action@v2 + with: + args: "--no-progress './**/*.md'" + fail: true + + # Principle: single aggregate gate. + ci-success: + name: CI Success + if: always() + needs: [lint, links] + uses: ai-agent-assembly/.github/.github/workflows/ci-success.yml@master + with: + needs-json: ${{ toJSON(needs) }} From 34ec3ef5ac17728f094e3283de23890377bd3fb4 Mon Sep 17 00:00:00 2001 From: Chisanan232 Date: Sat, 6 Jun 2026 12:52:01 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=93=9D=20(docs):=20Document=208-princ?= =?UTF-8?q?iple=20CI=20playbook=20in=20CONTRIBUTING?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- CONTRIBUTING.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 28a7d70..58c71a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -97,6 +97,46 @@ PRs always target `master`, even when your branch was created from another featu Keep PRs focused. One concern per PR — don't bundle unrelated changes. If a single ticket needs more than ~500 lines of diff, split it into a sequence of stacked PRs. +## Continuous integration — the 8-principle playbook + +Every repo's CI is hand-rolled, which is why the same gaps recur. To stop +retrofitting, the org `.github` repo ships **starter workflows** that bake these +eight principles in, so new and empty repos start compliant. Pick one from the +**Actions → New workflow** screen (look for "… (Agent Assembly playbook)"): +`Rust CI`, `Node / TypeScript CI`, `Python CI`, `Go CI`, or `Docs CI`. + +The reference rationale and the **measured-result evidence** (before/after CI +minutes and wall-clock) live in `agent-assembly`'s +[`docs/src/benchmarks/ci-cd-pipeline-performance.md`](https://github.com/AI-agent-assembly/agent-assembly/blob/master/docs/src/benchmarks/ci-cd-pipeline-performance.md). + +| # | Principle | How the starters apply it | +|---|---|---| +| 1 | **Path-scoped triggers** | `on.push` / `on.pull_request` list `paths:` so doc-only or unrelated edits don't spend CI minutes. | +| 2 | **Concurrency cancel** | `concurrency` cancels superseded runs **only on PRs** (`cancel-in-progress: ${{ github.event_name == 'pull_request' }}`) — `$default-branch` and tag pushes always run to completion. | +| 3 | **Single `CI Success` gate** | An aggregate job (`needs: [...]` + `if: always()`) is the one required check. Branch protection requires only `CI Success`, not every job. | +| 4 | **Linux-default matrices** | The OS matrix is `["ubuntu-latest"]` on PRs and only fans out to macOS/Windows on `$default-branch` / tag pushes. | +| 5 | **Least-privilege permissions** | A top-level `permissions: contents: read`; jobs widen scope only when they must. | +| 6 | **Job timeouts** | Every job sets `timeout-minutes` so a hung step can't burn the runner budget. | +| 7 | **Reusable gate** | The `CI Success` logic is a reusable workflow (`ci-success.yml`, `on: workflow_call`); repos `uses:` it instead of copy-pasting (see below). | +| 8 | **Cache the toolchain** | Each starter caches its package/build artifacts (`Swatinem/rust-cache`, `setup-node … cache: pnpm`, `setup-uv`, `setup-go`) to keep warm runs fast. | + +### Reusable `CI Success` gate + +Instead of copy-pasting the aggregate gate, call the org reusable workflow: + +```yaml +ci-success: + name: CI Success + if: always() + needs: [lint, test] + uses: ai-agent-assembly/.github/.github/workflows/ci-success.yml@master + with: + needs-json: ${{ toJSON(needs) }} +``` + +It fails if any upstream job's `result` is `failure` or `cancelled`; skipped +jobs are non-blocking. Make `CI Success` the only required status check. + ## Code review - At least **one approval** is required before merge.