From eea2235694ab00b4efb4946e40d46478df22b221 Mon Sep 17 00:00:00 2001 From: amcheste-ai-agent <278991699+amcheste-ai-agent@users.noreply.github.com> Date: Mon, 11 May 2026 18:00:19 -0400 Subject: [PATCH] chore(brand): em-dash sweep + scorecard publish_results fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two changes from an alanchester-brand audit pass. 1. Em-dash sweep across 12 prose files (177 dashes removed). Mechanical replacement of ` — ` with `. ` and capitalization of the following letter when it was lowercase. Code blocks and markdown table rows protected from substitution. Post-sweep audit produced 5 matches, all abbreviation false positives (e.g., i.e., E.g.) left as-is. Files swept: README.md, CONTRIBUTING.md, CLAUDE.md, CHANGELOG.md, docs/design/claude-bot-account.md, docs/tooling/dev-tooling-stack.md, docs/workflows/ci-automation.md, docs/workflows/branching-and-releases.md, docs/philosophies/{branching-strategy,release-cadence, testing,security-posture}.md. 2. .github/workflows/scorecard.yml: fix publish_results gating so the OpenSSF Scorecard badge actually populates. Before: publish_results only fired on `refs/heads/main`. This repo's default branch is `develop` (intentional, until a stable first release switches default to main). Scheduled runs fired on develop and succeeded but never published. Badge returned 404 from the Scorecard API. After: publish_results uses `github.ref_name == github.event.repository.default_branch`. Works through the develop-then-main lifecycle without further code changes; the workflow tracks whatever the GitHub default branch is set to. Also added develop to the push triggers so pushes to develop re-run the analysis (publish gate still keeps the score canonical to default). The same pattern bug lives in repo-template's scorecard.yml; propagating the fix there is queued as a follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com> --- .github/workflows/scorecard.yml | 6 +-- CHANGELOG.md | 2 +- CLAUDE.md | 4 +- CONTRIBUTING.md | 20 ++++---- README.md | 28 +++++------ docs/design/claude-bot-account.md | 50 +++++++++---------- docs/philosophies/branching-strategy.md | 26 +++++----- docs/philosophies/release-cadence.md | 20 ++++---- docs/philosophies/security-posture.md | 38 +++++++-------- docs/philosophies/testing.md | 34 ++++++------- docs/tooling/dev-tooling-stack.md | 20 ++++---- docs/workflows/branching-and-releases.md | 32 ++++++------ docs/workflows/ci-automation.md | 62 ++++++++++++------------ 13 files changed, 171 insertions(+), 171 deletions(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 3c02fb4..2915c74 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -5,7 +5,7 @@ on: schedule: - cron: '30 1 * * 1' # Every Monday at 01:30 UTC push: - branches: [main] + branches: [main, develop] workflow_dispatch: permissions: read-all @@ -29,7 +29,7 @@ jobs: with: results_file: results.sarif results_format: sarif - publish_results: ${{ github.ref == 'refs/heads/main' }} + publish_results: ${{ github.ref_name == github.event.repository.default_branch }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: @@ -38,7 +38,7 @@ jobs: retention-days: 5 - uses: github/codeql-action/upload-sarif@d4b3ca9fa7f69d38bfcd667bdc45bc373d16277e # v4 - if: github.ref == 'refs/heads/main' + if: github.ref_name == github.event.repository.default_branch continue-on-error: true with: sarif_file: results.sarif diff --git a/CHANGELOG.md b/CHANGELOG.md index fb72e55..f11a41b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Philosophies: Branching Strategy, Release Cadence & Semver Discipline, Security Posture, Testing. - Workflows: Branching & Releases, CI Automation Surface. - Tooling: Development Tooling Stack (Claude Desktop for planning, Claude Code as primary execution, Cursor as supplemental). -- Design notes: Claude Bot Account for AI-Authored PRs (design approved — Epsilon agent now active). +- Design notes: Claude Bot Account for AI-Authored PRs (design approved. Epsilon agent now active). - Rendered overview diagrams under `docs/images/` for branching strategy, release cadence, and Claude Bot Account. - Release infrastructure: `VERSION`, `scripts/bump-version.sh`, `CHANGELOG.md`. - `CONTRIBUTING.md` rewritten for the handbook as canonical source for branching, commits, and releases across all repos. diff --git a/CLAUDE.md b/CLAUDE.md index 139ddcf..b1faeee 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ This file is read by Claude Code at the start of every session in this repo. ## About This Repo -This is my personal engineering handbook — a documentation-primarily repo that captures the philosophies, workflows, and tooling that shape how I build software. +This is my personal engineering handbook. A documentation-primarily repo that captures the philosophies, workflows, and tooling that shape how I build software. - Content lives in `docs/` as markdown files, with `README.md` acting as the landing page / table of contents. - There is no application code to lint, test, or build. Treat this repo as a writing project: prioritize clarity, consistency of voice, and structure. @@ -27,7 +27,7 @@ This is my personal engineering handbook — a documentation-primarily repo that - **Branch model:** `main` = latest release. `develop` = integration branch. - Always branch from `develop`, never commit directly - PRs always target `develop` -- `main` is only updated via CLI merge (`git merge --no-ff origin/develop`) by `/publish-release` — **never via a GitHub PR**. GitHub's merge button squash-merges by default, dropping ancestry and causing conflicts on the next release. +- `main` is only updated via CLI merge (`git merge --no-ff origin/develop`) by `/publish-release`. **never via a GitHub PR**. GitHub's merge button squash-merges by default, dropping ancestry and causing conflicts on the next release. - Conventional commits: `feat:`, `fix:`, `docs:`, `chore:` ### Scripting Standards diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d444c8..5a24303 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,11 +1,11 @@ # Contributing to the Engineering Handbook -This repo **is** the canonical source for branching strategy, commit convention, and release process — so this file is about contributing *to the handbook itself*, not a copy of the rules documented inside it. +This repo **is** the canonical source for branching strategy, commit convention, and release process. So this file is about contributing *to the handbook itself*, not a copy of the rules documented inside it. For the rules, see: -- [Branching Strategy philosophy](docs/philosophies/branching-strategy.md) — the *why* -- [Branching & Releases workflow](docs/workflows/branching-and-releases.md) — the *how* +- [Branching Strategy philosophy](docs/philosophies/branching-strategy.md). The *why* +- [Branching & Releases workflow](docs/workflows/branching-and-releases.md). The *how* --- @@ -13,21 +13,21 @@ For the rules, see: This is a documentation repo, so the contribution model is the same as any other repo I own, just applied to prose instead of code. -1. Branch from `develop` — `docs/` or `chore/` as appropriate. +1. Branch from `develop`. `docs/` or `chore/` as appropriate. 2. Edit existing chapters when possible; only add a new doc under `docs/` when a topic is substantial enough to stand alone. -3. Commit with [Conventional Commits](https://www.conventionalcommits.org/) — most changes will be `docs:` or `chore:`. +3. Commit with [Conventional Commits](https://www.conventionalcommits.org/). Most changes will be `docs:` or `chore:`. 4. Open a PR targeting `develop`. 5. CI (`Lint`, `Commit Lint`) must pass before merging. ## What belongs where -- **Philosophies** (`docs/philosophies/`) — durable *why* content. Principles that justify decisions. Changes slowly. -- **Workflows** (`docs/workflows/`) — concrete *how* content. Steps, commands, tooling. Changes as tools evolve. -- **Tooling** (`docs/tooling/`) — what I use and how it fits together. Start at [`docs/tooling/dev-tooling-stack.md`](docs/tooling/dev-tooling-stack.md). -- **Design notes** (`docs/design/`) — brainstorm-level proposals. Not polished specs. +- **Philosophies** (`docs/philosophies/`). Durable *why* content. Principles that justify decisions. Changes slowly. +- **Workflows** (`docs/workflows/`). Concrete *how* content. Steps, commands, tooling. Changes as tools evolve. +- **Tooling** (`docs/tooling/`). What I use and how it fits together. Start at [`docs/tooling/dev-tooling-stack.md`](docs/tooling/dev-tooling-stack.md). +- **Design notes** (`docs/design/`). Brainstorm-level proposals. Not polished specs. If a note is truly repo-specific (only applies to one project), it belongs in that repo, not here. If it applies to all my repos, it belongs here. ## Releases -This repo uses the same release ceremony as every other repo I own — documented in the workflow doc linked above. +This repo uses the same release ceremony as every other repo I own. Documented in the workflow doc linked above. diff --git a/README.md b/README.md index 080ad59..c3f0558 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # Alan's Engineering Handbook -**How I build software — and the philosophies behind it.** +**How I build software. And the philosophies behind it.** [![Validate](https://github.com/amcheste/engineering-handbook/actions/workflows/validate.yml/badge.svg)](https://github.com/amcheste/engineering-handbook/actions/workflows/validate.yml) [![Version](https://img.shields.io/github/v/tag/amcheste/engineering-handbook?label=version&sort=semver&color=0B0B0C)](https://github.com/amcheste/engineering-handbook/releases) @@ -13,7 +13,7 @@ --- -This repo is my personal engineering handbook — the philosophies, workflows, and tooling that shape how I design, build, and ship software. It is written for myself first and shared publicly as a reference for anyone curious about how an engineer-first, AI-amplified practice actually works day to day. +This repo is my personal engineering handbook. The philosophies, workflows, and tooling that shape how I design, build, and ship software. It is written for myself first and shared publicly as a reference for anyone curious about how an engineer-first, AI-amplified practice actually works day to day. It is a living document. Expect it to grow and change as my thinking evolves. @@ -21,30 +21,30 @@ It is a living document. Expect it to grow and change as my thinking evolves. ### Philosophies -The *why* — principles that explain the decisions behind how I work. These change slowly. +The *why*. Principles that explain the decisions behind how I work. These change slowly. -- [Branching Strategy](docs/philosophies/branching-strategy.md) — why every repo runs on `develop` / `main` with CLI-merged releases and conventional commits. -- [Merge Strategy](docs/philosophies/merge-strategy.md) — why I rebase-merge PRs, never squash, and how that protects bot/human authorship. -- [Release Cadence & Semver Discipline](docs/philosophies/release-cadence.md) — why releases are a deliberate ceremony, why the monthly patch cycle, and how conventional commits drive the version number. -- [Security Posture](docs/philosophies/security-posture.md) — why every repo ships with the same SAST, Scorecard, CODEOWNERS, and SLA baseline from day one. -- [Testing](docs/philosophies/testing.md) — why I write the tests I write, and why I don't write the ones I don't. +- [Branching Strategy](docs/philosophies/branching-strategy.md). Why every repo runs on `develop` / `main` with CLI-merged releases and conventional commits. +- [Merge Strategy](docs/philosophies/merge-strategy.md). Why I rebase-merge PRs, never squash, and how that protects bot/human authorship. +- [Release Cadence & Semver Discipline](docs/philosophies/release-cadence.md). Why releases are a deliberate ceremony, why the monthly patch cycle, and how conventional commits drive the version number. +- [Security Posture](docs/philosophies/security-posture.md). Why every repo ships with the same SAST, Scorecard, CODEOWNERS, and SLA baseline from day one. +- [Testing](docs/philosophies/testing.md). Why I write the tests I write, and why I don't write the ones I don't. ### Workflows -The *how* — concrete steps, commands, and tooling that implement the philosophies. These change as tools evolve. +The *how*. Concrete steps, commands, and tooling that implement the philosophies. These change as tools evolve. -- [Branching & Releases](docs/workflows/branching-and-releases.md) — branch model, day-to-day flow, and the release ceremony. -- [CI Automation Surface](docs/workflows/ci-automation.md) — the GitHub Actions workflows that ship with every repo, what each does, and when it fires. +- [Branching & Releases](docs/workflows/branching-and-releases.md). Branch model, day-to-day flow, and the release ceremony. +- [CI Automation Surface](docs/workflows/ci-automation.md). The GitHub Actions workflows that ship with every repo, what each does, and when it fires. ### Development Tooling Stack -- [Development Tooling Stack](docs/tooling/dev-tooling-stack.md) — the AI-assisted development stack I use, and the end-to-end workflows that run on top of it. +- [Development Tooling Stack](docs/tooling/dev-tooling-stack.md). The AI-assisted development stack I use, and the end-to-end workflows that run on top of it. ### Design notes -Brainstorm-level design docs capturing how I'm thinking about specific engineering problems. Not polished specs — working documents that evolve as decisions get made. +Brainstorm-level design docs capturing how I'm thinking about specific engineering problems. Not polished specs. Working documents that evolve as decisions get made. -- [Claude Bot Account for AI-Authored PRs](docs/design/claude-bot-account.md) — separating authorship from review so AI-written code has a clean audit trail. +- [Claude Bot Account for AI-Authored PRs](docs/design/claude-bot-account.md). Separating authorship from review so AI-written code has a clean audit trail. _More chapters are in progress._ diff --git a/docs/design/claude-bot-account.md b/docs/design/claude-bot-account.md index e5774d3..1763657 100644 --- a/docs/design/claude-bot-account.md +++ b/docs/design/claude-bot-account.md @@ -1,20 +1,20 @@ # Design: Claude Bot Account for AI-Authored PRs -**Status:** Design Approved — Pending Implementation +**Status:** Design Approved. Pending Implementation **Author:** Alan Chester **Date:** 2026-04-22 (revised 2026-04-23) --- -![Claude Bot Account flow: Alan describes intent to Claude Code; Claude Code writes the code and commits as amcheste-ai-agent; GitHub opens the PR and CODEOWNERS auto-requests amcheste as reviewer — preserving a full audit trail](../images/claude-bot-account.png) +![Claude Bot Account flow: Alan describes intent to Claude Code; Claude Code writes the code and commits as amcheste-ai-agent; GitHub opens the PR and CODEOWNERS auto-requests amcheste as reviewer. Preserving a full audit trail](../images/claude-bot-account.png) ## Problem -Claude Code writes the lion's share of the code in my projects. Alan reviews and approves it. But because Claude pushes using Alan's GitHub credentials, every PR shows Alan as both the **author** and the only possible **reviewer** — and GitHub blocks self-review. Beyond the mechanical issue of not being able to request review in Graphite, Linear, or GitHub, this collapses three distinct properties of a healthy code-review workflow: +Claude Code writes the lion's share of the code in my projects. Alan reviews and approves it. But because Claude pushes using Alan's GitHub credentials, every PR shows Alan as both the **author** and the only possible **reviewer**. And GitHub blocks self-review. Beyond the mechanical issue of not being able to request review in Graphite, Linear, or GitHub, this collapses three distinct properties of a healthy code-review workflow: -- **Auditability** — the record doesn't distinguish what Alan wrote from what Claude wrote. From the git log and PR list, every line of code looks like Alan's. -- **Traceability** — there's no durable signal separating "AI-generated code that was reviewed" from "human-authored code." Months later, when investigating a regression, that distinction is load-bearing. -- **Accountability** — "AI wrote, human approved" is the contract. Without a visible separation, the contract is invisible. An external reviewer has no way to verify the contract was followed. +- **Auditability**. The record doesn't distinguish what Alan wrote from what Claude wrote. From the git log and PR list, every line of code looks like Alan's. +- **Traceability**. There's no durable signal separating "AI-generated code that was reviewed" from "human-authored code." Months later, when investigating a regression, that distinction is load-bearing. +- **Accountability**. "AI wrote, human approved" is the contract. Without a visible separation, the contract is invisible. An external reviewer has no way to verify the contract was followed. This design fixes all three by splitting authorship from review at the account level. @@ -47,7 +47,7 @@ The core question: **who owns the commit and the PR?** This depends on who is dr #### Scenario 2: Claude-driven with human steering in Cursor -> Alan has Claude open a file in Cursor via the side panel. Alan reads the code to give better guidance, or edits the file directly to show Claude what he means. Claude is still driving the overall task — Alan's edits are guidance, not primary authorship. +> Alan has Claude open a file in Cursor via the side panel. Alan reads the code to give better guidance, or edits the file directly to show Claude what he means. Claude is still driving the overall task. Alan's edits are guidance, not primary authorship. | | | |---|---| @@ -61,7 +61,7 @@ The core question: **who owns the commit and the PR?** This depends on who is dr #### Scenario 3: Automated from Linear ticket -> A Linear ticket is auto-assigned to Claude (via scheduled task or webhook). Claude picks it up, writes the code, commits, and raises the PR — all without Alan initiating a session. +> A Linear ticket is auto-assigned to Claude (via scheduled task or webhook). Claude picks it up, writes the code, commits, and raises the PR. All without Alan initiating a session. | | | |---|---| @@ -73,7 +73,7 @@ The core question: **who owns the commit and the PR?** This depends on who is dr #### Scenario 4: Human-driven coding -> Alan opens a terminal or Cursor and writes code himself. He may ask Claude for guidance (explain a function, suggest an approach, review a snippet) but Alan is driving — he's on the keyboard, he decides what to commit, he runs `git commit`. +> Alan opens a terminal or Cursor and writes code himself. He may ask Claude for guidance (explain a function, suggest an approach, review a snippet) but Alan is driving. He's on the keyboard, he decides what to commit, he runs `git commit`. | | | |---|---| @@ -96,7 +96,7 @@ The beauty of this model is the enforcement mechanism is dead simple. Claude Cod ### Edge Case: Scenario 2 Boundary -Scenario 2 has one subtle edge case worth calling out. If Alan edits a file in Cursor and then *Alan* runs `git commit` from Cursor's terminal (not Claude Code), the commit will be under `amcheste` — because Alan's git config is the default. But per the intent of Scenario 2, if Claude is driving the overall task, Claude should be the one committing. +Scenario 2 has one subtle edge case worth calling out. If Alan edits a file in Cursor and then *Alan* runs `git commit` from Cursor's terminal (not Claude Code), the commit will be under `amcheste`. Because Alan's git config is the default. But per the intent of Scenario 2, if Claude is driving the overall task, Claude should be the one committing. The rule of thumb: **if Claude is driving, let Claude commit.** Don't commit from Cursor's terminal while Claude is orchestrating the work. If you make edits to steer Claude in the right direction, save the file and let Claude pick up the changes and do the commit. This keeps the ownership model clean without any tooling gymnastics. @@ -125,7 +125,7 @@ The rule of thumb: **if Claude is driving, let Claude commit.** Don't commit fro approves / merges review queue ``` -> 📣 **Rendering note:** this ASCII diagram is the canonical reference. A rendered version (DALL-E or similar) is planned — see the issue tracking this doc for the image asset. +> 📣 **Rendering note:** this ASCII diagram is the canonical reference. A rendered version (DALL-E or similar) is planned. See the issue tracking this doc for the image asset. ## Components @@ -143,24 +143,24 @@ The rule of thumb: **if Claude is driving, let Claude commit.** Don't commit fro | Bio | "Bot account for AI-authored PRs. Code written by Claude, reviewed by @amcheste." | **Setup steps:** -1. ✅ Create the GitHub account (`amcheste.ai.agent@gmail.com` — done). +1. ✅ Create the GitHub account (`amcheste.ai.agent@gmail.com`. Done). 2. Add the clear bio above and a distinguishing avatar. 3. Record the account's noreply commit email from *Settings → Emails*. ### 2. GitHub App (not a PAT) -A GitHub App is the right auth surface for this — not a personal access token. The reasons: +A GitHub App is the right auth surface for this. Not a personal access token. The reasons: - **No token rotation.** The App issues short-lived installation tokens automatically. - **Better audit logs.** App actions show up as `[bot]` in the GitHub UI, so logs make the human-vs-bot distinction visible for free. - **Scoped installation.** Install on specific repos (or all owned repos) without granting account-wide access. -- **Rate limits.** App installations get their own bucket — won't contend with Alan's personal API usage. +- **Rate limits.** App installations get their own bucket. Won't contend with Alan's personal API usage. - **Showcases engineering rigor.** This is the "right way" pattern; worth the one-time setup cost. **Setup steps:** 1. Log into the `amcheste-ai-agent` account. 2. **Settings → Developer settings → GitHub Apps → New GitHub App**. -3. App name: `amcheste-ai-agent` (or similar — must be globally unique on GitHub). +3. App name: `amcheste-ai-agent` (or similar. Must be globally unique on GitHub). 4. Homepage URL: point at the engineering-handbook repo. 5. Webhook: disabled (we don't need event webhooks for this workflow). 6. Repository permissions: @@ -178,7 +178,7 @@ A GitHub App is the right auth surface for this — not a personal access token. 7. Account permissions: none needed. 8. "Where can this GitHub App be installed?" → **Only on this account**. 9. Create the App, generate a **private key** (`.pem`), download and store it in **Apple Passwords** (secure note attachment) as `amcheste-ai-agent GitHub App private key`. -10. Install the App on the target account and select repos — start with "All repositories" (see §Scope below). +10. Install the App on the target account and select repos. Start with "All repositories" (see §Scope below). **Token flow at runtime:** @@ -240,7 +240,7 @@ Alan's default `gh auth` stays his. The App token lives in the session's environ * @amcheste ``` -Since the PR author is now `amcheste-ai-agent` (not `amcheste`), GitHub will **auto-request `amcheste` as reviewer**. This is the key unlock — the review-request mechanism works normally, no workarounds required. +Since the PR author is now `amcheste-ai-agent` (not `amcheste`), GitHub will **auto-request `amcheste` as reviewer**. This is the key unlock. The review-request mechanism works normally, no workarounds required. ### 6. Branch Protection @@ -258,7 +258,7 @@ This enforces that no code reaches `main` without Alan's explicit approval. This design **deprecates** the current auto-assignee hack. -Background: because self-review was blocked when Alan was both author and reviewer, the current auto-assign workflow assigns the PR *creator* as the assignee. That was a workaround — `assignee` was being used to get a notification surface, not to represent actual assignment. +Background: because self-review was blocked when Alan was both author and reviewer, the current auto-assign workflow assigns the PR *creator* as the assignee. That was a workaround. `assignee` was being used to get a notification surface, not to represent actual assignment. With bot authorship, this stops being necessary. CODEOWNERS handles review request. `assignee` can go back to its real purpose: indicating **who is responsible for getting this PR done** (not a substitute for "reviewer" when review can't be requested). @@ -275,13 +275,13 @@ If future workflows need to assign somebody (e.g. "this PR blocks a release; ass ## What Changes Day-to-Day -**For Alan:** Nothing. Talk to Claude the same way. Review PRs in Graphite. Code in Cursor whenever you want — your commits are yours, Claude's commits are the bot's, even in the same repo on the same branch. +**For Alan:** Nothing. Talk to Claude the same way. Review PRs in Graphite. Code in Cursor whenever you want. Your commits are yours, Claude's commits are the bot's, even in the same repo on the same branch. **For Claude Code:** Two differences baked into CLAUDE.md: 1. Commits use `--author="amcheste-ai-agent <...>"` (Alan's git config unchanged) 2. PR creation uses an installation token from the GitHub App (minted per session) -**For Alan in Cursor/terminal:** Nothing changes. Default git identity stays `amcheste`. Cursor's AI agent assistance still commits as Alan (which is correct — Alan is directing Cursor, not delegating wholesale). +**For Alan in Cursor/terminal:** Nothing changes. Default git identity stays `amcheste`. Cursor's AI agent assistance still commits as Alan (which is correct. Alan is directing Cursor, not delegating wholesale). **Commit messages** continue to include `Co-Authored-By: Claude Opus 4.7 ` for additional audit trail, tied to the specific model version. @@ -333,11 +333,11 @@ Apply this pattern to **all existing repos and all new repos**. It is standard o All previously-open questions are now resolved: -1. ~~**Account naming:**~~ **Resolved** — `amcheste-ai-agent`, with Gmail address `amcheste.ai.agent@gmail.com`. -2. ~~**Scope:**~~ **Resolved** — all existing and future repos. SOP going forward. -3. ~~**Global vs per-repo git config:**~~ **Resolved** — Option A (`--author` flag) avoids the question entirely. Alan's git config stays his. -4. ~~**GitHub App vs PAT:**~~ **Resolved** — GitHub App. Worth the extra setup for audit logs, no token rotation, and the right-way signal. -5. ~~**Auto-assign action:**~~ **Resolved** — Retire the creator-as-assignee workaround. CODEOWNERS handles review request; `assignee` goes back to its real purpose of indicating actual ownership. +1. ~~**Account naming:**~~ **Resolved**. `amcheste-ai-agent`, with Gmail address `amcheste.ai.agent@gmail.com`. +2. ~~**Scope:**~~ **Resolved**. All existing and future repos. SOP going forward. +3. ~~**Global vs per-repo git config:**~~ **Resolved**. Option A (`--author` flag) avoids the question entirely. Alan's git config stays his. +4. ~~**GitHub App vs PAT:**~~ **Resolved**. GitHub App. Worth the extra setup for audit logs, no token rotation, and the right-way signal. +5. ~~**Auto-assign action:**~~ **Resolved**. Retire the creator-as-assignee workaround. CODEOWNERS handles review request; `assignee` goes back to its real purpose of indicating actual ownership. --- diff --git a/docs/philosophies/branching-strategy.md b/docs/philosophies/branching-strategy.md index cdb8abd..11d1282 100644 --- a/docs/philosophies/branching-strategy.md +++ b/docs/philosophies/branching-strategy.md @@ -10,7 +10,7 @@ This document is the *why*. For the concrete steps, commands, and tooling that i ![Branching Strategy: feature branches land on develop, develop is CLI-merged into main at release time, and main is tagged with semver](../images/branching-strategy.png) -Every repo I own follows the same two-branch model — `main` is the latest release, `develop` is the integration branch, and every change lands via pull request. Releases are an explicit, ceremonial promotion of `develop` into `main`, followed by a semver tag that triggers the release pipeline. +Every repo I own follows the same two-branch model. `main` is the latest release, `develop` is the integration branch, and every change lands via pull request. Releases are an explicit, ceremonial promotion of `develop` into `main`, followed by a semver tag that triggers the release pipeline. This isn't the simplest possible setup. Trunk-based development with a single `main` branch is simpler. I run the two-branch model on purpose. The rest of this document is why. @@ -22,7 +22,7 @@ This isn't the simplest possible setup. Trunk-based development with a single `m **Why:** A reader (or a CI system, or a downstream consumer) should be able to answer "what's in production right now?" without spelunking through git history. With one branch, "latest commit" can mean anything from "released an hour ago" to "experimental WIP someone merged this morning." With `main` = latest release, the answer is always just `git checkout main`. -This also means tags on `main` are meaningful by construction — every tag corresponds to an actual release event, not a mid-flight commit. +This also means tags on `main` are meaningful by construction. Every tag corresponds to an actual release event, not a mid-flight commit. --- @@ -32,7 +32,7 @@ This also means tags on `main` are meaningful by construction — every tag corr **Why:** The PR is the review surface. Skipping it removes the one forcing function that catches bad commit messages, missed tests, accidentally committed secrets, or "just a quick fix" changes that weren't actually quick. Even when I'm the only reviewer, the PR gives me a moment to look at the diff with fresh eyes, which almost always surfaces something. -Branch protection enforces this mechanically — not just as a convention. The rule is only real if the tooling refuses to break it. +Branch protection enforces this mechanically. Not just as a convention. The rule is only real if the tooling refuses to break it. --- @@ -40,7 +40,7 @@ Branch protection enforces this mechanically — not just as a convention. The r **Principle:** One logical change per branch. One branch per PR. Delete after merge. -**Why:** Long-lived feature branches accumulate drift, merge conflicts, and scope creep. They also hide work — until the PR opens, nobody (including future-me) has visibility into what's in flight. A short-lived branch forces the change to be small enough to review as a single unit. If it's too big for that, it's too big for one PR. +**Why:** Long-lived feature branches accumulate drift, merge conflicts, and scope creep. They also hide work. Until the PR opens, nobody (including future-me) has visibility into what's in flight. A short-lived branch forces the change to be small enough to review as a single unit. If it's too big for that, it's too big for one PR. One logical change per PR also means the commit history on `develop` tells a coherent story. Each merge commit answers "what did this change do?" in a single sentence. @@ -48,7 +48,7 @@ One logical change per PR also means the commit history on `develop` tells a coh ## 4. Conventional commits as a history contract -**Principle:** Commits use [Conventional Commits](https://www.conventionalcommits.org/) prefixes — `feat:`, `fix:`, `docs:`, `chore:`, `refactor:`, with `!` for breaking changes. +**Principle:** Commits use [Conventional Commits](https://www.conventionalcommits.org/) prefixes. `feat:`, `fix:`, `docs:`, `chore:`, `refactor:`, with `!` for breaking changes. **Why:** The prefix is a promise to the reader (and to tooling) about the *kind* of change this is. It drives semver bumps automatically (`feat` → minor, `fix` → patch, `!` → major), makes release notes generate themselves, and lets me grep history by change type. It also forces a tiny moment of discipline at commit time: "is this actually a fix, or am I rolling a refactor into a fix PR?" @@ -56,11 +56,11 @@ The cost is near-zero. The benefit compounds across hundreds of commits and ever --- -## 5. Releases preserve commit ancestry — CLI merge, not UI merge +## 5. Releases preserve commit ancestry. CLI merge, not UI merge **Principle:** The `develop → main` promotion happens via `git merge --no-ff` from the command line. Not via GitHub's merge button. -**Why:** GitHub's default merge button **squash-merges**, which flattens every commit on `develop` into a single new commit on `main`. That new commit has no ancestry relationship with any of the commits on `develop`. On the *next* release cycle, `main` and `develop` have diverged at every single commit — because from git's perspective the squashed commit is a completely different object. The result is a merge conflict on every release, forever. +**Why:** GitHub's default merge button **squash-merges**, which flattens every commit on `develop` into a single new commit on `main`. That new commit has no ancestry relationship with any of the commits on `develop`. On the *next* release cycle, `main` and `develop` have diverged at every single commit. Because from git's perspective the squashed commit is a completely different object. The result is a merge conflict on every release, forever. A non-fast-forward merge (`git merge --no-ff`) preserves the full commit graph. `main` becomes a strict ancestor of `develop` again at merge time, and the next release cycle starts from clean state. The ceremony is one CLI command; the cost of getting it wrong is permanent history corruption. @@ -72,9 +72,9 @@ This is one of those rules that looks pedantic until you've lived through the co **Principle:** Releasing isn't a side effect of merging. It's a deliberate step: version bump PR → `develop → main` CLI merge → semver tag → pipeline fires. -**Why:** "Continuous deployment on every merge" is a fine model for some shops — it's not my model. I want releases to be intentional, so that the version number on `main` means something, so that each tag is a point I can actually return to, and so that the release process has enough friction to catch "wait, I don't want to ship that yet." The friction is a feature. +**Why:** "Continuous deployment on every merge" is a fine model for some shops. It's not my model. I want releases to be intentional, so that the version number on `main` means something, so that each tag is a point I can actually return to, and so that the release process has enough friction to catch "wait, I don't want to ship that yet." The friction is a feature. -The tag itself triggers the release pipeline. That keeps the release event and the release artifact mechanically linked — there's no way to tag without publishing, and no way to publish without tagging. +The tag itself triggers the release pipeline. That keeps the release event and the release artifact mechanically linked. There's no way to tag without publishing, and no way to publish without tagging. --- @@ -84,7 +84,7 @@ The tag itself triggers the release pipeline. That keeps the release event and t **Why:** I have enough repos that remembering per-repo conventions is not free. When I jump into any repo, the answer to "how do I contribute?" and "how do I cut a release?" is the same answer. Muscle memory transfers. Tooling (`/setup-repo`, `/publish-release`, `/create-repo`) works identically everywhere. The cost of occasional friction when a repo would have wanted something bespoke is lower than the cost of cognitive overhead everywhere else. -This is also why this handbook is canonical — per-repo `CONTRIBUTING.md` files point here instead of drifting into their own dialects. +This is also why this handbook is canonical. Per-repo `CONTRIBUTING.md` files point here instead of drifting into their own dialects. --- @@ -92,11 +92,11 @@ This is also why this handbook is canonical — per-repo `CONTRIBUTING.md` files - It's not trunk-based development. Trunk-based is great for teams that release continuously off a single branch. I don't. - It's not GitFlow. GitFlow adds release branches, hotfix branches, and long-lived support branches. I don't need any of that at this scale. -- It's not dogma. If a repo genuinely needs a different model — say, a docs-only repo where there's no meaningful "release" — the model bends. But bending is the exception, and it's a deliberate choice, not a drift. +- It's not dogma. If a repo genuinely needs a different model. Say, a docs-only repo where there's no meaningful "release". The model bends. But bending is the exception, and it's a deliberate choice, not a drift. --- ## Related -- [Branching & Releases](../workflows/branching-and-releases.md) — the concrete steps, commands, and tooling. -- [Development Tooling Stack](../tooling/dev-tooling-stack.md) — how this workflow fits into the broader AI-assisted development stack. +- [Branching & Releases](../workflows/branching-and-releases.md). The concrete steps, commands, and tooling. +- [Development Tooling Stack](../tooling/dev-tooling-stack.md). How this workflow fits into the broader AI-assisted development stack. diff --git a/docs/philosophies/release-cadence.md b/docs/philosophies/release-cadence.md index e0b0167..61a9dd7 100644 --- a/docs/philosophies/release-cadence.md +++ b/docs/philosophies/release-cadence.md @@ -1,6 +1,6 @@ # Philosophy: Release Cadence & Semver Discipline -**Why releases are a deliberate, automated-but-not-automatic ceremony — and how conventional commits drive the version number.** +**Why releases are a deliberate, automated-but-not-automatic ceremony. And how conventional commits drive the version number.** This document is the *why*. For the concrete workflow steps, see [Branching & Releases](../workflows/branching-and-releases.md) and [CI Automation Surface](../workflows/ci-automation.md). @@ -25,7 +25,7 @@ A few rules hold across all of it: **Principle:** A release happens when I decide to release. It's not triggered by "merged a PR" or "end of sprint." -**Why:** Continuous deployment on every merge is a fine model for services that can afford to ship unfinished state behind feature flags. My repos generally don't have that luxury — they're libraries, CLIs, operators, and personal tooling where a tagged version is an artifact other systems depend on. Making the release explicit means the version number on `main` is a point I can return to, not a moving target. +**Why:** Continuous deployment on every merge is a fine model for services that can afford to ship unfinished state behind feature flags. My repos generally don't have that luxury. They're libraries, CLIs, operators, and personal tooling where a tagged version is an artifact other systems depend on. Making the release explicit means the version number on `main` is a point I can return to, not a moving target. The ceremony is the feature. Friction at release time catches "wait, I don't want to ship that yet." Friction at merge time doesn't. @@ -52,7 +52,7 @@ This only works because the PR is auto-opened but manually merged. The automatio | `docs:`, `chore:`, `refactor:`, `test:`, `ci:` | no bump on their own | | Any commit with `!` after the type | major | -**Why:** Deferring the bump decision to release time is where semver discipline breaks down. You look at a list of 40 commits, try to remember which ones were breaking, and pick a number. That's error-prone and subjective. Moving the decision to commit time — when I'm looking at a single change and know exactly what it does — makes it accurate by construction. +**Why:** Deferring the bump decision to release time is where semver discipline breaks down. You look at a list of 40 commits, try to remember which ones were breaking, and pick a number. That's error-prone and subjective. Moving the decision to commit time. When I'm looking at a single change and know exactly what it does. Makes it accurate by construction. This is enforced mechanically: the validate workflow's Commit Lint job rejects PRs with non-conventional messages. The monthly release workflow reads the commits and suggests the correct bump. A human still confirms, but they're confirming a calculation, not making one. @@ -60,7 +60,7 @@ This is enforced mechanically: the validate workflow's Commit Lint job rejects P ## 4. The `VERSION` file is the single source of truth -**Principle:** Every repo has a one-line `VERSION` file at the root. It holds the current (released) version. Nothing else — no `package.json` sprinkling, no `setup.py` duplication, no hardcoded version strings in source. +**Principle:** Every repo has a one-line `VERSION` file at the root. It holds the current (released) version. Nothing else. No `package.json` sprinkling, no `setup.py` duplication, no hardcoded version strings in source. **Why:** Multiple sources of version truth is how you end up with `v1.3.0` in one place and `1.2.9` in another, with nobody sure which is "real." A single `VERSION` file is trivial to read from any language, trivial for CI to validate, and forces every part of the system that cares about the version to look in one place. @@ -74,7 +74,7 @@ The `bump-version.sh` script (in repo-template) is the only thing that writes to **Why:** Coupling the tag and the release means they can't drift. You can't accidentally tag without publishing. You can't accidentally publish without tagging. The tag is the truth; the pipeline is a consequence. -This also means tags are protected (ruleset on `refs/tags/v*` — no creation, deletion, or non-fast-forward moves outside the release flow). Accidentally deleting a tag after publish would create a version number that corresponds to nothing, which is worse than nothing at all. +This also means tags are protected (ruleset on `refs/tags/v*`. No creation, deletion, or non-fast-forward moves outside the release flow). Accidentally deleting a tag after publish would create a version number that corresponds to nothing, which is worse than nothing at all. --- @@ -82,7 +82,7 @@ This also means tags are protected (ruleset on `refs/tags/v*` — no creation, d **Principle:** Versions containing a `-` (e.g. `1.0.0-beta.1`, `2.0.0-rc.3`) are published as pre-releases. They do not become `latest`. Users have to explicitly opt in. -**Why:** Some changes need real-world exposure before I'm willing to call them stable — new features that might behave differently than the tests suggest, breaking changes where I want to give consumers a chance to adapt. Pre-releases let me get artifacts into the world without committing to stability. +**Why:** Some changes need real-world exposure before I'm willing to call them stable. New features that might behave differently than the tests suggest, breaking changes where I want to give consumers a chance to adapt. Pre-releases let me get artifacts into the world without committing to stability. The release pipeline handles this automatically based on the tag format. No separate workflow, no manual steps, no risk of accidentally promoting a beta to latest. @@ -118,7 +118,7 @@ The value of this discipline is that consumers (including future-me) know what p **Why:** Writing release notes from scratch at release time means either (a) scrolling through commits trying to remember what shipped, or (b) skipping it, which makes the GitHub release page useless. Accumulating them as work happens means the notes are written by the person closest to each change, at the moment they made it. -This pairs with conventional commits and the PR labeler — the label determines the category in the release notes, the commit type determines the bump, and the PR title becomes the bullet. All three reuse the same underlying discipline. +This pairs with conventional commits and the PR labeler. The label determines the category in the release notes, the commit type determines the bump, and the PR title becomes the bullet. All three reuse the same underlying discipline. --- @@ -132,6 +132,6 @@ This pairs with conventional commits and the PR labeler — the label determines ## Related -- [Branching Strategy philosophy](branching-strategy.md) — how `develop`/`main` and CLI-merged promotion support this cadence. -- [Branching & Releases workflow](../workflows/branching-and-releases.md) — concrete steps for cutting a release. -- [CI Automation Surface](../workflows/ci-automation.md) — the validate/commit-lint/release-drafter/monthly-dep-release workflows that implement this. +- [Branching Strategy philosophy](branching-strategy.md). How `develop`/`main` and CLI-merged promotion support this cadence. +- [Branching & Releases workflow](../workflows/branching-and-releases.md). Concrete steps for cutting a release. +- [CI Automation Surface](../workflows/ci-automation.md). The validate/commit-lint/release-drafter/monthly-dep-release workflows that implement this. diff --git a/docs/philosophies/security-posture.md b/docs/philosophies/security-posture.md index 38b1d5c..3304a3a 100644 --- a/docs/philosophies/security-posture.md +++ b/docs/philosophies/security-posture.md @@ -1,6 +1,6 @@ # Philosophy: Security Posture -**Why I run the same baseline security automation on every repo — public or private, ten stars or zero — and why the reporting SLA is in writing.** +**Why I run the same baseline security automation on every repo. Public or private, ten stars or zero. And why the reporting SLA is in writing.** This document is the *why*. For the concrete workflow steps and the specific actions/tools, see [CI Automation Surface](../workflows/ci-automation.md). @@ -8,9 +8,9 @@ This document is the *why*. For the concrete workflow steps and the specific act ## The core idea -Security is not something I bolt on when a project "gets important enough to matter." It's a baseline that ships with every repo from day one. That baseline is small on purpose — SAST, supply-chain hygiene, vulnerability reporting, branch protection — but it runs uniformly so I never have to ask "is this repo protected?" +Security is not something I bolt on when a project "gets important enough to matter." It's a baseline that ships with every repo from day one. That baseline is small on purpose. SAST, supply-chain hygiene, vulnerability reporting, branch protection. But it runs uniformly so I never have to ask "is this repo protected?" -The alternative — adding security gates only when something bad happens — is how incidents compound. A vulnerable dependency in a "just a side project" repo still lands on my GitHub account, still gets indexed by anyone scraping, and still sets a bad precedent for the next repo I spin up. Uniformity is the cheapest insurance I can buy. +The alternative. Adding security gates only when something bad happens. Is how incidents compound. A vulnerable dependency in a "just a side project" repo still lands on my GitHub account, still gets indexed by anyone scraping, and still sets a bad precedent for the next repo I spin up. Uniformity is the cheapest insurance I can buy. --- @@ -18,9 +18,9 @@ The alternative — adding security gates only when something bad happens — is **Principle:** Every repo has a `SECURITY.md` that commits to concrete SLAs: **7 days to acknowledge** a reported issue, **30 days to resolve or publicly explain why not**. Reporting channel is GitHub's private vulnerability reporting. -**Why:** Putting a number in writing is the difference between "I'll get to it" and "the clock is running." If someone takes the time to responsibly disclose a vulnerability to me, they deserve to know what response to expect. A vague "we take security seriously" statement is worse than no statement at all — it claims a commitment without making one. +**Why:** Putting a number in writing is the difference between "I'll get to it" and "the clock is running." If someone takes the time to responsibly disclose a vulnerability to me, they deserve to know what response to expect. A vague "we take security seriously" statement is worse than no statement at all. It claims a commitment without making one. -The SLA is also a forcing function for me. A tight acknowledge-window (7 days) means I can't let reports sit in a notification backlog. The resolve-window (30 days) is deliberately aggressive for a solo maintainer — if I can't hit it, the policy requires me to explain why publicly, which keeps the decision visible. +The SLA is also a forcing function for me. A tight acknowledge-window (7 days) means I can't let reports sit in a notification backlog. The resolve-window (30 days) is deliberately aggressive for a solo maintainer. If I can't hit it, the policy requires me to explain why publicly, which keeps the decision visible. --- @@ -28,7 +28,7 @@ The SLA is also a forcing function for me. A tight acknowledge-window (7 days) m **Principle:** Security fixes land on `main` and are released as the next version. Older tagged versions don't get backports. -**Why:** Maintaining security branches for old versions is a commitment I'm not willing to make at this scale. Saying so explicitly, in `SECURITY.md`, means consumers know the contract up front — "use the latest tag or accept that you're on your own." +**Why:** Maintaining security branches for old versions is a commitment I'm not willing to make at this scale. Saying so explicitly, in `SECURITY.md`, means consumers know the contract up front. "use the latest tag or accept that you're on your own." This also shapes how I think about breaking changes. If major versions don't get backported security fixes, breaking changes need to be infrequent enough that consumers have a realistic path forward. Semver discipline (see [Release Cadence](release-cadence.md)) is what makes this sustainable. @@ -38,9 +38,9 @@ This also shapes how I think about breaking changes. If major versions don't get **Principle:** Every repo runs [Semgrep](https://semgrep.dev/) against the codebase on a weekly schedule and on every PR targeting `develop` or `main`. PRs don't upload findings to the Security tab (to avoid Advanced Security requirements); scheduled runs on `main` do. -**Why:** SAST is cheap, noisy, and occasionally invaluable. Running it weekly on `main` gives me a snapshot of the current state; running it on PRs catches introduced problems before they land. Separating the two outputs means PR noise doesn't pollute the Security tab — only `main`'s actual state does. +**Why:** SAST is cheap, noisy, and occasionally invaluable. Running it weekly on `main` gives me a snapshot of the current state; running it on PRs catches introduced problems before they land. Separating the two outputs means PR noise doesn't pollute the Security tab. Only `main`'s actual state does. -The rule pack is intentionally minimal — `p/secrets` by default, language-specific packs added per-repo. I'd rather have a small set of rules I actually listen to than a giant set I reflexively ignore. +The rule pack is intentionally minimal. `p/secrets` by default, language-specific packs added per-repo. I'd rather have a small set of rules I actually listen to than a giant set I reflexively ignore. --- @@ -77,7 +77,7 @@ Developers install once: `pre-commit install`. From then on, every `git commit` **If a secret does leak:** 1. Rotate the secret immediately (invalidate the leaked credential). -2. Don't rely on `git push --force` to fix it — the secret was already pulled by any CI system, fork, mirror, or clone. +2. Don't rely on `git push --force` to fix it. The secret was already pulled by any CI system, fork, mirror, or clone. 3. Document the incident in the repo's security notes (or Linear) so future-me remembers. The handbook's position: **rotation is the fix, not history rewriting.** Pre-commit prevention is what keeps rotations rare. @@ -88,9 +88,9 @@ The handbook's position: **rotation is the fix, not history rewriting.** Pre-com **Principle:** Every public repo runs [OpenSSF Scorecard](https://scorecard.dev/) on a weekly schedule, on branch protection rule changes, and on every push to `main`. The score is visible as a badge in the README. -**Why:** Scorecard gives me a third-party audit of the things that are easy to forget — are tokens scoped minimally? Is branch protection enforced? Are dependencies pinned? A weekly cadence catches drift (e.g., I relaxed a branch protection rule three months ago and forgot). Running on branch protection changes catches mistakes the moment they're made. +**Why:** Scorecard gives me a third-party audit of the things that are easy to forget. Are tokens scoped minimally? Is branch protection enforced? Are dependencies pinned? A weekly cadence catches drift (e.g., I relaxed a branch protection rule three months ago and forgot). Running on branch protection changes catches mistakes the moment they're made. -The badge in the README is partly signaling (public repos benefit from visible posture) and partly self-discipline — a falling score is visible to anyone looking, which is motivation not to let it slip. +The badge in the README is partly signaling (public repos benefit from visible posture) and partly self-discipline. A falling score is visible to anyone looking, which is motivation not to let it slip. Private repos have `continue-on-error: true` on Scorecard because many of its checks require the public surface. No noise on repos where it can't give a useful answer. @@ -100,9 +100,9 @@ Private repos have `continue-on-error: true` on Scorecard because many of its ch **Principle:** `main` and `develop` both require PRs. `main` additionally requires the release PR path. Neither allows force pushes or deletions. Tag ruleset on `v*` blocks creation/deletion/non-ff outside the release flow. -**Why:** Protection rules are the mechanical enforcement of the branching philosophy. A rule that exists only as a convention isn't a rule — it's a preference. `/setup-repo` applies these rules to every new repo as part of the creation ceremony, so there's no gap between "repo exists" and "repo is protected." +**Why:** Protection rules are the mechanical enforcement of the branching philosophy. A rule that exists only as a convention isn't a rule. It's a preference. `/setup-repo` applies these rules to every new repo as part of the creation ceremony, so there's no gap between "repo exists" and "repo is protected." -The tag protection specifically matters because release tags are the artifact. A deleted or moved `v1.2.3` tag corresponds to a released version that consumers may already have downloaded — moving it is a supply-chain problem, not a git one. +The tag protection specifically matters because release tags are the artifact. A deleted or moved `v1.2.3` tag corresponds to a released version that consumers may already have downloaded. Moving it is a supply-chain problem, not a git one. --- @@ -120,9 +120,9 @@ This gets more important with AI-authored PRs (see [Claude Bot Account design no **Principle:** Issues and PRs without activity for 30 days get marked stale; another 7 days closes them. Issues labeled `pinned` or `security` are exempt. -**Why:** A stale issue backlog isn't just clutter — it hides live security reports. If a genuine vulnerability report sits next to 40 old "nice to have" issues, the signal gets lost. Aggressive staling forces the backlog to reflect what's actually open; the `security` exemption ensures real reports never get auto-closed. +**Why:** A stale issue backlog isn't just clutter. It hides live security reports. If a genuine vulnerability report sits next to 40 old "nice to have" issues, the signal gets lost. Aggressive staling forces the backlog to reflect what's actually open; the `security` exemption ensures real reports never get auto-closed. -Auto-close isn't for the reporter's benefit — it's for mine. A clean issue list is one I can actually scan in 30 seconds. +Auto-close isn't for the reporter's benefit. It's for mine. A clean issue list is one I can actually scan in 30 seconds. --- @@ -136,7 +136,7 @@ Auto-close isn't for the reporter's benefit — it's for mine. A clean issue lis ## Related -- [Branching Strategy philosophy](branching-strategy.md) — how branch protection supports the security model. -- [Release Cadence philosophy](release-cadence.md) — why "only the latest release is supported" is viable. -- [Claude Bot Account design note](../design/claude-bot-account.md) — how AI-authored PRs preserve the review checkpoint. -- [CI Automation Surface](../workflows/ci-automation.md) — the concrete workflows that implement this posture. +- [Branching Strategy philosophy](branching-strategy.md). How branch protection supports the security model. +- [Release Cadence philosophy](release-cadence.md). Why "only the latest release is supported" is viable. +- [Claude Bot Account design note](../design/claude-bot-account.md). How AI-authored PRs preserve the review checkpoint. +- [CI Automation Surface](../workflows/ci-automation.md). The concrete workflows that implement this posture. diff --git a/docs/philosophies/testing.md b/docs/philosophies/testing.md index 8a02236..c1ed998 100644 --- a/docs/philosophies/testing.md +++ b/docs/philosophies/testing.md @@ -1,6 +1,6 @@ # Philosophy: Testing -**Why I write the tests I write — and, more importantly, why I don't write the ones I don't.** +**Why I write the tests I write. And, more importantly, why I don't write the ones I don't.** This document is the *why*. Concrete testing conventions per-language or per-project live in each repo's CLAUDE.md or CONTRIBUTING.md. @@ -8,7 +8,7 @@ This document is the *why*. Concrete testing conventions per-language or per-pro ## The core idea -Tests exist to protect behavior that matters. That's the whole philosophy. Everything else — coverage thresholds, test pyramids, TDD/BDD/TDC acronyms — is scaffolding around that idea, and some of that scaffolding is actively unhelpful. +Tests exist to protect behavior that matters. That's the whole philosophy. Everything else. Coverage thresholds, test pyramids, TDD/BDD/TDC acronyms. Is scaffolding around that idea, and some of that scaffolding is actively unhelpful. A test is worth writing if it would fail when I break something that matters, and pass otherwise. A test that fails when I refactor without changing behavior is a bad test. A test that passes while real behavior silently regresses is a worse test. Everything below is an elaboration of that one principle. @@ -18,11 +18,11 @@ A test is worth writing if it would fail when I break something that matters, an **Principle:** When a piece of code does both calculation and I/O, split it. The calculation gets unit tests. The I/O gets integration tests, sparingly. -**Why:** Pure logic is trivially testable — no mocks, no fixtures, no setup. The moment I mix calculation with a database call or an HTTP request, the test cost jumps by an order of magnitude, and the resulting test is usually worse (slow, flaky, dependent on environment). Separating the two is a design decision driven by testability, not a test style. +**Why:** Pure logic is trivially testable. No mocks, no fixtures, no setup. The moment I mix calculation with a database call or an HTTP request, the test cost jumps by an order of magnitude, and the resulting test is usually worse (slow, flaky, dependent on environment). Separating the two is a design decision driven by testability, not a test style. -In practice this means: stats computations, date parsing, file path manipulation, normalization, parsing — all pure, all heavily tested. Network calls, database reads, filesystem writes — integration surface, tested with the minimum necessary coverage to know it works end-to-end. +In practice this means: stats computations, date parsing, file path manipulation, normalization, parsing. All pure, all heavily tested. Network calls, database reads, filesystem writes. Integration surface, tested with the minimum necessary coverage to know it works end-to-end. -When I see code where I can't figure out how to test a piece of logic without mocking three things, the answer is almost always "extract the logic into a pure function" — not "write the three mocks." +When I see code where I can't figure out how to test a piece of logic without mocking three things, the answer is almost always "extract the logic into a pure function". Not "write the three mocks." --- @@ -30,17 +30,17 @@ When I see code where I can't figure out how to test a piece of logic without mo **Principle:** I avoid mocking internal code. If a function needs to be mocked to test its caller, the caller is probably doing too much. -**Why:** Every mock is a lie I'm telling the test about how the real system behaves. One or two lies are fine — e.g. mocking a third-party API response so I don't hit it from CI. Twenty lies means the test is essentially a restatement of the implementation, not a verification that it works. That test will pass when I refactor the implementation and fail when I change the mock expectations, which is exactly backwards. +**Why:** Every mock is a lie I'm telling the test about how the real system behaves. One or two lies are fine. E.g. mocking a third-party API response so I don't hit it from CI. Twenty lies means the test is essentially a restatement of the implementation, not a verification that it works. That test will pass when I refactor the implementation and fail when I change the mock expectations, which is exactly backwards. -Integration tests that hit real dependencies (real database, real file system) catch problems that mocked tests cannot — migration failures, connection handling, serialization mismatches. They're slower and more annoying to set up, but they're where I get my real signal. +Integration tests that hit real dependencies (real database, real file system) catch problems that mocked tests cannot. Migration failures, connection handling, serialization mismatches. They're slower and more annoying to set up, but they're where I get my real signal. --- ## 3. Acceptance tests gate releases, not every PR -**Principle:** Heavy end-to-end tests — VM boot-and-verify runs, full-cluster acceptance tests, real API integration with external services — run on PRs *to `main`* and on `workflow_dispatch`. They do not run on every PR to `develop`. +**Principle:** Heavy end-to-end tests. VM boot-and-verify runs, full-cluster acceptance tests, real API integration with external services. Run on PRs *to `main`* and on `workflow_dispatch`. They do not run on every PR to `develop`. -**Why:** Acceptance tests are expensive: 30–45 minutes of runner time, real infrastructure, real API quota. Running them on every commit turns CI into a bottleneck and trains me to ignore failing checks. Gating them to the release PR (the `develop → main` merge commit) means they run once per release, at the moment it actually matters — before any version tag gets pushed. +**Why:** Acceptance tests are expensive: 30–45 minutes of runner time, real infrastructure, real API quota. Running them on every commit turns CI into a bottleneck and trains me to ignore failing checks. Gating them to the release PR (the `develop → main` merge commit) means they run once per release, at the moment it actually matters. Before any version tag gets pushed. The tradeoff: if an acceptance-breaking change lands on `develop`, I find out at release time instead of at PR time. That's acceptable because the release-time failure still blocks the release, and the smaller fast-CI signal on `develop` is fast enough to catch the same class of problems earlier for anything the unit/integration tests can see. @@ -50,13 +50,13 @@ Fast feedback on `develop` PRs; comprehensive gating at release. That's the divi ## 4. No coverage-threshold gaming -**Principle:** Coverage is measured, reported, and watched — but there's no required percentage that blocks merges. +**Principle:** Coverage is measured, reported, and watched. But there's no required percentage that blocks merges. **Why:** Coverage targets optimize the wrong thing. Code can be 100% covered by tests that don't actually verify any meaningful behavior (test just calls the function and checks it returns something). Conversely, a critical 20-line function can be fully protected by two well-chosen tests with middling line coverage. Locking the gate at "85%" pushes people to write the former to avoid writing the latter. What I look at instead: is the critical path tested? If this function silently returned wrong results, would a test catch it? If I deleted a branch, would something fail? Those questions aren't answerable from a coverage number. They're answerable from reading the tests. -Coverage is useful as a trend signal — if it drops 30 points in a PR, I want to know why. It's not useful as a gate. +Coverage is useful as a trend signal. If it drops 30 points in a PR, I want to know why. It's not useful as a gate. --- @@ -66,7 +66,7 @@ Coverage is useful as a trend signal — if it drops 30 points in a PR, I want t **Why:** If tests pass locally but fail in CI (or vice versa), I'm debugging the environment rather than the code. Making the commands identical means CI is just "my machine, but in a cleaner room." Catching issues locally saves the CI round-trip; reproducing CI failures locally is trivial. -This also means the repo has to be honest about its dependencies — if the test requires a running database, the `make test` target has to either start one or clearly fail telling me to. Hidden CI-only setup steps are a tax on every contributor, including me, three months from now. +This also means the repo has to be honest about its dependencies. If the test requires a running database, the `make test` target has to either start one or clearly fail telling me to. Hidden CI-only setup steps are a tax on every contributor, including me, three months from now. --- @@ -74,7 +74,7 @@ This also means the repo has to be honest about its dependencies — if the test **Principle:** If a test requires credentials, API keys, or access to a live third-party service, it's either skipped in CI with a clear explanation or excluded from the default test target entirely. The README documents how to run it locally. -**Why:** A test that silently passes in CI because it can't run is worse than no test. A test that requires "wait, do I have the right env var set?" is a daily paper cut. Either the test runs everywhere or it doesn't — ambiguity is the failure mode to avoid. +**Why:** A test that silently passes in CI because it can't run is worse than no test. A test that requires "wait, do I have the right env var set?" is a daily paper cut. Either the test runs everywhere or it doesn't. Ambiguity is the failure mode to avoid. For golf-coach-agent's R-Cloud scraper, cam-brand's distribution scripts, anything that talks to a third party I don't control: the integration layer is tested manually with documented steps. The pure logic extracted from it is unit-tested and runs in CI. @@ -84,7 +84,7 @@ For golf-coach-agent's R-Cloud scraper, cam-brand's distribution scripts, anythi **Principle:** A flaky or broken test gets fixed or removed. Skipping a test because "it'll be fixed later" is almost always a lie. -**Why:** Every skipped test is a commitment I've implicitly deferred to future-me. Future-me has no memory of why the test was skipped, and the skip is invisible in CI output — it just silently passes. Better to delete the test (with a commit message explaining why) than to leave a skip that nobody will ever revisit. +**Why:** Every skipped test is a commitment I've implicitly deferred to future-me. Future-me has no memory of why the test was skipped, and the skip is invisible in CI output. It just silently passes. Better to delete the test (with a commit message explaining why) than to leave a skip that nobody will ever revisit. If a test is flaky because of a real race condition, the fix is to address the race, not increase retries or add `sleep()`. Retries are a signal that the test knows something is wrong and is lying about it. @@ -101,6 +101,6 @@ If a test is flaky because of a real race condition, the fix is to address the r ## Related -- [Security Posture philosophy](security-posture.md) — why the baseline includes SAST (which is a different kind of verification than unit tests). -- [CI Automation Surface](../workflows/ci-automation.md) — how validate, acceptance, and release workflows are gated in practice. -- Per-repo CLAUDE.md or CONTRIBUTING.md — project-specific test commands, frameworks, and credentials. +- [Security Posture philosophy](security-posture.md). Why the baseline includes SAST (which is a different kind of verification than unit tests). +- [CI Automation Surface](../workflows/ci-automation.md). How validate, acceptance, and release workflows are gated in practice. +- Per-repo CLAUDE.md or CONTRIBUTING.md. Project-specific test commands, frameworks, and credentials. diff --git a/docs/tooling/dev-tooling-stack.md b/docs/tooling/dev-tooling-stack.md index 6a0cb43..63e87e4 100644 --- a/docs/tooling/dev-tooling-stack.md +++ b/docs/tooling/dev-tooling-stack.md @@ -55,12 +55,12 @@ All code ultimately lives in GitHub: ### Claude Desktop -Claude Desktop is where I figure out *what* to build and *why*. It is not a primary code-writing environment — that role belongs to Claude Code (see 2.3). Claude Desktop covers four distinct modes of use: +Claude Desktop is where I figure out *what* to build and *why*. It is not a primary code-writing environment. That role belongs to Claude Code (see 2.3). Claude Desktop covers four distinct modes of use: -- **Chat / conversation** — system design and architecture thinking, breaking down complex tasks, reviewing tradeoffs, exploring edge cases. The classic "thinking partner" use case. -- **Cowork mode** — Claude takes actions on my Mac with my confirmation: running commands, opening files, inspecting state. Useful for debugging workflows that span multiple apps or for guided exploration of an unfamiliar codebase without switching to a terminal. -- **Artifacts and file sharing** — uploading designs, screenshots, logs, API responses, or long documents so Claude can reason about them directly rather than from a summary. Important for reviewing incident evidence, long error traces, or design documents that are hard to paste as text. -- **Session continuity per project** — persistent conversations scoped to a project, which means I can return to a design discussion days later without re-establishing context. This is the biggest reason Claude Desktop is where planning lives — the thread survives. +- **Chat / conversation**. System design and architecture thinking, breaking down complex tasks, reviewing tradeoffs, exploring edge cases. The classic "thinking partner" use case. +- **Cowork mode**. Claude takes actions on my Mac with my confirmation: running commands, opening files, inspecting state. Useful for debugging workflows that span multiple apps or for guided exploration of an unfamiliar codebase without switching to a terminal. +- **Artifacts and file sharing**. Uploading designs, screenshots, logs, API responses, or long documents so Claude can reason about them directly rather than from a summary. Important for reviewing incident evidence, long error traces, or design documents that are hard to paste as text. +- **Session continuity per project**. Persistent conversations scoped to a project, which means I can return to a design discussion days later without re-establishing context. This is the biggest reason Claude Desktop is where planning lives. The thread survives. Output of this layer is usually a written plan, a decision, or a set of implementation tasks that then get handed to Claude Code for execution. @@ -76,10 +76,10 @@ Claude Code writes the lion's share of my production code. It is the CLI-native - Refactoring across an entire codebase with reasoning about call sites and side effects - Writing tests for new and existing code - Creating and iterating on pull requests (branching, commits, PR body, review response) -- Debugging with full repo context — Claude Code can read the code, run it, interpret failures, and iterate +- Debugging with full repo context. Claude Code can read the code, run it, interpret failures, and iterate - Handles the branching and release ceremony per the [Branching & Releases workflow](../workflows/branching-and-releases.md) -Claude Code also enforces the authorship boundary — commits are authored as `amcheste-ai-agent` (see the [Claude Bot Account design note](../design/claude-bot-account.md)) so that the audit trail distinguishes AI-written code from human-written code. My own commits from the terminal or Cursor remain under `amcheste`. +Claude Code also enforces the authorship boundary. Commits are authored as `amcheste-ai-agent` (see the [Claude Bot Account design note](../design/claude-bot-account.md)) so that the audit trail distinguishes AI-written code from human-written code. My own commits from the terminal or Cursor remain under `amcheste`. Usage pattern: - give Claude Code a task (often decomposed from Claude Desktop planning) @@ -88,14 +88,14 @@ Usage pattern: ### Cursor (supplemental) -Cursor is my AI-native IDE, used for **targeted manual work** — not as the primary code-writing environment. +Cursor is my AI-native IDE, used for **targeted manual work**. Not as the primary code-writing environment. - Surgical keystroke-by-keystroke edits where I want to drive directly - Reading and understanding code with AI assistance, without handing off a task - Reviewing Claude Code's diffs with the IDE's inline tooling - Quick local changes that aren't worth a full Claude Code session -When I'm coding in Cursor, commits go under my personal identity (`amcheste`) — Cursor does not author as the bot. This matches the ownership model in the Claude Bot Account design: whoever runs `git commit` determines the author. +When I'm coding in Cursor, commits go under my personal identity (`amcheste`). Cursor does not author as the bot. This matches the ownership model in the Claude Bot Account design: whoever runs `git commit` determines the author. --- @@ -230,4 +230,4 @@ This development stack is designed to function as a semi-autonomous software eng - Execution is handled primarily by Claude Code, with Cursor as a supplemental tool for targeted manual work - GitHub acts as the system of record -The goal is to progressively shift from manual coding to AI-assisted and eventually agent-driven software development — with a clear audit trail separating what the AI authored from what the human authored. +The goal is to progressively shift from manual coding to AI-assisted and eventually agent-driven software development. With a clear audit trail separating what the AI authored from what the human authored. diff --git a/docs/workflows/branching-and-releases.md b/docs/workflows/branching-and-releases.md index a771077..fa7e15f 100644 --- a/docs/workflows/branching-and-releases.md +++ b/docs/workflows/branching-and-releases.md @@ -1,6 +1,6 @@ # Workflow: Branching & Releases -**The concrete how — branch model, day-to-day flow, and the release process that every repo I own follows.** +**The concrete how. Branch model, day-to-day flow, and the release process that every repo I own follows.** This document is the *how*. For the reasoning behind each decision below, see [Branching Strategy](../philosophies/branching-strategy.md). @@ -29,7 +29,7 @@ This document is the *how*. For the reasoning behind each decision below, see [B | `chore:` | Maintenance, dependencies, housekeeping, releases | | `refactor:` | Code change that neither fixes a bug nor adds a feature | -Breaking changes: append `!` after the type — e.g. `feat!: remove legacy API`. +Breaking changes: append `!` after the type. E.g. `feat!: remove legacy API`. One logical change per PR. Keep commits atomic and the history readable. @@ -39,15 +39,15 @@ One logical change per PR. Keep commits atomic and the history readable. Two paths: -**New repo from scratch:** run `/create-repo ` — creates from the template, clones locally, applies branch protection, default branch, and tag ruleset in one shot. +**New repo from scratch:** run `/create-repo `. Creates from the template, clones locally, applies branch protection, default branch, and tag ruleset in one shot. -**Existing repo:** run `/setup-repo ` — applies the standard branch model, protection, and tag ruleset to a repo that's already there. +**Existing repo:** run `/setup-repo `. Applies the standard branch model, protection, and tag ruleset to a repo that's already there. Both skills set up: - `develop` branch created from `main` if missing, set as default -- Branch protection on `develop` — require PR, require `Lint` and `Commit Lint` status checks -- Branch protection on `main` — require PR, require `Lint` -- Tag ruleset on `v*` — no creation, deletion, or non-fast-forward +- Branch protection on `develop`. Require PR, require `Lint` and `Commit Lint` status checks +- Branch protection on `main`. Require PR, require `Lint` +- Tag ruleset on `v*`. No creation, deletion, or non-fast-forward After `/setup-repo`, customise `.github/workflows/validate.yml` for the project's language/toolchain, then rerun `/setup-repo` to update required status check names to match the actual job names. @@ -62,7 +62,7 @@ git checkout develop && git pull git checkout -b feature/ ``` -Use the prefix that matches the change type — `feature/`, `fix/`, `chore/`, `docs/`, `refactor/`. +Use the prefix that matches the change type. `feature/`, `fix/`, `chore/`, `docs/`, `refactor/`. ### 2. Make changes, commit atomically @@ -84,7 +84,7 @@ PR body: a short summary of what changed and why, plus a test plan checklist. ### 4. CI must pass, then merge -Wait for `Lint` and `Commit Lint` (plus any project-specific checks) to pass. Then merge via the GitHub UI — a standard merge is fine for feature branches landing on `develop`. +Wait for `Lint` and `Commit Lint` (plus any project-specific checks) to pass. Then merge via the GitHub UI. A standard merge is fine for feature branches landing on `develop`. ### 5. Delete the feature branch @@ -107,7 +107,7 @@ Releases are handled by `/publish-release `, which walks through the fo 2. On `develop`, up to date with `origin/develop` 3. `VERSION` file exists in the repo root -### Step 1 — Version bump PR to `develop` +### Step 1. Version bump PR to `develop` ```bash git checkout -b chore/release-v @@ -124,7 +124,7 @@ gh pr create \ Wait for CI. Merge via the GitHub UI. -### Step 2 — Promote `develop` → `main` via CLI merge +### Step 2. Promote `develop` → `main` via CLI merge **This step does *not* use a GitHub PR.** GitHub's merge button squash-merges by default, which drops commit ancestry and causes merge conflicts on every subsequent release. See [the philosophy doc](../philosophies/branching-strategy.md#5-releases-preserve-commit-ancestry--cli-merge-not-ui-merge) for the full story. @@ -134,9 +134,9 @@ git merge --no-ff origin/develop -m "chore: release v" git push origin main ``` -This preserves the full commit graph — `main` remains a strict ancestor of `develop`, and the next release cycle starts from clean state. +This preserves the full commit graph. `main` remains a strict ancestor of `develop`, and the next release cycle starts from clean state. -### Step 3 — Tag `main` +### Step 3. Tag `main` ```bash git tag -a "v" -m "Release v" @@ -145,7 +145,7 @@ git push origin "v" The tag push triggers the release pipeline via the `v*` tag filter on the release workflow. -### Step 4 — Confirm pipeline +### Step 4. Confirm pipeline ```bash gh run list --limit 3 @@ -170,5 +170,5 @@ feature branch ──► develop ──► develop ──► main ──► v1.2 ## Related -- [Branching Strategy philosophy](../philosophies/branching-strategy.md) — the reasoning behind every decision above. -- [Development Tooling Stack](../tooling/dev-tooling-stack.md) — how this workflow runs inside the broader AI-assisted development stack. +- [Branching Strategy philosophy](../philosophies/branching-strategy.md). The reasoning behind every decision above. +- [Development Tooling Stack](../tooling/dev-tooling-stack.md). How this workflow runs inside the broader AI-assisted development stack. diff --git a/docs/workflows/ci-automation.md b/docs/workflows/ci-automation.md index a14735c..0e87212 100644 --- a/docs/workflows/ci-automation.md +++ b/docs/workflows/ci-automation.md @@ -27,23 +27,23 @@ The table below is the complete surface at a glance. The rest of the doc covers --- -## `validate.yml` — the gate every PR goes through +## `validate.yml`. The gate every PR goes through **Fires on:** every push and PR targeting `main` or `develop`. **Does:** -1. **Lint** — language-specific linter (`ruff`, `golangci-lint`, `shellcheck`, whatever fits the repo). This is the check that gets required in branch protection. -2. **Commit Lint** — enforces [Conventional Commits](https://www.conventionalcommits.org/). Rejects PRs where any commit message doesn't match the `[(scope)]: ` grammar with a recognized type (`feat` / `fix` / `docs` / `chore` / `refactor` / `test` / `ci`). -3. **Semver suggestion** — reads the commits on the PR and posts a suggested bump (`major` / `minor` / `patch` / `none`) in the PR output. `feat:` → minor, `fix:` → patch, any `!` → major. -4. **`detect-changes` short-circuit** — if a PR only touches `.github/` files, skip the expensive lint/test jobs. +1. **Lint**. Language-specific linter (`ruff`, `golangci-lint`, `shellcheck`, whatever fits the repo). This is the check that gets required in branch protection. +2. **Commit Lint**. Enforces [Conventional Commits](https://www.conventionalcommits.org/). Rejects PRs where any commit message doesn't match the `[(scope)]: ` grammar with a recognized type (`feat` / `fix` / `docs` / `chore` / `refactor` / `test` / `ci`). +3. **Semver suggestion**. Reads the commits on the PR and posts a suggested bump (`major` / `minor` / `patch` / `none`) in the PR output. `feat:` → minor, `fix:` → patch, any `!` → major. +4. **`detect-changes` short-circuit**. If a PR only touches `.github/` files, skip the expensive lint/test jobs. **Why it matters:** This is the only workflow whose success is required by branch protection. Everything else reports; this one gates. -**Customizing per repo:** the lint step is the part you'll replace — `validate.yml` ships with a TODO placeholder. After editing, rerun `/setup-repo` to sync the required-check names in branch protection. +**Customizing per repo:** the lint step is the part you'll replace. `validate.yml` ships with a TODO placeholder. After editing, rerun `/setup-repo` to sync the required-check names in branch protection. --- -## `labeler.yml` — automatic label assignment +## `labeler.yml`. Automatic label assignment **Fires on:** PR open, reopen, and synchronize. @@ -55,7 +55,7 @@ The table below is the complete surface at a glance. The rest of the doc covers --- -## `release-drafter.yml` — release notes that write themselves +## `release-drafter.yml`. Release notes that write themselves **Fires on:** push to `develop` or `main`, and every PR event. @@ -63,11 +63,11 @@ The table below is the complete surface at a glance. The rest of the doc covers **Why it matters:** Release notes get written the moment each change lands, not at the chaos of release time. Paired with `labeler.yml`, each PR is auto-categorized without me thinking about it. -**Config:** `.github/release-drafter.yml` — defines the categories (Features, Bug Fixes, Maintenance, Security, etc.) and which labels map to each. +**Config:** `.github/release-drafter.yml`. Defines the categories (Features, Bug Fixes, Maintenance, Security, etc.) and which labels map to each. --- -## `monthly-dependency-release.yml` — the patch release cycle +## `monthly-dependency-release.yml`. The patch release cycle **Fires on:** cron (1st of each month, early UTC) + `workflow_dispatch` for manual trigger. @@ -75,31 +75,31 @@ The table below is the complete surface at a glance. The rest of the doc covers 1. Checks `git log` on `develop` since the last release tag for commits matching `chore(deps):` or `chore: bump`. 2. If none, exits silently (no release this cycle). 3. If any, runs `scripts/bump-version.sh patch` to increment the VERSION file. -4. Updates `CHANGELOG.md` — moves `[Unreleased]` entries under a new `[] - YYYY-MM-DD` heading. +4. Updates `CHANGELOG.md`. Moves `[Unreleased]` entries under a new `[] - YYYY-MM-DD` heading. 5. Commits on a branch (`chore/release-v`) and opens a PR to `develop` titled `chore: release v`. 6. Does **not** auto-merge. I review and merge manually. -**Why it matters:** Monthly patch releases eat dependency drift without manual effort. The "open the PR, don't merge" choice is deliberate — see [Release Cadence philosophy §2](../philosophies/release-cadence.md#2-monthly-patch-cycle-for-dependency-drift). +**Why it matters:** Monthly patch releases eat dependency drift without manual effort. The "open the PR, don't merge" choice is deliberate. See [Release Cadence philosophy §2](../philosophies/release-cadence.md#2-monthly-patch-cycle-for-dependency-drift). -**What runs after:** once the monthly PR merges to `develop`, follow the standard [Branching & Releases workflow](branching-and-releases.md#publishing-a-release) — CLI merge `develop → main`, tag, push. +**What runs after:** once the monthly PR merges to `develop`, follow the standard [Branching & Releases workflow](branching-and-releases.md#publishing-a-release). CLI merge `develop → main`, tag, push. --- -## `release.yml` — publishing a tagged version +## `release.yml`. Publishing a tagged version **Fires on:** pushing a `v*` tag to `main`. **Does (structure varies by repo; this is the pattern):** -1. **Validate** — re-runs the full validate workflow as a sanity check. -2. **Acceptance gate** (if applicable) — runs the heavy end-to-end test suite. -3. **Publish** — builds artifacts (containers, binaries, packages) and publishes to the appropriate registry (GHCR, npm, PyPI, a GitHub Release, etc.). -4. **Mark release** — if the tag contains `-` (e.g. `-beta.1`), marks as pre-release. Otherwise marks as latest. +1. **Validate**. Re-runs the full validate workflow as a sanity check. +2. **Acceptance gate** (if applicable). Runs the heavy end-to-end test suite. +3. **Publish**. Builds artifacts (containers, binaries, packages) and publishes to the appropriate registry (GHCR, npm, PyPI, a GitHub Release, etc.). +4. **Mark release**. If the tag contains `-` (e.g. `-beta.1`), marks as pre-release. Otherwise marks as latest. **Why it matters:** The tag *is* the release event. Coupling "tag pushed" → "release published" means they can't drift. See [Release Cadence philosophy §5](../philosophies/release-cadence.md#5-the-tag-is-the-release-event). --- -## `sast.yml` — Semgrep static analysis +## `sast.yml`. Semgrep static analysis **Fires on:** weekly cron (Monday ~02:00 UTC) + push/PR to `main` or `develop`. @@ -113,7 +113,7 @@ The table below is the complete surface at a glance. The rest of the doc covers --- -## `gitleaks.yml` — secret-scan CI backstop +## `gitleaks.yml`. Secret-scan CI backstop **Fires on:** push/PR to `main` or `develop`. @@ -128,19 +128,19 @@ See [Security Posture §4](../philosophies/security-posture.md#4-prevent-secrets --- -## `scorecard.yml` — OpenSSF Scorecard +## `scorecard.yml`. OpenSSF Scorecard **Fires on:** weekly cron (Monday ~01:30 UTC) + push to `main` + branch-protection-rule changes. **Does:** Runs the OpenSSF Scorecard action against the repo and uploads results. Badge in README reflects current score. -**Config:** `continue-on-error: true` on private repos (Scorecard doesn't work well on private) — no noise for repos it can't give a useful answer on. +**Config:** `continue-on-error: true` on private repos (Scorecard doesn't work well on private). No noise for repos it can't give a useful answer on. **Why it matters:** Third-party audit of branch protection, token scoping, dependency pinning, etc. Catches drift. See [Security Posture philosophy §5](../philosophies/security-posture.md#5-supply-chain-hygiene-via-scorecard). --- -## `stale.yml` — issue/PR hygiene +## `stale.yml`. Issue/PR hygiene **Fires on:** daily cron (09:00 UTC). @@ -153,17 +153,17 @@ See [Security Posture §4](../philosophies/security-posture.md#4-prevent-secrets --- -## `acceptance.yml` — heavy end-to-end tests (where applicable) +## `acceptance.yml`. Heavy end-to-end tests (where applicable) **Fires on:** PRs targeting `main` + `workflow_dispatch`. **Not on `develop` PRs.** -**Does:** Runs the full end-to-end test suite for repos that have one — e.g. `mac-dev-setup` boots a Tart VM and runs `setup.sh` against fresh macOS; `claude-teams-operator` spins up a Kind cluster and runs the controller suite. +**Does:** Runs the full end-to-end test suite for repos that have one. E.g. `mac-dev-setup` boots a Tart VM and runs `setup.sh` against fresh macOS; `claude-teams-operator` spins up a Kind cluster and runs the controller suite. **Why gated to `main` PRs:** these tests take 30–45 minutes and consume real infrastructure. Running them on every `develop` PR would be expensive and would train me to ignore failing checks. Gating to the release PR catches release-blocking regressions at the right moment. See [Testing philosophy §3](../philosophies/testing.md#3-acceptance-tests-gate-releases-not-every-pr). --- -## Dependabot (`.github/dependabot.yml`) — not a workflow, but adjacent +## Dependabot (`.github/dependabot.yml`). Not a workflow, but adjacent **Fires on:** weekly schedule (Monday). @@ -177,7 +177,7 @@ See [Security Posture §4](../philosophies/security-posture.md#4-prevent-secrets ## Applying this to a new repo -1. Run `/create-repo ` — creates from the template with all of the above already wired. +1. Run `/create-repo `. Creates from the template with all of the above already wired. 2. Customize `validate.yml`'s lint step for the project's language/toolchain. 3. Customize `labeler.yml` paths for the repo's structure. 4. Rerun `/setup-repo ` to sync required-check names in branch protection after you've renamed any jobs. @@ -189,7 +189,7 @@ That's the whole setup. No per-repo reinvention. ## Related -- [Release Cadence philosophy](../philosophies/release-cadence.md) — why the release automation looks like this. -- [Security Posture philosophy](../philosophies/security-posture.md) — why the security automation is the baseline. -- [Testing philosophy](../philosophies/testing.md) — why acceptance tests gate releases and nothing else. -- [Branching & Releases workflow](branching-and-releases.md) — how releases actually happen, end-to-end. +- [Release Cadence philosophy](../philosophies/release-cadence.md). Why the release automation looks like this. +- [Security Posture philosophy](../philosophies/security-posture.md). Why the security automation is the baseline. +- [Testing philosophy](../philosophies/testing.md). Why acceptance tests gate releases and nothing else. +- [Branching & Releases workflow](branching-and-releases.md). How releases actually happen, end-to-end.