Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion .agents/skills/repo-worktree/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,38 @@ 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:
place:

```text
~/repos/<Repo> base clone, on its default/working branch
~/repos/worktrees/<Repo>-<task-slug> one worktree per in-flight task, own branch
~/repos/upstream/<owner>-<repo> clone of a repo under another owner, not a fork
```

The top level carries no owner segment because everything in it is the fleet owner's own, an
original repo and a fork alike. A fork is named `<upstream-owner>-<upstream-repo>` at fork time,
so a fork of `acme/core` is `acme-core`, and its name identifies the upstream project and stays
unique in the flat namespace without an owner segment of its own. A repository adopted as the
owner's own work rather than kept as a fork is detached from its parent and keeps a plain name,
`widget` rather than `initech-widget`, since it no longer tracks anything upstream.

A clone of a repository under another owner is neither of those, and flattening one collides
rather than merely reading oddly: `acme/core` joined the way a fork is joined **is** the fork's
name, `acme-core`, while reduced to a bare `core` it names no project and collides with the next
`core` cloned from any other owner. Those clones live one level down under `upstream/`, named by
that same join, so `upstream/acme-core` sits beside the fork it would otherwise land on. The
segment states the relationship rather than the owner, so a reference checkout is told from a
working repo without a `git remote` call, and the names under it never compete with the flat
namespace above. The join is ambiguous in the abstract, since a hyphen in either half means
`acme-labs/core` and `acme/labs-core` produce one name, and it is kept anyway because it is the
fork convention's own join: the ambiguity is inherited from the flat namespace above rather than
introduced here, and it surfaces at clone time as a directory that already exists, where the
second clone takes a hand-picked name. A worktree off one of them keeps the flat worktrees path
under the same name, `~/repos/worktrees/<owner>-<repo>-<task-slug>`. Contributing a change from
such a clone is never a push out of it: fork the upstream first, per the
`upstream-contribution-workflow` skill, and that fork's own clone then belongs in the flat
namespace above, under the name this one already has.

```sh
git -C ~/repos/<Repo> fetch origin develop
git -C ~/repos/<Repo> worktree add ~/repos/worktrees/<Repo>-<task-slug> -b <task-branch> origin/develop
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/fleet-skills/.source-digest
Original file line number Diff line number Diff line change
@@ -1 +1 @@
e4a4146ddbea9745
b1c02e77cc574d26
27 changes: 26 additions & 1 deletion .claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,38 @@ 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:
place:

```text
~/repos/<Repo> base clone, on its default/working branch
~/repos/worktrees/<Repo>-<task-slug> one worktree per in-flight task, own branch
~/repos/upstream/<owner>-<repo> clone of a repo under another owner, not a fork
```

The top level carries no owner segment because everything in it is the fleet owner's own, an
original repo and a fork alike. A fork is named `<upstream-owner>-<upstream-repo>` at fork time,
so a fork of `acme/core` is `acme-core`, and its name identifies the upstream project and stays
unique in the flat namespace without an owner segment of its own. A repository adopted as the
owner's own work rather than kept as a fork is detached from its parent and keeps a plain name,
`widget` rather than `initech-widget`, since it no longer tracks anything upstream.

A clone of a repository under another owner is neither of those, and flattening one collides
rather than merely reading oddly: `acme/core` joined the way a fork is joined **is** the fork's
name, `acme-core`, while reduced to a bare `core` it names no project and collides with the next
`core` cloned from any other owner. Those clones live one level down under `upstream/`, named by
that same join, so `upstream/acme-core` sits beside the fork it would otherwise land on. The
segment states the relationship rather than the owner, so a reference checkout is told from a
working repo without a `git remote` call, and the names under it never compete with the flat
namespace above. The join is ambiguous in the abstract, since a hyphen in either half means
`acme-labs/core` and `acme/labs-core` produce one name, and it is kept anyway because it is the
fork convention's own join: the ambiguity is inherited from the flat namespace above rather than
introduced here, and it surfaces at clone time as a directory that already exists, where the
second clone takes a hand-picked name. A worktree off one of them keeps the flat worktrees path
under the same name, `~/repos/worktrees/<owner>-<repo>-<task-slug>`. Contributing a change from
such a clone is never a push out of it: fork the upstream first, per the
`upstream-contribution-workflow` skill, and that fork's own clone then belongs in the flat
namespace above, under the name this one already has.

```sh
git -C ~/repos/<Repo> fetch origin develop
git -C ~/repos/<Repo> worktree add ~/repos/worktrees/<Repo>-<task-slug> -b <task-branch> origin/develop
Expand Down
4 changes: 2 additions & 2 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ gh pr view <N> --json reviews --jq \

