From 1a21eb4b41280251032a421ecf7f05051057f460 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Fri, 14 Aug 2026 15:56:08 -0700 Subject: [PATCH 01/12] Add a Branch-Selection Rule for Negative Existence Claims GOVERNANCE.md "Verification Discipline" already covers a clone that fetched stale content, but not a clone that fetched correctly from the wrong ref. Add a sibling bullet requiring a "does not exist" claim to name the branch it was checked against and to check the branch the repo's own model designates as current before asserting absence repo-wide. Mirror the same rule in the agent-conduct skill's "Before Claiming Done" excerpt. Fixes #714 Co-Authored-By: Claude Sonnet 5 --- .agents/skills/agent-conduct/SKILL.md | 1 + GOVERNANCE.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.agents/skills/agent-conduct/SKILL.md b/.agents/skills/agent-conduct/SKILL.md index 77647925..586a2070 100644 --- a/.agents/skills/agent-conduct/SKILL.md +++ b/.agents/skills/agent-conduct/SKILL.md @@ -19,6 +19,7 @@ Read `GOVERNANCE.md` "Verification Discipline" before reporting success on anyth - **Run the repo's whole lint gate before every push**, not the parts that look relevant, because the tool most likely to catch a change is often the one it seems least about. - **A launched process is not a result.** Report the output the wait produced, and where it produced none, that absence is the report. Never name an external cause the record does not carry. - **A local clone is not the branch it names.** Fetch immediately before reading, or read the live ref, and name the ref and commit in any finding a local read produced. +- **A "does not exist" claim names the branch it was checked against.** A worktree's default branch is not necessarily the one the content lives on: in-flight content on a `release`-model repo lands on `develop` before `main`, per `GOVERNANCE.md` "Branching Model," so check that branch before reporting anything absent repo-wide. - **A test asserts the mechanism it names, and a gate has to be watched failing.** A case that passes for an incidental reason is worse than no case, because it is later cited as evidence. Claims about a pull request being reviewed, clean, or mergeable are owned by the `pr-review-conduct` skill, and claims that a commit landed by `git-commit-conventions`. diff --git a/GOVERNANCE.md b/GOVERNANCE.md index be38ed0d..0a8a08b2 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -166,6 +166,7 @@ The checks that separate work actually done from work that merely reports succes - **Never edit an active `.code-workspace` file.** A workspace file rewritten on disk can make VS Code reload the window, and a reload destroys the running agent session's context, so the work in flight is lost with nothing to catch it, and the trigger is not fully characterized (an agent's edit has caused the reload where a human's identical edit did not). Surface the needed change for the maintainer to apply by hand. - **A green check is not evidence the work happened.** A skipped job and a passing job are indistinguishable in the aggregated required check. When a job exists to exercise something, confirm from its log that it ran and produced the output it promises. - **A local clone is not the branch it names, it is whatever that clone last fetched.** Reading a checkout on disk answers what that clone last saw, so a finding taken from one carries a date nobody stated, and two failures of exactly that shape are on record from one session: a repository reported as still drifted on a file whose fix had already merged, and a repository reported as missing a file it carries because the checkout sat on an older branch. Read the live ref through the API where the claim will be acted on, or fetch immediately before reading, and name the ref and the commit in any finding a local read produced. A clone stays the right tool for anything needing history or a build, which an API read cannot give. +- **A "does not exist" claim names the branch it was checked against.** A worktree or checkout answers for whichever ref it was built from, and that ref is not necessarily the one the content lives on: a `release`-model repo carries in-flight content on `develop`, per "Branching Model" above, well before it reaches `main`, so a worktree defaulted to the fleet's default branch can hold nothing while the repository holds everything. Before reporting a file, a directory, or a piece of content as absent anywhere in a repo, check it against the branch the repo's own model designates as current for that kind of content, not only whichever branch a worktree or checkout happened to default to, and name the branch the negative claim was checked against in the finding itself. - **A launched process is not a result, and a cause nobody observed is not a diagnosis.** "The watcher is armed" names a process rather than a finding, so what gets reported is the output that process produced, and where it produced none, that absence is the report. The failure it prevents is an agent standing still on a condition that was met half an hour earlier, having announced the wait and never read it. Naming an external cause for such a stall afterwards, a throttle or a quota that appears nowhere in the record, turns a local defect into a story about someone else and closes the investigation on the wrong party, so read the record for the cause before naming one, and where the record does not carry it, report the cause as unknown. - **A workflow change is only fully exercised by CI.** Extracting a `run:` block and executing it locally validates the script and nothing else, because `secrets: inherit`, `permissions:`, `needs:` wiring, and reusable-workflow inputs resolve only in a real run. - **A review flags an instance, so fix the class.** When a reviewer cites one stale claim, one silent-narrowing pattern, or one mis-worded contract, sweep for its siblings before replying. Reviewers sample rather than enumerate. From e91e06af02bcb1798086597ba82ab0c63c3239f5 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Fri, 14 Aug 2026 15:59:32 -0700 Subject: [PATCH 02/12] Make the GitHub-Default-Branch vs. Develop Distinction Explicit Both GOVERNANCE.md "Branching Model" and the operational-vs-release-workflow skill described the develop -> main flow without ever stating the specific trap that caused #714: GitHub's own 'default branch' repository setting reads main, but develop is where work starts and where in-flight content lives. A tool that branches from 'the default branch' lands on main and can silently miss content already merged to develop. State that explicitly in both places. Refs #714 Co-Authored-By: Claude Sonnet 5 --- .agents/skills/operational-vs-release-workflow/SKILL.md | 7 +++++++ GOVERNANCE.md | 9 +++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.agents/skills/operational-vs-release-workflow/SKILL.md b/.agents/skills/operational-vs-release-workflow/SKILL.md index 4a0cb078..9151d35d 100644 --- a/.agents/skills/operational-vs-release-workflow/SKILL.md +++ b/.agents/skills/operational-vs-release-workflow/SKILL.md @@ -41,6 +41,13 @@ rather than guessing from the repo's contents. ## Branching (release model) +- **GitHub's repository setting for "default branch" reads `main`, but `develop` is where work + starts and where in-flight content lives.** A tool that branches from "the default branch" + (a fresh worktree, a new clone's initial checkout) lands on `main` and can silently miss + content that has merged to `develop` but not yet been promoted. Before branching off, cloning + for, or asserting something absent from this repo, check `develop`, not just whichever branch + a tool defaulted to. See GOVERNANCE.md "Verification Discipline" on naming the branch a + "does not exist" claim was checked against. - `develop` is the integration branch. Feature branches -> `develop` is **squash-only**, which keeps `develop` linear. - `develop -> main` is **merge-commit only** (no squash, no rebase). Merge commits preserve diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 0a8a08b2..2c342c49 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -57,8 +57,13 @@ Two workflow models, set per repo by the registry `workflowModel` field. Most re `release`: squash-only feature branches into `develop`, merge-commit-only `develop -> main` promotions, forward-only with no back-merges, and two promotion traps worth knowing before the first one (never delete `develop`, resolve an EOL-only conflict by taking `develop`'s side). -**Operational** repos differ substantially (direct-to-`develop`, advisory CI, dispatch-only -release), covered as a delta rather than a separate model. +**GitHub's own "default branch" repository setting reads `main`, but `develop` is where work +starts and where in-flight content lives**, so a tool that branches from "the default branch" +(a fresh worktree, a new clone) lands on `main` and can silently miss content already merged to +`develop` but not yet promoted. Branch from `develop` for a `release`-model repo unless the task +is explicitly about `main`-only content. **Operational** repos differ substantially +(direct-to-`develop`, advisory CI, dispatch-only release), covered as a delta rather than a +separate model. This is packaged as the `operational-vs-release-workflow` Skill at `.agents/skills/operational-vs-release-workflow/SKILL.md` in the hub, not a repo-relative link From 6263752deab3a5da017dae322f5478f677a423cb Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Fri, 14 Aug 2026 16:05:37 -0700 Subject: [PATCH 03/12] Tighten Wording and Regenerate the Stale Dist Skills Address Copilot review feedback on PR #716: 'a fresh worktree, a new clone' overclaimed that any newly created worktree lands on the default branch, when the actual pitfall is one that defaults to it. Rephrase both spots as 'a worktree or clone that defaults to the default branch'. Also run scripts/build_dist.py, which the prior two commits left stale (the CI 'Lint sources job' check caught this) since .agents/skills content is mirrored into .claude-plugin/fleet-skills. Refs #714 Co-Authored-By: Claude Sonnet 5 --- .../skills/operational-vs-release-workflow/SKILL.md | 11 +++++------ .claude-plugin/fleet-skills/.source-digest | 2 +- .../fleet-skills/skills/agent-conduct/SKILL.md | 1 + .../skills/operational-vs-release-workflow/SKILL.md | 6 ++++++ GOVERNANCE.md | 8 ++++---- 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/.agents/skills/operational-vs-release-workflow/SKILL.md b/.agents/skills/operational-vs-release-workflow/SKILL.md index 9151d35d..8307f6c4 100644 --- a/.agents/skills/operational-vs-release-workflow/SKILL.md +++ b/.agents/skills/operational-vs-release-workflow/SKILL.md @@ -42,12 +42,11 @@ rather than guessing from the repo's contents. ## Branching (release model) - **GitHub's repository setting for "default branch" reads `main`, but `develop` is where work - starts and where in-flight content lives.** A tool that branches from "the default branch" - (a fresh worktree, a new clone's initial checkout) lands on `main` and can silently miss - content that has merged to `develop` but not yet been promoted. Before branching off, cloning - for, or asserting something absent from this repo, check `develop`, not just whichever branch - a tool defaulted to. See GOVERNANCE.md "Verification Discipline" on naming the branch a - "does not exist" claim was checked against. + starts and where in-flight content lives.** A worktree or clone that defaults to "the default + branch" lands on `main` and can silently miss content that has merged to `develop` but not yet + been promoted. Before branching off, cloning for, or asserting something absent from this repo, + check `develop`, not just whichever branch a tool defaulted to. See GOVERNANCE.md "Verification + Discipline" on naming the branch a "does not exist" claim was checked against. - `develop` is the integration branch. Feature branches -> `develop` is **squash-only**, which keeps `develop` linear. - `develop -> main` is **merge-commit only** (no squash, no rebase). Merge commits preserve diff --git a/.claude-plugin/fleet-skills/.source-digest b/.claude-plugin/fleet-skills/.source-digest index 134eeaa7..86d9993d 100644 --- a/.claude-plugin/fleet-skills/.source-digest +++ b/.claude-plugin/fleet-skills/.source-digest @@ -1 +1 @@ -f9e5473792ab198e +45204450870e4fb2 diff --git a/.claude-plugin/fleet-skills/skills/agent-conduct/SKILL.md b/.claude-plugin/fleet-skills/skills/agent-conduct/SKILL.md index 77647925..586a2070 100644 --- a/.claude-plugin/fleet-skills/skills/agent-conduct/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/agent-conduct/SKILL.md @@ -19,6 +19,7 @@ Read `GOVERNANCE.md` "Verification Discipline" before reporting success on anyth - **Run the repo's whole lint gate before every push**, not the parts that look relevant, because the tool most likely to catch a change is often the one it seems least about. - **A launched process is not a result.** Report the output the wait produced, and where it produced none, that absence is the report. Never name an external cause the record does not carry. - **A local clone is not the branch it names.** Fetch immediately before reading, or read the live ref, and name the ref and commit in any finding a local read produced. +- **A "does not exist" claim names the branch it was checked against.** A worktree's default branch is not necessarily the one the content lives on: in-flight content on a `release`-model repo lands on `develop` before `main`, per `GOVERNANCE.md` "Branching Model," so check that branch before reporting anything absent repo-wide. - **A test asserts the mechanism it names, and a gate has to be watched failing.** A case that passes for an incidental reason is worse than no case, because it is later cited as evidence. Claims about a pull request being reviewed, clean, or mergeable are owned by the `pr-review-conduct` skill, and claims that a commit landed by `git-commit-conventions`. diff --git a/.claude-plugin/fleet-skills/skills/operational-vs-release-workflow/SKILL.md b/.claude-plugin/fleet-skills/skills/operational-vs-release-workflow/SKILL.md index 4a0cb078..8307f6c4 100644 --- a/.claude-plugin/fleet-skills/skills/operational-vs-release-workflow/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/operational-vs-release-workflow/SKILL.md @@ -41,6 +41,12 @@ rather than guessing from the repo's contents. ## Branching (release model) +- **GitHub's repository setting for "default branch" reads `main`, but `develop` is where work + starts and where in-flight content lives.** A worktree or clone that defaults to "the default + branch" lands on `main` and can silently miss content that has merged to `develop` but not yet + been promoted. Before branching off, cloning for, or asserting something absent from this repo, + check `develop`, not just whichever branch a tool defaulted to. See GOVERNANCE.md "Verification + Discipline" on naming the branch a "does not exist" claim was checked against. - `develop` is the integration branch. Feature branches -> `develop` is **squash-only**, which keeps `develop` linear. - `develop -> main` is **merge-commit only** (no squash, no rebase). Merge commits preserve diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 2c342c49..6b2694eb 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -58,10 +58,10 @@ Two workflow models, set per repo by the registry `workflowModel` field. Most re promotions, forward-only with no back-merges, and two promotion traps worth knowing before the first one (never delete `develop`, resolve an EOL-only conflict by taking `develop`'s side). **GitHub's own "default branch" repository setting reads `main`, but `develop` is where work -starts and where in-flight content lives**, so a tool that branches from "the default branch" -(a fresh worktree, a new clone) lands on `main` and can silently miss content already merged to -`develop` but not yet promoted. Branch from `develop` for a `release`-model repo unless the task -is explicitly about `main`-only content. **Operational** repos differ substantially +starts and where in-flight content lives**, so a worktree or clone that defaults to "the default +branch" lands on `main` and can silently miss content already merged to `develop` but not yet +promoted. Branch from `develop` for a `release`-model repo unless the task is explicitly about +`main`-only content. **Operational** repos differ substantially (direct-to-`develop`, advisory CI, dispatch-only release), covered as a delta rather than a separate model. From 5542ae66b99fdf3181cdab0c55e0c845a609af20 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Fri, 14 Aug 2026 16:11:01 -0700 Subject: [PATCH 04/12] Stop Wrapping Bold Text Across a Line Break The bold span opening the branching-model addition broke across a hard-wrapped line, the last GOVERNANCE.md pattern of its kind and inconsistent with the majority style. Address Copilot's on-head finding by keeping each bold span on one line. Refs #714 Co-Authored-By: Claude Sonnet 5 --- .../skills/operational-vs-release-workflow/SKILL.md | 7 +------ .claude-plugin/fleet-skills/.source-digest | 2 +- .../skills/operational-vs-release-workflow/SKILL.md | 7 +------ GOVERNANCE.md | 10 +++------- 4 files changed, 6 insertions(+), 20 deletions(-) diff --git a/.agents/skills/operational-vs-release-workflow/SKILL.md b/.agents/skills/operational-vs-release-workflow/SKILL.md index 8307f6c4..a1e4bf6d 100644 --- a/.agents/skills/operational-vs-release-workflow/SKILL.md +++ b/.agents/skills/operational-vs-release-workflow/SKILL.md @@ -41,12 +41,7 @@ rather than guessing from the repo's contents. ## Branching (release model) -- **GitHub's repository setting for "default branch" reads `main`, but `develop` is where work - starts and where in-flight content lives.** A worktree or clone that defaults to "the default - branch" lands on `main` and can silently miss content that has merged to `develop` but not yet - been promoted. Before branching off, cloning for, or asserting something absent from this repo, - check `develop`, not just whichever branch a tool defaulted to. See GOVERNANCE.md "Verification - Discipline" on naming the branch a "does not exist" claim was checked against. +- **GitHub's repository setting for "default branch" reads `main`, but `develop` is where work starts and where in-flight content lives.** A worktree or clone that defaults to "the default branch" lands on `main` and can silently miss content that has merged to `develop` but not yet been promoted. Before branching off a change, or asserting something absent from this repo, check `develop`, not just whichever branch a tool defaulted to. See GOVERNANCE.md "Verification Discipline" on naming the branch a "does not exist" claim was checked against. - `develop` is the integration branch. Feature branches -> `develop` is **squash-only**, which keeps `develop` linear. - `develop -> main` is **merge-commit only** (no squash, no rebase). Merge commits preserve diff --git a/.claude-plugin/fleet-skills/.source-digest b/.claude-plugin/fleet-skills/.source-digest index 86d9993d..b7116b38 100644 --- a/.claude-plugin/fleet-skills/.source-digest +++ b/.claude-plugin/fleet-skills/.source-digest @@ -1 +1 @@ -45204450870e4fb2 +b70700af99465f0a diff --git a/.claude-plugin/fleet-skills/skills/operational-vs-release-workflow/SKILL.md b/.claude-plugin/fleet-skills/skills/operational-vs-release-workflow/SKILL.md index 8307f6c4..a1e4bf6d 100644 --- a/.claude-plugin/fleet-skills/skills/operational-vs-release-workflow/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/operational-vs-release-workflow/SKILL.md @@ -41,12 +41,7 @@ rather than guessing from the repo's contents. ## Branching (release model) -- **GitHub's repository setting for "default branch" reads `main`, but `develop` is where work - starts and where in-flight content lives.** A worktree or clone that defaults to "the default - branch" lands on `main` and can silently miss content that has merged to `develop` but not yet - been promoted. Before branching off, cloning for, or asserting something absent from this repo, - check `develop`, not just whichever branch a tool defaulted to. See GOVERNANCE.md "Verification - Discipline" on naming the branch a "does not exist" claim was checked against. +- **GitHub's repository setting for "default branch" reads `main`, but `develop` is where work starts and where in-flight content lives.** A worktree or clone that defaults to "the default branch" lands on `main` and can silently miss content that has merged to `develop` but not yet been promoted. Before branching off a change, or asserting something absent from this repo, check `develop`, not just whichever branch a tool defaulted to. See GOVERNANCE.md "Verification Discipline" on naming the branch a "does not exist" claim was checked against. - `develop` is the integration branch. Feature branches -> `develop` is **squash-only**, which keeps `develop` linear. - `develop -> main` is **merge-commit only** (no squash, no rebase). Merge commits preserve diff --git a/GOVERNANCE.md b/GOVERNANCE.md index fcb64d7f..42701e8c 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -57,13 +57,9 @@ Two workflow models, set per repo by the registry `workflowModel` field. Most re `release`: squash-only feature branches into `develop`, merge-commit-only `develop -> main` promotions, forward-only with no back-merges, and two promotion traps worth knowing before the first one (never delete `develop`, resolve an EOL-only conflict by taking `develop`'s side). -**GitHub's own "default branch" repository setting reads `main`, but `develop` is where work -starts and where in-flight content lives**, so a worktree or clone that defaults to "the default -branch" lands on `main` and can silently miss content already merged to `develop` but not yet -promoted. Branch from `develop` for a `release`-model repo unless the task is explicitly about -`main`-only content. **Operational** repos differ substantially -(direct-to-`develop`, advisory CI, dispatch-only release), covered as a delta rather than a -separate model. +**GitHub's own "default branch" repository setting reads `main`, but `develop` is where work starts and where in-flight content lives**, so a worktree or clone that defaults to "the default branch" lands on `main` and can silently miss content already merged to `develop` but not yet promoted. Branch from `develop` for a `release`-model repo unless the task is explicitly about `main`-only content. +**Operational** repos differ substantially (direct-to-`develop`, advisory CI, dispatch-only +release), covered as a delta rather than a separate model. This is packaged as the `operational-vs-release-workflow` Skill at `.agents/skills/operational-vs-release-workflow/SKILL.md` in the hub, not a repo-relative link From e456de44917b27fac477ec34c52d6b31e36440f8 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Fri, 14 Aug 2026 16:16:22 -0700 Subject: [PATCH 05/12] Insist on a Unique Worktree for Every Task and Package It as the repo-worktree Skill All new work, a continuation of a prior session's task included, now begins by creating its own git worktree before the first file edit, based on the branch work starts on rather than whichever branch a tool defaulted to. The rule lands in GOVERNANCE.md "Repository Boundaries and Write Safety", is surfaced at the task-start moment by a new repo-worktree skill that also carries the fleet worktree layout convention and cleanup mechanics, and is mirrored into the host-wide agent-safety block so Claude Code's EnterWorktree tool is armed even outside a conformant repo. git-commit-conventions now requires staging by explicit path, never a blanket add, and treats any change the session did not make as a stop rather than something to bundle. Includes the fix-714-branch-verification branch (PR #716), so the base-branch rule and the worktree mandate land as one consolidated update, and this pull request supersedes PR #716. Closes #711. Closes #714. Co-Authored-By: Claude Fable 5 --- .../skills/git-commit-conventions/SKILL.md | 18 +++- .../operational-vs-release-workflow/SKILL.md | 2 +- .agents/skills/repo-worktree/SKILL.md | 93 +++++++++++++++++++ .../fleet-skills/.claude-plugin/plugin.json | 1 + .claude-plugin/fleet-skills/.source-digest | 2 +- .../skills/git-commit-conventions/SKILL.md | 18 +++- .../operational-vs-release-workflow/SKILL.md | 2 +- .../skills/repo-worktree/SKILL.md | 93 +++++++++++++++++++ AGENTS.md | 6 +- GOVERNANCE.md | 10 +- host-setup/agent-safety/README.md | 2 +- host-setup/agent-safety/claude-md-safety.md | 1 + 12 files changed, 229 insertions(+), 19 deletions(-) create mode 100644 .agents/skills/repo-worktree/SKILL.md create mode 100644 .claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md diff --git a/.agents/skills/git-commit-conventions/SKILL.md b/.agents/skills/git-commit-conventions/SKILL.md index 7ff9e43b..9790f987 100644 --- a/.agents/skills/git-commit-conventions/SKILL.md +++ b/.agents/skills/git-commit-conventions/SKILL.md @@ -32,6 +32,12 @@ scope-widened commit, a rewritten shared history, a destructive reset). developer unless the developer has explicitly authorized committing for the current ask ("commit this", "open a PR"). Authorization is scope-bound: it covers the commits that specific task needs, not a blanket license for the rest of the session. +- **Stage by explicit path, never `git add -A` or `git add .`.** A blanket add stages whatever + else happens to be in the tree, and what it sweeps in is another task's uncommitted work, + landing in a commit whose subject never mentions it, committed by a session that never saw it. + That sweep has happened, which is why task isolation exists (the `repo-worktree` skill), and + isolation makes a shared tree rare rather than impossible. Name the files this task changed, + and let anything else stay unstaged. - **"Commit" means commit and push.** An authorization to commit carries the push to the feature branch the work belongs on, because nothing reviews a local commit. The Copilot review loop, the required status checks, and the maintainer all read the remote, so stopping at `git commit` @@ -39,10 +45,14 @@ scope-widened commit, a rewritten shared history, a destructive reset). progress while none of the gates have run. Push to the feature branch, never to a protected branch, and never with `--force`. Holding a commit locally is the narrower case: it happens when the developer asks for it, not by default. -- **Check `git status` for the maintainer's own uncommitted edits before committing.** The - maintainer hand-edits files live, often `README.md`/`HISTORY.md`, sometimes with an editor's - LF -> CRLF flip on top. If there are changes not made this session, ask whether to include them - rather than bundling half-finished work or stranding it in an unrelated commit. +- **Check `git status` before committing, and treat any change this session did not make as a + stop.** The maintainer hand-edits files live, often `README.md`/`HISTORY.md`, sometimes with an + editor's LF -> CRLF flip on top, and a sibling agent session sharing the tree leaves its edits + the same way. Whoever the author is, a change this session did not make is never bundled: ask + whether to include it, or leave it unstaged and say so, rather than committing half-finished + work or stranding it in an unrelated commit. An unexpected change in the tree is also the + signal to re-check isolation per the `repo-worktree` skill, since it may mean another task is + live in this checkout. ## Signing, verified not configured diff --git a/.agents/skills/operational-vs-release-workflow/SKILL.md b/.agents/skills/operational-vs-release-workflow/SKILL.md index a1e4bf6d..ad30d26c 100644 --- a/.agents/skills/operational-vs-release-workflow/SKILL.md +++ b/.agents/skills/operational-vs-release-workflow/SKILL.md @@ -41,7 +41,7 @@ rather than guessing from the repo's contents. ## Branching (release model) -- **GitHub's repository setting for "default branch" reads `main`, but `develop` is where work starts and where in-flight content lives.** A worktree or clone that defaults to "the default branch" lands on `main` and can silently miss content that has merged to `develop` but not yet been promoted. Before branching off a change, or asserting something absent from this repo, check `develop`, not just whichever branch a tool defaulted to. See GOVERNANCE.md "Verification Discipline" on naming the branch a "does not exist" claim was checked against. +- **GitHub's repository setting for "default branch" reads `main`, but `develop` is where work starts and where in-flight content lives.** A worktree or clone that defaults to "the default branch" lands on `main` and can silently miss content that has merged to `develop` but not yet been promoted. Before branching off a change, or asserting something absent from this repo, check `develop`, not just whichever branch a tool defaulted to. See GOVERNANCE.md "Verification Discipline" on naming the branch a "does not exist" claim was checked against, and the `repo-worktree` skill, which owns the worktree-creation moment this base-branch choice is made at. - `develop` is the integration branch. Feature branches -> `develop` is **squash-only**, which keeps `develop` linear. - `develop -> main` is **merge-commit only** (no squash, no rebase). Merge commits preserve diff --git a/.agents/skills/repo-worktree/SKILL.md b/.agents/skills/repo-worktree/SKILL.md new file mode 100644 index 00000000..d2154df4 --- /dev/null +++ b/.agents/skills/repo-worktree/SKILL.md @@ -0,0 +1,93 @@ +--- +name: repo-worktree +description: >- + Mandates and mechanizes task isolation in ptr727/ProjectTemplate fleet repos: every task, + including a continuation of a prior session's task, creates its own git worktree on its own + feature branch before its first file edit, based on the branch work starts on (develop for a + release-model repo, never whichever branch a tool defaulted to). Also wraps the mechanics: + creating a worktree with git worktree add, the fleet layout convention, listing what is in + flight, and removing a worktree and its branch after merge. Use this whenever about to create + or edit files in a fleet repo, whenever starting or resuming a task, whenever the task's + branch is already checked out in a shared checkout, and whenever creating, listing, or + removing a worktree. Triggers even when the session was launched in the primary checkout or + the change looks like a one-line fix, because the primary checkout is the maintainer's own + surface and the incident this guards against was two sessions sharing one checkout, each + session's blanket add committing the other's uncommitted files. +--- + +# Repo Worktree + +## Why This Exists + +Two agent sessions once ran concurrently in the same primary checkout, on the same feature +branch, neither knowing the other was in the tree. One session's commits swept in the other +session's uncommitted files, so two commits landed carrying work their subjects never mention, +committed by a task that never saw it. No rule fired at the moment it was violated, which is the +first file edit: the commit-time and review-time skills all run after a sweep has already +happened. This skill is that missing task-start surface. `GOVERNANCE.md` "Repository Boundaries +and Write Safety" keeps the isolation law and wins on any disagreement, and the mechanics below +are this skill's own content. + +## The Mandate + +- **Every task isolates into its own worktree before its first file edit.** All new work begins + by creating a unique worktree (or clone) on its own feature branch. The primary checkout is + the maintainer's own surface, so a session launched there isolates before writing rather than + after noticing contention. +- **A continuation re-isolates.** A session resuming prior work finds its branch already checked + out somewhere and naturally resumes there, and that instinct is the hazard: a branch sitting + checked out in a shared tree is exactly how two sessions end up in one checkout. Create a + fresh worktree for the continuation and check the branch out there. +- **The moment is the first file edit, not the commit.** By commit time another task's + uncommitted work can already be swept into the staging area, so isolating late protects + nothing. Reading anywhere is fine, and the worktree exists before the first write. +- **Someone else's tree stays theirs.** A branch that changes when nothing you did changed it, + or an edit of yours reverted with no conflict, means another task is live in that tree, and + the response is to stop rather than to re-apply the edit, per `GOVERNANCE.md` "Repository + Boundaries and Write Safety". + +## The Base Branch + +Base the worktree on the branch work starts on for the repository's model, not on whichever +branch a tool defaulted to. GitHub's own "default branch" setting reads `main`, but on both +fleet workflow models work starts on `develop`, so a worktree defaulted to "the default branch" +lands on `main` and silently misses everything merged to `develop` but not yet promoted. Branch +from `develop` unless the task is explicitly about `main`-only content, per `GOVERNANCE.md` +"Branching Model". Fetch immediately before creating and base on the remote ref, because a clone +is whatever it last fetched rather than the branch it names. + +## Creating a Worktree + +The fleet layout convention keeps every base clone and every in-flight task visible in one +place, with no owner segment since every repo here is under one owner: + +```text +~/repos/ base clone, on its default/working branch +~/repos/worktrees/- one worktree per in-flight task, own branch +``` + +```sh +git -C ~/repos/ fetch origin develop +git -C ~/repos/ worktree add ~/repos/worktrees/- -b origin/develop +``` + +A machine not yet migrated to this layout still isolates exactly the same way, since the mandate +is the isolation rather than the path: create the worktree beside whatever layout the machine +has, and note that the base clone may live elsewhere than `~/repos/`. + +Claude Code's own `EnterWorktree` tool acts only on an explicit instruction from the user or the +project instructions, which is why the carried rules state this mandate in so many words. Given +a `name`, it creates the worktree under `.claude/worktrees/` inside the repo and bases it on the +GitHub default branch, which is the wrong path and the wrong base here. Create the worktree with +`git worktree add` as above, then attach with `EnterWorktree` `path:`, not `name:`. + +## Listing and Cleanup + +- `git worktree list`, run in any checkout of a repo, names that repo's base clone and every + worktree with its branch. On the convention layout, one `ls ~/repos/worktrees/` reads what is + in flight across the whole fleet. +- After the task's pull request merges, remove the worktree and its branch from the base clone: + `git worktree remove ~/repos/worktrees/-`, then `git branch -d `. +- A worktree that refuses removal is dirty, and force is not the fix: look at what is + uncommitted in it first, since discarding uncommitted work runs only on explicit instruction, + per the `git-commit-conventions` skill. diff --git a/.claude-plugin/fleet-skills/.claude-plugin/plugin.json b/.claude-plugin/fleet-skills/.claude-plugin/plugin.json index aac58e07..00016e2f 100644 --- a/.claude-plugin/fleet-skills/.claude-plugin/plugin.json +++ b/.claude-plugin/fleet-skills/.claude-plugin/plugin.json @@ -17,6 +17,7 @@ "./skills/operational-vs-release-workflow", "./skills/pr-review-conduct", "./skills/python-codestyle", + "./skills/repo-worktree", "./skills/resync-a-repo", "./skills/shell-codestyle", "./skills/skill-lifecycle", diff --git a/.claude-plugin/fleet-skills/.source-digest b/.claude-plugin/fleet-skills/.source-digest index b7116b38..e21aa20a 100644 --- a/.claude-plugin/fleet-skills/.source-digest +++ b/.claude-plugin/fleet-skills/.source-digest @@ -1 +1 @@ -b70700af99465f0a +51333ca94cd8a1d6 diff --git a/.claude-plugin/fleet-skills/skills/git-commit-conventions/SKILL.md b/.claude-plugin/fleet-skills/skills/git-commit-conventions/SKILL.md index 7ff9e43b..9790f987 100644 --- a/.claude-plugin/fleet-skills/skills/git-commit-conventions/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/git-commit-conventions/SKILL.md @@ -32,6 +32,12 @@ scope-widened commit, a rewritten shared history, a destructive reset). developer unless the developer has explicitly authorized committing for the current ask ("commit this", "open a PR"). Authorization is scope-bound: it covers the commits that specific task needs, not a blanket license for the rest of the session. +- **Stage by explicit path, never `git add -A` or `git add .`.** A blanket add stages whatever + else happens to be in the tree, and what it sweeps in is another task's uncommitted work, + landing in a commit whose subject never mentions it, committed by a session that never saw it. + That sweep has happened, which is why task isolation exists (the `repo-worktree` skill), and + isolation makes a shared tree rare rather than impossible. Name the files this task changed, + and let anything else stay unstaged. - **"Commit" means commit and push.** An authorization to commit carries the push to the feature branch the work belongs on, because nothing reviews a local commit. The Copilot review loop, the required status checks, and the maintainer all read the remote, so stopping at `git commit` @@ -39,10 +45,14 @@ scope-widened commit, a rewritten shared history, a destructive reset). progress while none of the gates have run. Push to the feature branch, never to a protected branch, and never with `--force`. Holding a commit locally is the narrower case: it happens when the developer asks for it, not by default. -- **Check `git status` for the maintainer's own uncommitted edits before committing.** The - maintainer hand-edits files live, often `README.md`/`HISTORY.md`, sometimes with an editor's - LF -> CRLF flip on top. If there are changes not made this session, ask whether to include them - rather than bundling half-finished work or stranding it in an unrelated commit. +- **Check `git status` before committing, and treat any change this session did not make as a + stop.** The maintainer hand-edits files live, often `README.md`/`HISTORY.md`, sometimes with an + editor's LF -> CRLF flip on top, and a sibling agent session sharing the tree leaves its edits + the same way. Whoever the author is, a change this session did not make is never bundled: ask + whether to include it, or leave it unstaged and say so, rather than committing half-finished + work or stranding it in an unrelated commit. An unexpected change in the tree is also the + signal to re-check isolation per the `repo-worktree` skill, since it may mean another task is + live in this checkout. ## Signing, verified not configured diff --git a/.claude-plugin/fleet-skills/skills/operational-vs-release-workflow/SKILL.md b/.claude-plugin/fleet-skills/skills/operational-vs-release-workflow/SKILL.md index a1e4bf6d..ad30d26c 100644 --- a/.claude-plugin/fleet-skills/skills/operational-vs-release-workflow/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/operational-vs-release-workflow/SKILL.md @@ -41,7 +41,7 @@ rather than guessing from the repo's contents. ## Branching (release model) -- **GitHub's repository setting for "default branch" reads `main`, but `develop` is where work starts and where in-flight content lives.** A worktree or clone that defaults to "the default branch" lands on `main` and can silently miss content that has merged to `develop` but not yet been promoted. Before branching off a change, or asserting something absent from this repo, check `develop`, not just whichever branch a tool defaulted to. See GOVERNANCE.md "Verification Discipline" on naming the branch a "does not exist" claim was checked against. +- **GitHub's repository setting for "default branch" reads `main`, but `develop` is where work starts and where in-flight content lives.** A worktree or clone that defaults to "the default branch" lands on `main` and can silently miss content that has merged to `develop` but not yet been promoted. Before branching off a change, or asserting something absent from this repo, check `develop`, not just whichever branch a tool defaulted to. See GOVERNANCE.md "Verification Discipline" on naming the branch a "does not exist" claim was checked against, and the `repo-worktree` skill, which owns the worktree-creation moment this base-branch choice is made at. - `develop` is the integration branch. Feature branches -> `develop` is **squash-only**, which keeps `develop` linear. - `develop -> main` is **merge-commit only** (no squash, no rebase). Merge commits preserve diff --git a/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md b/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md new file mode 100644 index 00000000..d2154df4 --- /dev/null +++ b/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md @@ -0,0 +1,93 @@ +--- +name: repo-worktree +description: >- + Mandates and mechanizes task isolation in ptr727/ProjectTemplate fleet repos: every task, + including a continuation of a prior session's task, creates its own git worktree on its own + feature branch before its first file edit, based on the branch work starts on (develop for a + release-model repo, never whichever branch a tool defaulted to). Also wraps the mechanics: + creating a worktree with git worktree add, the fleet layout convention, listing what is in + flight, and removing a worktree and its branch after merge. Use this whenever about to create + or edit files in a fleet repo, whenever starting or resuming a task, whenever the task's + branch is already checked out in a shared checkout, and whenever creating, listing, or + removing a worktree. Triggers even when the session was launched in the primary checkout or + the change looks like a one-line fix, because the primary checkout is the maintainer's own + surface and the incident this guards against was two sessions sharing one checkout, each + session's blanket add committing the other's uncommitted files. +--- + +# Repo Worktree + +## Why This Exists + +Two agent sessions once ran concurrently in the same primary checkout, on the same feature +branch, neither knowing the other was in the tree. One session's commits swept in the other +session's uncommitted files, so two commits landed carrying work their subjects never mention, +committed by a task that never saw it. No rule fired at the moment it was violated, which is the +first file edit: the commit-time and review-time skills all run after a sweep has already +happened. This skill is that missing task-start surface. `GOVERNANCE.md` "Repository Boundaries +and Write Safety" keeps the isolation law and wins on any disagreement, and the mechanics below +are this skill's own content. + +## The Mandate + +- **Every task isolates into its own worktree before its first file edit.** All new work begins + by creating a unique worktree (or clone) on its own feature branch. The primary checkout is + the maintainer's own surface, so a session launched there isolates before writing rather than + after noticing contention. +- **A continuation re-isolates.** A session resuming prior work finds its branch already checked + out somewhere and naturally resumes there, and that instinct is the hazard: a branch sitting + checked out in a shared tree is exactly how two sessions end up in one checkout. Create a + fresh worktree for the continuation and check the branch out there. +- **The moment is the first file edit, not the commit.** By commit time another task's + uncommitted work can already be swept into the staging area, so isolating late protects + nothing. Reading anywhere is fine, and the worktree exists before the first write. +- **Someone else's tree stays theirs.** A branch that changes when nothing you did changed it, + or an edit of yours reverted with no conflict, means another task is live in that tree, and + the response is to stop rather than to re-apply the edit, per `GOVERNANCE.md` "Repository + Boundaries and Write Safety". + +## The Base Branch + +Base the worktree on the branch work starts on for the repository's model, not on whichever +branch a tool defaulted to. GitHub's own "default branch" setting reads `main`, but on both +fleet workflow models work starts on `develop`, so a worktree defaulted to "the default branch" +lands on `main` and silently misses everything merged to `develop` but not yet promoted. Branch +from `develop` unless the task is explicitly about `main`-only content, per `GOVERNANCE.md` +"Branching Model". Fetch immediately before creating and base on the remote ref, because a clone +is whatever it last fetched rather than the branch it names. + +## Creating a Worktree + +The fleet layout convention keeps every base clone and every in-flight task visible in one +place, with no owner segment since every repo here is under one owner: + +```text +~/repos/ base clone, on its default/working branch +~/repos/worktrees/- one worktree per in-flight task, own branch +``` + +```sh +git -C ~/repos/ fetch origin develop +git -C ~/repos/ worktree add ~/repos/worktrees/- -b origin/develop +``` + +A machine not yet migrated to this layout still isolates exactly the same way, since the mandate +is the isolation rather than the path: create the worktree beside whatever layout the machine +has, and note that the base clone may live elsewhere than `~/repos/`. + +Claude Code's own `EnterWorktree` tool acts only on an explicit instruction from the user or the +project instructions, which is why the carried rules state this mandate in so many words. Given +a `name`, it creates the worktree under `.claude/worktrees/` inside the repo and bases it on the +GitHub default branch, which is the wrong path and the wrong base here. Create the worktree with +`git worktree add` as above, then attach with `EnterWorktree` `path:`, not `name:`. + +## Listing and Cleanup + +- `git worktree list`, run in any checkout of a repo, names that repo's base clone and every + worktree with its branch. On the convention layout, one `ls ~/repos/worktrees/` reads what is + in flight across the whole fleet. +- After the task's pull request merges, remove the worktree and its branch from the base clone: + `git worktree remove ~/repos/worktrees/-`, then `git branch -d `. +- A worktree that refuses removal is dirty, and force is not the fix: look at what is + uncommitted in it first, since discarding uncommitted work runs only on explicit instruction, + per the `git-commit-conventions` skill. diff --git a/AGENTS.md b/AGENTS.md index b1f25247..fff1fd30 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -77,7 +77,7 @@ Every rule below is a level-two section of [`GOVERNANCE.md`](./GOVERNANCE.md). R | --- | --- | | Why the rules are shaped this way | `Foundational Principles` | | Recording a durable lesson or updating governance | `Durable Knowledge and Self-Improvement`, surfaced at its decision moment by the `agent-conduct` Skill, and the section keeps the full rules | -| Any push, API mutation, comment, label, or merge, or which checkout the work happens in | `Repository Boundaries and Write Safety` | +| Any push, API mutation, comment, label, or merge, or which checkout the work happens in | `Repository Boundaries and Write Safety`, its task-isolation rule surfaced at the task-start moment by the `repo-worktree` Skill, and the section keeps the full rules | | Quoting data into a comment, commit, test, or doc | `Representative Data in Agent-Authored Text` | | Committing, signing, rebasing, force-pushing | `Git and Commit Rules`, packaged as the `git-commit-conventions` Skill | | Branch choice, promotion, keeping branches in sync | `Branching Model`, packaged as the `operational-vs-release-workflow` Skill | @@ -97,6 +97,6 @@ Every rule below is a level-two section of [`GOVERNANCE.md`](./GOVERNANCE.md). R | The About panel, description, or repo toggles | `Repository Details` | | Where a file belongs in the tree | `Repository Layout` | -A row above with no Skill annotation is doc-only by decision, not by omission. A Skill surfaces rules at a trigger moment, and each unannotated section either binds always or carries no moment narrower than reading it: `Foundational Principles` is rationale read once rather than a procedure, `Repository Boundaries and Write Safety` and `Representative Data in Agent-Authored Text` are always-on law that must bind even when no Skill fires (the `gh-write-guard` hook and the host-wide instruction blocks the agent-safety installer maintains are their enforcement layer), and `Hub-Hosted Tooling`, `Supported Development Platforms`, `Devcontainer`, `Editor and Tasks`, `Repository Details`, and `Repository Layout` are short reference sections a task reads at the moment it touches their subject, each already routed to by the procedures and Skills that need it. +A row above with no Skill annotation is doc-only by decision, not by omission. A Skill surfaces rules at a trigger moment, and each unannotated section either binds always or carries no moment narrower than reading it: `Foundational Principles` is rationale read once rather than a procedure, `Repository Boundaries and Write Safety` and `Representative Data in Agent-Authored Text` are always-on law that must bind even when no Skill fires (the `gh-write-guard` hook and the host-wide instruction blocks the agent-safety installer maintains are their enforcement layer, and the one moment in the boundaries section narrow enough to surface, isolating into a worktree at task start, gets the `repo-worktree` Skill on top of that law rather than instead of it), and `Hub-Hosted Tooling`, `Supported Development Platforms`, `Devcontainer`, `Editor and Tasks`, `Repository Details`, and `Repository Layout` are short reference sections a task reads at the moment it touches their subject, each already routed to by the procedures and Skills that need it. -Some of the rules above are also packaged as Claude Code / opencode / Codex Skills, hand-authored at `.agents/skills/` in the hub (not a repo-relative link here, since that path is hub-local and not carried into every fleet repo), so they surface automatically instead of needing to be re-read every session. `scripts/` is hub-hosted and reached rather than carried, per "Hub-Hosted Tooling", so run the installer from a hub checkout: `python3 scripts/skills_install.py` (or the `.sh`/`.ps1` wrapper) once per machine, from `github.com/ptr727/ProjectTemplate`, installs them for every repo touched from that machine. `python3 scripts/skills_install.py --report`, also from a hub checkout, says whether this machine is current. A rule that keeps needing to be restated is a sign the install is missing or stale, not that the rule does not exist. Keeping a repo's own carried `.github/copilot-instructions.md` in sync with the hub, without losing that repo's own "Disproved Claims" ledger entries in the process, is `copilot-instructions-keeper`, a skill about maintaining that file rather than a rule extracted from it, since the file itself is read directly by the Copilot bot and stays fully intact everywhere it is carried. Checking, from inside this repo's own session with no operator watching, whether this repo and this machine are actually current against the hub is `fleet-conformance-check`, new content rather than a rule extracted from a section, the counterpart to `resync-a-repo` that needs no standing hub checkout or named target beyond the repo the session is already in, even though its own check fetches a hub checkout to reach `scripts/skills_install.py`. Opening a pull request against a repository outside this fleet, one the maintainer does not control, follows a different workflow entirely, new content rather than a rule extracted from a section, packaged as `upstream-contribution-workflow` and independent of the target repo's own type or workflow model. Creating, changing, or retiring one of these skills is itself packaged as `skill-lifecycle`, hub-context only, since `.agents/skills/` exists only in the hub and the generated plugin tree is never hand-edited. +Some of the rules above are also packaged as Claude Code / opencode / Codex Skills, hand-authored at `.agents/skills/` in the hub (not a repo-relative link here, since that path is hub-local and not carried into every fleet repo), so they surface automatically instead of needing to be re-read every session. `scripts/` is hub-hosted and reached rather than carried, per "Hub-Hosted Tooling", so run the installer from a hub checkout: `python3 scripts/skills_install.py` (or the `.sh`/`.ps1` wrapper) once per machine, from `github.com/ptr727/ProjectTemplate`, installs them for every repo touched from that machine. `python3 scripts/skills_install.py --report`, also from a hub checkout, says whether this machine is current. A rule that keeps needing to be restated is a sign the install is missing or stale, not that the rule does not exist. Keeping a repo's own carried `.github/copilot-instructions.md` in sync with the hub, without losing that repo's own "Disproved Claims" ledger entries in the process, is `copilot-instructions-keeper`, a skill about maintaining that file rather than a rule extracted from it, since the file itself is read directly by the Copilot bot and stays fully intact everywhere it is carried. Checking, from inside this repo's own session with no operator watching, whether this repo and this machine are actually current against the hub is `fleet-conformance-check`, new content rather than a rule extracted from a section, the counterpart to `resync-a-repo` that needs no standing hub checkout or named target beyond the repo the session is already in, even though its own check fetches a hub checkout to reach `scripts/skills_install.py`. Opening a pull request against a repository outside this fleet, one the maintainer does not control, follows a different workflow entirely, new content rather than a rule extracted from a section, packaged as `upstream-contribution-workflow` and independent of the target repo's own type or workflow model. Isolating a task into its own worktree before its first file edit, with the base-branch choice, the layout convention, and the cleanup mechanics, is `repo-worktree`, the task-start surface of the `Repository Boundaries and Write Safety` law, which keeps the rule. Creating, changing, or retiring one of these skills is itself packaged as `skill-lifecycle`, hub-context only, since `.agents/skills/` exists only in the hub and the generated plugin tree is never hand-edited. diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 42701e8c..f3406843 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -29,6 +29,7 @@ A state-changing GitHub call is the highest-blast-radius thing an agent does her - **A write is never a probe, and a write's output is never suppressed.** Never fire a state-changing call to see whether it works: decide it should happen, make it happen, and read the result. Never append output-discarding redirection or a force-success tail to a mutation (for example `>/dev/null`, `2>/dev/null`, `&>/dev/null`, `|| true`, `|| :`, `|| echo`), because the write's output is exactly what must be read. A write that appears to fail is **verified, not assumed harmless**, because the operation may have succeeded on the server while the client reported an error, so confirm the actual state before retrying or moving on. The ban targets hiding a *failure*. An ad-hoc call's response is the only signal you get, so `>/dev/null 2>&1`, `|| true`, and `|| echo`, which swallow the error stream or force success, are never acceptable on one. A committed script under `set -e` is a narrow exception: it may send a write's *stdout* to `/dev/null` to drop the success-response noise, because stderr stays visible and a failed write still aborts loudly (the hub's own `repo-config/configure.sh` does exactly this, and a repository reaches it there rather than carrying a copy). The exception is stdout-only suppression inside a reviewed, fail-loud script, never `2>&1` or a force-success tail, and never an ad-hoc command. - **A refused write is reported, never re-shaped, and the maintainer's say-so does not lift a refusal by the harness.** These are two different permissions and only one of them is the maintainer's to give. When the agent harness refuses a write, the maintainer authorizing it in conversation does not change the outcome, and the identical call is refused again, so a second attempt is not worth making and reading the second refusal as a flake is how an agent starts hunting for another shape of the same request. **That hunt is the failure this rule exists to stop.** Re-expressing a refused `gh` command as a raw `gh api -X POST` reaches the same endpoint with the same identity and the same blast radius, having defeated the one control that stopped it, and it is the more dangerous version because the agent believes it has permission. So a refused write is never re-attempted through a different API surface, a different tool, or a rephrasing, and it is never routed around by the agent writing itself a permission rule, which is self-authorization whatever the maintainer said. Two routes remain, both of them the maintainer's: they add the permission rule themselves, or they run the command themselves. Raise it as a blocked decision naming those two (see "Communicating with the User"), and where the work needs the result rather than the call, say what the agent will verify once the maintainer has run it. **A refusal is also a fact about the contract, not just about the session**: where a required verification can only be performed by a write the agent is refused, the document requiring it says so and names who runs it, since a check that is mandatory and unperformable is quietly dropped and then reported as done. - **Each task runs in its own checkout, in its own directory, on its own feature branch.** The unit is the task rather than the agent, since one agent moving between two repositories meets the same hazard as two agents sharing one tree, and a rule written per agent permits exactly the case that goes wrong. The commands that cross the boundary are the ordinary ones rather than the reckless ones, and each is correct in isolation: a blanket `git add -A` sweeps another task's uncommitted work into the commit, a `git reset --hard` deletes it, and a branch switch carries it into an unrelated change. The mechanical habit that holds the rule up is that a mutating command takes an absolute path, or a `cd` to one in the same invocation, rather than the working directory it inherited, because a read in the wrong directory is a wasted call and a write there is damage. +- **A task isolates into its own worktree before its first file edit, and a continuation re-isolates.** All new work begins by creating a unique git worktree (or clone) on its own feature branch, based on the branch work starts on for the repository's model per "Branching Model", which is `develop` unless the task is explicitly about `main`-only content. The primary checkout is the maintainer's own surface, so a session launched there isolates before writing rather than after noticing contention, and a session resuming a prior task creates a fresh worktree rather than resuming wherever its branch happens to be checked out, since a branch sitting checked out in a shared tree is exactly how two sessions end up in one checkout. The moment this rule binds is the first file edit, because the commit-time and review-time checks all run after another task's uncommitted work can already be swept. The worktree mechanics, the layout convention, and the cleanup are packaged as the `repo-worktree` Skill at `.agents/skills/repo-worktree/SKILL.md` in the hub, not a repo-relative link since that path is hub-local and not carried into every fleet repo, and this section keeps the rule. - **A checkout another task is live in is left rather than shared, and a footprint already left there is undone deliberately.** Two signals say someone else is in the tree, a branch that changes when nothing you did changed it, and an edit of yours reverted with no conflict, and the response to either is to stop rather than to re-apply the edit, which is the instinct and the wrong one. Leaving and cloning your own costs about a minute against an incident that costs the better part of an hour, so it is the cheap move rather than the cautious one. Once you have written there, leaving it alone arrives too late, so save your work aside, restore only the files you touched, verify the tree is clean, delete your branch from that clone, and then say plainly what was touched, since a regenerated report left behind reads as the other task's own and is committed by whoever runs the next blanket add. ## Representative Data in Agent-Authored Text @@ -41,10 +42,11 @@ Agent-authored text illustrates with data the agent constructed, never with data ## Git and Commit Rules -The fleet's mechanical git rules: default to staging rather than committing, commit means commit -and push, every commit is signed and carries the committer's own verified GitHub `noreply` -identity, never force push, a history rewrite re-identifies only the commits it touches that -aren't yours, and destructive git commands run only on explicit instruction. +The fleet's mechanical git rules: default to staging rather than committing and stage by +explicit path only, never a blanket add, commit means commit and push, every commit is signed +and carries the committer's own verified GitHub `noreply` identity, never force push, a history +rewrite re-identifies only the commits it touches that aren't yours, and destructive git +commands run only on explicit instruction. This is packaged as the `git-commit-conventions` Skill at `.agents/skills/git-commit-conventions/SKILL.md` in the hub, not a repo-relative link since that diff --git a/host-setup/agent-safety/README.md b/host-setup/agent-safety/README.md index 40537407..002aa729 100644 --- a/host-setup/agent-safety/README.md +++ b/host-setup/agent-safety/README.md @@ -8,7 +8,7 @@ Into `~/.claude/` (or `%USERPROFILE%\.claude\` on Windows): - **`hooks/gh-write-guard.py`**: a PreToolUse hook that denies two classes of dangerous action. First, the GitHub **write** footguns behind the cross-repo comment incident: a state-changing `gh` call whose output is discarded, a GraphQL mutation passing a **literal** node id instead of a `$variable`, and a `gh` write whose explicit target is under an owner other than the checkout origin's. Sibling repositories under the same owner are allowed, since the harm this guards is reaching a stranger's repository rather than working across one maintainer's own fleet, and a different owner is allowed only when the maintainer names it in `GH_WRITE_GUARD_ALLOW` (an `owner/repo` list, where `owner/*` grants a whole owner). That variable is read from the environment the session was launched with, which is the one channel an agent cannot set for itself: a hook runs as its own process, so an inline `VAR=x cmd` prefix or an `export` inside a Bash call never reaches it. Second, a **git operation that bypasses a repository protection**. The branch-rule cases (a direct push to a branch that requires a pull request, a force-push where history is protected, a branch delete where deletion is blocked) are judged against the branch's **live** rules, so a code-style `develop` is denied while a config-style `develop` is allowed with no per-repo configuration. A push to a protected-default branch fails closed when its rules cannot be determined, either because the API is unreachable or because the checkout's origin cannot be resolved to query them. The explicit-bypass flags are denied **unconditionally**, since the flag is itself the bypass and needs no branch query: `gh pr merge --admin` overrides the server-side merge gate, and `git commit`/`git push --no-verify` skips the local git hooks. Reads and everything else pass through. It fires even in autonomous / bypass-permissions sessions, which is how the incident happened. - **Two marker-delimited blocks of host-wide content in `CLAUDE.md`**, loaded into every session on the machine (including ad-hoc work outside any project), installed and updated independently so one can change without rewriting the other. - - The `agent-safety` block carries restrictions alone, so nothing in it can widen a permission. `Repository Boundaries and Write Safety` mirrors the committed `GOVERNANCE.md` section of the same name, carrying its write rules and its rule that a task works in its own checkout as behavioral guidance. `Data in Agent-Authored Text` mirrors `GOVERNANCE.md` "Representative Data in Agent-Authored Text", so an agent illustrates with constructed data rather than data observed on the machine. `Authorization Scope and Memory Hygiene` bounds how a granted permission is recorded and read. Each mirrors a rule that otherwise reaches fleet repos only. + - The `agent-safety` block carries restrictions alone, so nothing in it can widen a permission. `Repository Boundaries and Write Safety` mirrors the committed `GOVERNANCE.md` section of the same name, carrying as behavioral guidance its write rules and its rules that a task works in its own checkout and isolates into its own worktree before its first file edit. `Data in Agent-Authored Text` mirrors `GOVERNANCE.md` "Representative Data in Agent-Authored Text", so an agent illustrates with constructed data rather than data observed on the machine. `Authorization Scope and Memory Hygiene` bounds how a granted permission is recorded and read. Each mirrors a rule that otherwise reaches fleet repos only. - The `fleet-bootstrap` block carries `Fleet Governance Entry Point`, which names the template repository and routes by the state a repository is actually in. It is separate precisely because it enables rather than restricts, and the safety block's own text says nothing in it widens a permission, so merging the two would contradict that. It is host-wide rather than per repository because the repositories that most need it are the ones carrying no instruction set to point the way, and it mirrors the byte-locked `AGENTS.md` "Fleet Bootstrap" section that a conformant repository carries. - **The permission rules this kit owns, merged into `settings.json`** beside the hook registration. Each is declared as a prefix and a rule, and a re-run drops every rule the prefix owns before adding the current one, so a rule whose spelling changes updates in place rather than accumulating beside the version it replaced. Ownership requires a rule-syntax delimiter after the prefix, since the prefix ends at the script name and a bare prefix test would also claim a longer path such as `pr_review.py-custom`, so a rule written by hand for a different script is never touched. These widen rather than restrict, which is why they are their own component for the same reason the `fleet-bootstrap` block is separate from the `agent-safety` one. Today the list holds one rule, for `scripts/pr_review.py`, the review loop's reply and resolve. Driving that loop by hand needs a raw GraphQL mutation carrying a node id, which is the shape that reached a stranger's repository, where the script queries the id itself and takes no argument an id fits in. What the rule decides is which command runs without a prompt, and it matches the command text rather than the directory the command runs in, so it reaches a `scripts/pr_review.py` in any checkout that carries one. An absolute path would not narrow that, since the hub is reached as a checkout of the caller's own and its location differs per task, so pinning one path would name a checkout the next task does not use. What bounds it is the rule that an agent reaches the hub as a checkout of its own, fetched immediately before it is read, rather than a copy it happens to find on disk, which the `fleet-bootstrap` block beside this carries and [`GOVERNANCE.md`][governance] "Hub-Hosted Tooling" states in full. diff --git a/host-setup/agent-safety/claude-md-safety.md b/host-setup/agent-safety/claude-md-safety.md index 79ce9928..4bbd6b45 100644 --- a/host-setup/agent-safety/claude-md-safety.md +++ b/host-setup/agent-safety/claude-md-safety.md @@ -7,6 +7,7 @@ A `gh` / GitHub API write runs under the logged-in identity, so a mis-targeted w - **Never fabricate, guess, or reuse an identifier passed to a write.** Every id a write consumes (a node id, a numeric id, a thread or comment id) is captured from a live query in the same session into a variable and passed from there. Ids resolve globally, so a wrong-but-valid id does not fail. It writes to the wrong target, in another repository. If a query returns no id, stop rather than invent one. - **A write is never a probe, and a write's output is never suppressed.** Never fire a state-changing call to see whether it works, and never append an output-discarding or force-success tail (for example `>/dev/null`, `2>/dev/null`, `&>/dev/null`, `|| true`, `|| :`, `|| echo`) to a mutation. A write that appears to fail is verified, not assumed harmless, because it may have succeeded on the server. - **Each task runs in its own checkout, in its own directory, on its own feature branch.** The unit is the task rather than the agent, since one agent moving between two repositories meets the same hazard as two agents sharing one tree. A blanket `git add -A`, a `git reset --hard`, and a branch switch each cross the boundary while being correct in isolation, so a mutating command takes an absolute path, or a `cd` to one in the same invocation, rather than the working directory it inherited. A branch that changes when nothing you did changed it, or an edit of yours reverted with no conflict, means another task is live in that tree, and the response is to stop rather than to re-apply the edit. +- **A task isolates into its own git worktree before its first file edit, and a continuation re-isolates.** All new work, a continuation of a prior session's task included, begins by creating a unique worktree (or clone) on its own feature branch, based on the branch work starts on for that repository rather than whichever branch a tool defaulted to. The primary checkout is the maintainer's own surface, so a session launched there isolates before writing rather than after noticing contention, and stage by explicit path, never `git add -A` or `git add .`, since a blanket add is how a shared tree turns into a mis-scoped commit. A committed repo's `GOVERNANCE.md` "Repository Boundaries and Write Safety" and the hub's `repo-worktree` skill carry the full rule and the mechanics. ## Data in Agent-Authored Text (Any Project, Every Session) From 2d00c344d52e6ac088c659cd59ba0eb78c797744 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Fri, 14 Aug 2026 16:42:54 -0700 Subject: [PATCH 06/12] Unwrap the Edited Git-Rules Summary and Fix the Staging Bullet's Grammar Copilot round 1 on PR #717: the Git and Commit Rules summary paragraph edited by this PR joins the one-logical-paragraph-per-line rule it was already subject to, and the host-wide staging sentence gets its own subject instead of dangling off the isolation clause. Co-Authored-By: Claude Fable 5 --- GOVERNANCE.md | 6 +----- host-setup/agent-safety/claude-md-safety.md | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index f3406843..4f1b5c41 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -42,11 +42,7 @@ Agent-authored text illustrates with data the agent constructed, never with data ## Git and Commit Rules -The fleet's mechanical git rules: default to staging rather than committing and stage by -explicit path only, never a blanket add, commit means commit and push, every commit is signed -and carries the committer's own verified GitHub `noreply` identity, never force push, a history -rewrite re-identifies only the commits it touches that aren't yours, and destructive git -commands run only on explicit instruction. +The fleet's mechanical git rules: default to staging rather than committing and stage by explicit path only, never a blanket add, commit means commit and push, every commit is signed and carries the committer's own verified GitHub `noreply` identity, never force push, a history rewrite re-identifies only the commits it touches that aren't yours, and destructive git commands run only on explicit instruction. This is packaged as the `git-commit-conventions` Skill at `.agents/skills/git-commit-conventions/SKILL.md` in the hub, not a repo-relative link since that diff --git a/host-setup/agent-safety/claude-md-safety.md b/host-setup/agent-safety/claude-md-safety.md index 4bbd6b45..3a465767 100644 --- a/host-setup/agent-safety/claude-md-safety.md +++ b/host-setup/agent-safety/claude-md-safety.md @@ -7,7 +7,7 @@ A `gh` / GitHub API write runs under the logged-in identity, so a mis-targeted w - **Never fabricate, guess, or reuse an identifier passed to a write.** Every id a write consumes (a node id, a numeric id, a thread or comment id) is captured from a live query in the same session into a variable and passed from there. Ids resolve globally, so a wrong-but-valid id does not fail. It writes to the wrong target, in another repository. If a query returns no id, stop rather than invent one. - **A write is never a probe, and a write's output is never suppressed.** Never fire a state-changing call to see whether it works, and never append an output-discarding or force-success tail (for example `>/dev/null`, `2>/dev/null`, `&>/dev/null`, `|| true`, `|| :`, `|| echo`) to a mutation. A write that appears to fail is verified, not assumed harmless, because it may have succeeded on the server. - **Each task runs in its own checkout, in its own directory, on its own feature branch.** The unit is the task rather than the agent, since one agent moving between two repositories meets the same hazard as two agents sharing one tree. A blanket `git add -A`, a `git reset --hard`, and a branch switch each cross the boundary while being correct in isolation, so a mutating command takes an absolute path, or a `cd` to one in the same invocation, rather than the working directory it inherited. A branch that changes when nothing you did changed it, or an edit of yours reverted with no conflict, means another task is live in that tree, and the response is to stop rather than to re-apply the edit. -- **A task isolates into its own git worktree before its first file edit, and a continuation re-isolates.** All new work, a continuation of a prior session's task included, begins by creating a unique worktree (or clone) on its own feature branch, based on the branch work starts on for that repository rather than whichever branch a tool defaulted to. The primary checkout is the maintainer's own surface, so a session launched there isolates before writing rather than after noticing contention, and stage by explicit path, never `git add -A` or `git add .`, since a blanket add is how a shared tree turns into a mis-scoped commit. A committed repo's `GOVERNANCE.md` "Repository Boundaries and Write Safety" and the hub's `repo-worktree` skill carry the full rule and the mechanics. +- **A task isolates into its own git worktree before its first file edit, and a continuation re-isolates.** All new work, a continuation of a prior session's task included, begins by creating a unique worktree (or clone) on its own feature branch, based on the branch work starts on for that repository rather than whichever branch a tool defaulted to. The primary checkout is the maintainer's own surface, so a session launched there isolates before writing rather than after noticing contention. Staging is by explicit path, never `git add -A` or `git add .`, since a blanket add is how a shared tree turns into a mis-scoped commit. A committed repo's `GOVERNANCE.md` "Repository Boundaries and Write Safety" and the hub's `repo-worktree` skill carry the full rule and the mechanics. ## Data in Agent-Authored Text (Any Project, Every Session) From 8f1fac6cf8ec87b2ac174cdd7553a523c09cc5d7 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Fri, 14 Aug 2026 16:46:48 -0700 Subject: [PATCH 07/12] State the develop Base for Both Workflow Models, Not Only release Copilot round 2 on PR #717: operational repos also start work on develop (direct-to-develop commits), so the Branching Model sentence and the repo-worktree skill description drop the release-model qualifier that contradicted the skill body. Co-Authored-By: Claude Fable 5 --- .agents/skills/repo-worktree/SKILL.md | 4 ++-- .claude-plugin/fleet-skills/.source-digest | 2 +- .claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md | 4 ++-- GOVERNANCE.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.agents/skills/repo-worktree/SKILL.md b/.agents/skills/repo-worktree/SKILL.md index d2154df4..4114d00a 100644 --- a/.agents/skills/repo-worktree/SKILL.md +++ b/.agents/skills/repo-worktree/SKILL.md @@ -3,8 +3,8 @@ name: repo-worktree description: >- Mandates and mechanizes task isolation in ptr727/ProjectTemplate fleet repos: every task, including a continuation of a prior session's task, creates its own git worktree on its own - feature branch before its first file edit, based on the branch work starts on (develop for a - release-model repo, never whichever branch a tool defaulted to). Also wraps the mechanics: + feature branch before its first file edit, based on the branch work starts on (develop on both + fleet workflow models, never whichever branch a tool defaulted to). Also wraps the mechanics: creating a worktree with git worktree add, the fleet layout convention, listing what is in flight, and removing a worktree and its branch after merge. Use this whenever about to create or edit files in a fleet repo, whenever starting or resuming a task, whenever the task's diff --git a/.claude-plugin/fleet-skills/.source-digest b/.claude-plugin/fleet-skills/.source-digest index e21aa20a..e6a12273 100644 --- a/.claude-plugin/fleet-skills/.source-digest +++ b/.claude-plugin/fleet-skills/.source-digest @@ -1 +1 @@ -51333ca94cd8a1d6 +923fd05e067b47cf diff --git a/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md b/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md index d2154df4..4114d00a 100644 --- a/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md @@ -3,8 +3,8 @@ name: repo-worktree description: >- Mandates and mechanizes task isolation in ptr727/ProjectTemplate fleet repos: every task, including a continuation of a prior session's task, creates its own git worktree on its own - feature branch before its first file edit, based on the branch work starts on (develop for a - release-model repo, never whichever branch a tool defaulted to). Also wraps the mechanics: + feature branch before its first file edit, based on the branch work starts on (develop on both + fleet workflow models, never whichever branch a tool defaulted to). Also wraps the mechanics: creating a worktree with git worktree add, the fleet layout convention, listing what is in flight, and removing a worktree and its branch after merge. Use this whenever about to create or edit files in a fleet repo, whenever starting or resuming a task, whenever the task's diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 4f1b5c41..b9791999 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -55,7 +55,7 @@ Two workflow models, set per repo by the registry `workflowModel` field. Most re `release`: squash-only feature branches into `develop`, merge-commit-only `develop -> main` promotions, forward-only with no back-merges, and two promotion traps worth knowing before the first one (never delete `develop`, resolve an EOL-only conflict by taking `develop`'s side). -**GitHub's own "default branch" repository setting reads `main`, but `develop` is where work starts and where in-flight content lives**, so a worktree or clone that defaults to "the default branch" lands on `main` and can silently miss content already merged to `develop` but not yet promoted. Branch from `develop` for a `release`-model repo unless the task is explicitly about `main`-only content. +**GitHub's own "default branch" repository setting reads `main`, but `develop` is where work starts and where in-flight content lives**, so a worktree or clone that defaults to "the default branch" lands on `main` and can silently miss content already merged to `develop` but not yet promoted. Branch from `develop`, on either workflow model, unless the task is explicitly about `main`-only content. **Operational** repos differ substantially (direct-to-`develop`, advisory CI, dispatch-only release), covered as a delta rather than a separate model. From e9a9711df28be1d744941e3f7ddcba8503e316c6 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Fri, 14 Aug 2026 16:50:55 -0700 Subject: [PATCH 08/12] Show How a Continuation Attaches Its Existing Branch to a New Worktree Copilot round 3 on PR #717: the skill mandated that continuations re-isolate but only showed the new-branch sequence, so a continuation could accidentally fork a fresh branch. The added sequence attaches the existing task branch, and the prose names git's own refusal to attach a checked-out branch as the mandate working. Co-Authored-By: Claude Fable 5 --- .agents/skills/repo-worktree/SKILL.md | 13 +++++++++++++ .claude-plugin/fleet-skills/.source-digest | 2 +- .../fleet-skills/skills/repo-worktree/SKILL.md | 13 +++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/.agents/skills/repo-worktree/SKILL.md b/.agents/skills/repo-worktree/SKILL.md index 4114d00a..7b18f26a 100644 --- a/.agents/skills/repo-worktree/SKILL.md +++ b/.agents/skills/repo-worktree/SKILL.md @@ -71,6 +71,19 @@ git -C ~/repos/ fetch origin develop git -C ~/repos/ worktree add ~/repos/worktrees/- -b origin/develop ``` +A continuation attaches the task's existing branch rather than forking a fresh one: + +```sh +git -C ~/repos/ fetch origin +git -C ~/repos/ worktree add ~/repos/worktrees/- +``` + +Git refuses to attach a branch that is already checked out somewhere else, and that refusal is +the mandate working, since the branch sitting checked out in a shared tree is the hazard the +continuation rule exists for. Return that checkout to its own working branch first when its tree +is clean, and stop when it is not, because a dirty tree there may be another task's uncommitted +work. + A machine not yet migrated to this layout still isolates exactly the same way, since the mandate is the isolation rather than the path: create the worktree beside whatever layout the machine has, and note that the base clone may live elsewhere than `~/repos/`. diff --git a/.claude-plugin/fleet-skills/.source-digest b/.claude-plugin/fleet-skills/.source-digest index e6a12273..8adfc060 100644 --- a/.claude-plugin/fleet-skills/.source-digest +++ b/.claude-plugin/fleet-skills/.source-digest @@ -1 +1 @@ -923fd05e067b47cf +70f5e956df2078cb diff --git a/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md b/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md index 4114d00a..7b18f26a 100644 --- a/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md @@ -71,6 +71,19 @@ git -C ~/repos/ fetch origin develop git -C ~/repos/ worktree add ~/repos/worktrees/- -b origin/develop ``` +A continuation attaches the task's existing branch rather than forking a fresh one: + +```sh +git -C ~/repos/ fetch origin +git -C ~/repos/ worktree add ~/repos/worktrees/- +``` + +Git refuses to attach a branch that is already checked out somewhere else, and that refusal is +the mandate working, since the branch sitting checked out in a shared tree is the hazard the +continuation rule exists for. Return that checkout to its own working branch first when its tree +is clean, and stop when it is not, because a dirty tree there may be another task's uncommitted +work. + A machine not yet migrated to this layout still isolates exactly the same way, since the mandate is the isolation rather than the path: create the worktree beside whatever layout the machine has, and note that the base clone may live elsewhere than `~/repos/`. From 054b42adff739b3636741e03060eec0792f81db6 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Fri, 14 Aug 2026 16:57:45 -0700 Subject: [PATCH 09/12] Document the Checkout Guessing That Makes the Continuation Snippet Work Cold Copilot round 4 on PR #717 claimed the continuation snippet fails on a clone holding only the remote-tracking ref. It does not: git's checkout DWIM creates the local tracking branch, verified against git 2.47 with a constructed two-repo case. The sentence lands anyway, so the next reader does not have to re-derive why the command works on a fresh clone. Co-Authored-By: Claude Fable 5 --- .agents/skills/repo-worktree/SKILL.md | 5 ++++- .claude-plugin/fleet-skills/.source-digest | 2 +- .claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md | 5 ++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.agents/skills/repo-worktree/SKILL.md b/.agents/skills/repo-worktree/SKILL.md index 7b18f26a..6616f806 100644 --- a/.agents/skills/repo-worktree/SKILL.md +++ b/.agents/skills/repo-worktree/SKILL.md @@ -78,7 +78,10 @@ git -C ~/repos/ fetch origin git -C ~/repos/ worktree add ~/repos/worktrees/- ``` -Git refuses to attach a branch that is already checked out somewhere else, and that refusal is +When the base clone holds only the remote-tracking ref, the same command creates the local +branch tracking `origin/` through git's ordinary checkout guessing, so a fresh +clone needs no separate branch setup. Git refuses to attach a branch that is already checked +out somewhere else, and that refusal is the mandate working, since the branch sitting checked out in a shared tree is the hazard the continuation rule exists for. Return that checkout to its own working branch first when its tree is clean, and stop when it is not, because a dirty tree there may be another task's uncommitted diff --git a/.claude-plugin/fleet-skills/.source-digest b/.claude-plugin/fleet-skills/.source-digest index 8adfc060..66b34191 100644 --- a/.claude-plugin/fleet-skills/.source-digest +++ b/.claude-plugin/fleet-skills/.source-digest @@ -1 +1 @@ -70f5e956df2078cb +822d18ac3e2c0df8 diff --git a/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md b/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md index 7b18f26a..6616f806 100644 --- a/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md @@ -78,7 +78,10 @@ git -C ~/repos/ fetch origin git -C ~/repos/ worktree add ~/repos/worktrees/- ``` -Git refuses to attach a branch that is already checked out somewhere else, and that refusal is +When the base clone holds only the remote-tracking ref, the same command creates the local +branch tracking `origin/` through git's ordinary checkout guessing, so a fresh +clone needs no separate branch setup. Git refuses to attach a branch that is already checked +out somewhere else, and that refusal is the mandate working, since the branch sitting checked out in a shared tree is the hazard the continuation rule exists for. Return that checkout to its own working branch first when its tree is clean, and stop when it is not, because a dirty tree there may be another task's uncommitted From 21aa9ac7a34d99bce8013f2548a0021ea0fc1558 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Fri, 14 Aug 2026 17:01:31 -0700 Subject: [PATCH 10/12] Rewrap the Continuation Paragraph Evenly Copilot round 5 on PR #717: the previous edit left one short line splitting a sentence mid-clause, which read as a formatting glitch inside the skill's wrapped style. Co-Authored-By: Claude Fable 5 --- .agents/skills/repo-worktree/SKILL.md | 9 ++++----- .claude-plugin/fleet-skills/.source-digest | 2 +- .../fleet-skills/skills/repo-worktree/SKILL.md | 9 ++++----- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/.agents/skills/repo-worktree/SKILL.md b/.agents/skills/repo-worktree/SKILL.md index 6616f806..820bac6e 100644 --- a/.agents/skills/repo-worktree/SKILL.md +++ b/.agents/skills/repo-worktree/SKILL.md @@ -81,11 +81,10 @@ git -C ~/repos/ worktree add ~/repos/worktrees/- ` through git's ordinary checkout guessing, so a fresh clone needs no separate branch setup. Git refuses to attach a branch that is already checked -out somewhere else, and that refusal is -the mandate working, since the branch sitting checked out in a shared tree is the hazard the -continuation rule exists for. Return that checkout to its own working branch first when its tree -is clean, and stop when it is not, because a dirty tree there may be another task's uncommitted -work. +out somewhere else, and that refusal is the mandate working, since the branch sitting checked +out in a shared tree is the hazard the continuation rule exists for. Return that checkout to +its own working branch first when its tree is clean, and stop when it is not, because a dirty +tree there may be another task's uncommitted work. A machine not yet migrated to this layout still isolates exactly the same way, since the mandate is the isolation rather than the path: create the worktree beside whatever layout the machine diff --git a/.claude-plugin/fleet-skills/.source-digest b/.claude-plugin/fleet-skills/.source-digest index 66b34191..dd6f9500 100644 --- a/.claude-plugin/fleet-skills/.source-digest +++ b/.claude-plugin/fleet-skills/.source-digest @@ -1 +1 @@ -822d18ac3e2c0df8 +52f514af2c7148f3 diff --git a/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md b/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md index 6616f806..820bac6e 100644 --- a/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md @@ -81,11 +81,10 @@ git -C ~/repos/ worktree add ~/repos/worktrees/- ` through git's ordinary checkout guessing, so a fresh clone needs no separate branch setup. Git refuses to attach a branch that is already checked -out somewhere else, and that refusal is -the mandate working, since the branch sitting checked out in a shared tree is the hazard the -continuation rule exists for. Return that checkout to its own working branch first when its tree -is clean, and stop when it is not, because a dirty tree there may be another task's uncommitted -work. +out somewhere else, and that refusal is the mandate working, since the branch sitting checked +out in a shared tree is the hazard the continuation rule exists for. Return that checkout to +its own working branch first when its tree is clean, and stop when it is not, because a dirty +tree there may be another task's uncommitted work. A machine not yet migrated to this layout still isolates exactly the same way, since the mandate is the isolation rather than the path: create the worktree beside whatever layout the machine From 6b8535039b5aaf00b282b2be280a548ccaa7f736 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Fri, 14 Aug 2026 17:07:16 -0700 Subject: [PATCH 11/12] Separate the Staging Rules in the Git Summary With a Comma Copilot round 6 on PR #717: the two staging rules read as one garbled clause without a separator, so they become two list items. Co-Authored-By: Claude Fable 5 --- GOVERNANCE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index b9791999..5473a184 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -42,7 +42,7 @@ Agent-authored text illustrates with data the agent constructed, never with data ## Git and Commit Rules -The fleet's mechanical git rules: default to staging rather than committing and stage by explicit path only, never a blanket add, commit means commit and push, every commit is signed and carries the committer's own verified GitHub `noreply` identity, never force push, a history rewrite re-identifies only the commits it touches that aren't yours, and destructive git commands run only on explicit instruction. +The fleet's mechanical git rules: default to staging rather than committing, stage by explicit path only and never with a blanket add, commit means commit and push, every commit is signed and carries the committer's own verified GitHub `noreply` identity, never force push, a history rewrite re-identifies only the commits it touches that aren't yours, and destructive git commands run only on explicit instruction. This is packaged as the `git-commit-conventions` Skill at `.agents/skills/git-commit-conventions/SKILL.md` in the hub, not a repo-relative link since that From b271f36aa9eca72f8ee6a2ba4e793e83aa331185 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Fri, 14 Aug 2026 17:12:50 -0700 Subject: [PATCH 12/12] Carry the main-Only Exception Into the Skill Description Copilot round 7 on PR #717: the description's short summary said develop with no exception, encouraging always branching from develop, while the body and GOVERNANCE.md state the main-only-content exception. Co-Authored-By: Claude Fable 5 --- .agents/skills/repo-worktree/SKILL.md | 3 ++- .claude-plugin/fleet-skills/.source-digest | 2 +- .claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.agents/skills/repo-worktree/SKILL.md b/.agents/skills/repo-worktree/SKILL.md index 820bac6e..93ed6901 100644 --- a/.agents/skills/repo-worktree/SKILL.md +++ b/.agents/skills/repo-worktree/SKILL.md @@ -4,7 +4,8 @@ description: >- Mandates and mechanizes task isolation in ptr727/ProjectTemplate fleet repos: every task, including a continuation of a prior session's task, creates its own git worktree on its own feature branch before its first file edit, based on the branch work starts on (develop on both - fleet workflow models, never whichever branch a tool defaulted to). Also wraps the mechanics: + fleet workflow models unless the task is explicitly about main-only content, never whichever + branch a tool defaulted to). Also wraps the mechanics: creating a worktree with git worktree add, the fleet layout convention, listing what is in flight, and removing a worktree and its branch after merge. Use this whenever about to create or edit files in a fleet repo, whenever starting or resuming a task, whenever the task's diff --git a/.claude-plugin/fleet-skills/.source-digest b/.claude-plugin/fleet-skills/.source-digest index dd6f9500..f0ee8330 100644 --- a/.claude-plugin/fleet-skills/.source-digest +++ b/.claude-plugin/fleet-skills/.source-digest @@ -1 +1 @@ -52f514af2c7148f3 +e4a4146ddbea9745 diff --git a/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md b/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md index 820bac6e..93ed6901 100644 --- a/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md @@ -4,7 +4,8 @@ description: >- Mandates and mechanizes task isolation in ptr727/ProjectTemplate fleet repos: every task, including a continuation of a prior session's task, creates its own git worktree on its own feature branch before its first file edit, based on the branch work starts on (develop on both - fleet workflow models, never whichever branch a tool defaulted to). Also wraps the mechanics: + fleet workflow models unless the task is explicitly about main-only content, never whichever + branch a tool defaulted to). Also wraps the mechanics: creating a worktree with git worktree add, the fleet layout convention, listing what is in flight, and removing a worktree and its branch after merge. Use this whenever about to create or edit files in a fleet repo, whenever starting or resuming a task, whenever the task's