**So an unrecognized shape is a blocking outcome, and its remedy is an issue rather than a judgment call.** When any reader here meets a heading, a collapsed section, a metadata line, a coverage wording or a reviewer login it has no vetted spelling for, the review loop **does not close**, whatever else the digest says. Do not read past it, do not infer what the new wording probably means, and do not treat a body that looks clean as a clean review, because "looks clean" is precisely what a misread review looks like. Two things follow, in this order:

1. **File an issue on the hub, `ptr727/ProjectTemplate`**, which hosts `scripts/pr_review.py` and holds the vetted inventory. Name each unrecognized shape and quote the review body it came from, so the fix is made against the real wording rather than a paraphrase. The issue is filed even when the shape turns out to be cosmetic, since "cosmetic" is a conclusion drawn after reading the body and not before.
1. **File an issue on the hub**, the repository `AGENTS.md` "Fleet Bootstrap" names, which hosts `scripts/pr_review.py` and holds the vetted inventory. Name each unrecognized shape and quote the review body it came from, so the fix is made against the real wording rather than a paraphrase. The issue is filed even when the shape turns out to be cosmetic, since "cosmetic" is a conclusion drawn after reading the body and not before.
2. **The merge decision is the maintainer's**, not the agent's and not the script's. An unrecognized shape does not mean the pull request is bad, it means nothing here can vouch for the review of it. Report the state, hand it over, and stop.

`scripts/pr_review.py status <N> --repo <owner>/<repo>` reports this as `shapes=UNRECOGNIZED`, lists each shape under a marker naming the remedy, and exits `43`. `wait` carries the same code, so a wait cannot end on a clean zero over output nothing read. The vetted inventory lives in that script and is small on purpose: measured over 332 Copilot review bodies on this repository, with fenced blocks dropped and text reduced to ASCII, the whole corpus is seven headings, six `<summary>` texts and three metadata labels, and every body carries at least one of them. A body carrying none is itself the unrecognized shape, which is what catches a rewrite that changes everything at once, the refusal wording drifting among it.
Expand Down Expand Up @@ -317,7 +317,7 @@ After the final push, sweep-resolve stale older threads for removed code paths.

### Disproved Claims

**A disproof is proof about this repository, and the thread it was written in is not where the next round looks.** [GOVERNANCE.md "Every Finding Ends in an Action"](../GOVERNANCE.md#every-finding-ends-in-an-action) closes a false finding by disproving it in the thread, addressed to the reviewer so it does not raise the same thing again, and while the pull request is open that is the right place for it. Afterwards it is the wrong one. The pull request merges, the next round begins with no memory of the last, and the second occurrence reaches a maintainer with no way to tell it from a first. Each entry below is a claim that was tested against this repository and found false, kept so the proof is read rather than built twice.
**A disproof is proof about this repository, and the thread it was written in is not where the next round looks.** [GOVERNANCE.md "PR Review Etiquette"](../GOVERNANCE.md#pr-review-etiquette), which routes to the `pr-review-conduct` Skill, closes a false finding by disproving it in the thread, addressed to the reviewer so it does not raise the same thing again, and while the pull request is open that is the right place for it. Afterwards it is the wrong one. The pull request merges, the next round begins with no memory of the last, and the second occurrence reaches a maintainer with no way to tell it from a first. Each entry below is a claim that was tested against this repository and found false, kept so the proof is read rather than built twice.

**An entry names the claim, what was run or read to disprove it, the revision it was proved against, and what ends it.** A disproof is true of one tree at one revision, so an entry whose subject moves is deleted by the change that moves it rather than edited to look current, which is the same sweep the [GOVERNANCE.md "Documentation Style Conventions"](../GOVERNANCE.md#documentation-style-conventions) rule already requires of prose asserting a behavior that has changed underneath it. This is deliberately not a list to append to, since an entry outliving the code it was proved against becomes a reason not to check, and that is strictly worse than proving the claim a second time.

Expand Down
2 changes: 1 addition & 1 deletion docs/fleet-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ flowchart LR
### G4: Deletion Sweeps Miss Prose (Closed)

- **Gap** - A resync that deletes a carried file greps for the path and finds code uses, not prose describing the file without naming its path. A measured incident left a layout section describing a deleted script.
- **Resolution** - Split by what a pattern can reach. The named-path half is mechanized: the `dead-path` rule in [`scripts/prose_lint.py`][prose-lint] reports a Markdown mention (a backtick span, an inline link target, or a reference definition) of a path git once tracked and the tree no longer holds. Keying on deletion history is what scopes it: a proposed file a backlog names, another repository's layout, and a ref like `origin/develop` each have no history here and stay silent, and a manifest-declared carried path is exempt since the hub's own instance retires to a catalog snippet while docs keep naming the carried file. The rule runs in the default set and in CI, where the checkout fetches full history because the rule stands down loudly in a shallow clone rather than pass blind. The name-shaped half, the description that names no path, is `accepted` as manual: no pattern reaches it, the same judgment the home-path rule records, so the [`RESYNC.md`][resync] section 4 read of the layout and operations sections stands and now names the lint beside it.
- **Resolution** - Split by what a pattern can reach. The named-path half is mechanized: the `dead-path` rule in [`scripts/prose_lint.py`][prose-lint] reports a Markdown mention (a backtick span, an inline link target, or a reference definition) of a path git once tracked and the tree no longer holds. Keying on deletion history is what scopes it: a proposed file a backlog names, another repository's layout, and a ref like `origin/develop` each have no history here and stay silent, and a manifest-declared carried path is exempt since the hub's own instance retires to a catalog snippet while docs keep naming the carried file. A hub-hosted path is exempt for the mirror-image reason, measured downstream rather than predicted: a repository that deleted its copy per a `retire` disposition still names the hub's, which is the pointer [`GOVERNANCE.md`][governance] "Hub-Hosted Tooling" requires of it, and the manifest exemption cannot reach that case because no repository carries `spec/files.json`. That set is a literal in the gate, since the prose-gate action can fetch the one file and no ledger beside it, and a test asserts it against the ledger's `retire` dispositions so a new retirement cannot land on one side only. The rule runs in the default set and in CI, where the checkout fetches full history because the rule stands down loudly in a shallow clone rather than pass blind. The name-shaped half, the description that names no path, is `accepted` as manual: no pattern reaches it, the same judgment the home-path rule records, so the [`RESYNC.md`][resync] section 4 read of the layout and operations sections stands and now names the lint beside it.
- **Closing test** - `TestDeadPath` in `scripts/tests/test_prose_lint.py`, including the shallow stand-down and the tree-clean assertion. The rule's first tree-wide run caught a real instance, [`docs/host-setup.md`][host-setup-doc] describing bind-mounts in the deleted `.devcontainer/` definitions, fixed by re-pointing at the catalog snippets.

### G5: Intent-Fidelity Drift Is Invisible (Closed)
Expand Down
4 changes: 2 additions & 2 deletions registry/repos.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
"consumerModel": "pull",
"releaseTrigger": "two-phase",
"configLayout": { "rulesetsDir": "repo-config", "pythonConfig": null },
"driftNotes": ["Carries ARCHITECTURE.md and codecov.yml beyond the baseline.", "First csharp+python repo: a C# console app at the root plus a stdlib-only Python tooling subtree (RegressionTests/, uvx scripts profile - no uv.lock, pyproject carries only ruff+mypy config; PlexCleaner#855). python.uvlock.pinned and python.coverage.codecov are N/A for that subtree (no uv project, no tests); codecov.yml stays required for the C# side. Reference for the csharp+python shape (issue #339).", "Branch hygiene: 3 stale Dependabot nuget branches (PRs closed/superseded) and an unmerged feature/727-decouple-release-assets branch linger; main+develop otherwise clean after the 2026-07 sweep."]
"driftNotes": ["Carries ARCHITECTURE.md and codecov.yml beyond the baseline.", "First csharp+python repo: a C# console app at the root plus a stdlib-only Python tooling subtree (RegressionTests/, uvx scripts profile - no uv.lock, pyproject carries only ruff+mypy config; PlexCleaner#855). python.uvlock.pinned and python.coverage.codecov are N/A for that subtree (no uv project, no tests); codecov.yml stays required for the C# side. Reference for the csharp+python shape (issue #339)."]
},
{
"name": "ESPHome-NonRoot",
Expand Down Expand Up @@ -170,7 +170,7 @@
"requiredSecrets": [],
"consumerModel": "pull",
"releaseTrigger": "dispatch-only",
"driftNotes": ["Maintainer config/ops repo (docker-compose stacks, lifecycle scripts, Firewalla configs); Linux-consumed on the Proxmox host, so lineEndings lf.", "Renamed from HomeAutomation for fleet naming consistency (config repos are *-Config). The Vantage controller config is split out to its own Windows/CRLF Vantage-Config repo, not carried here; the legacy Vantage/ subtree is stripped.", "Private; README self-flags previously-committed secrets - secrets-hygiene concern."]
"driftNotes": ["Maintainer config/ops repo (docker-compose stacks, lifecycle scripts, Firewalla configs); Linux-consumed on the Proxmox host, so lineEndings lf.", "Renamed from HomeAutomation for fleet naming consistency (config repos are *-Config). The Vantage controller config is split out to its own Windows/CRLF Vantage-Config repo, not carried here; the legacy Vantage/ subtree is stripped."]
},
{
"name": "KiCadLibrary",
Expand Down
Loading