Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
872785a
Scaffold fleet Skills: agents/skills source, dist build, installer
ptr727 Aug 13, 2026
8c75e1f
Add carried-instruction-file-guard skill
ptr727 Aug 13, 2026
edea7e5
Fix Copilot findings: cross-platform digest, git pathspec, mkdir, doc…
ptr727 Aug 13, 2026
dfdc428
Reflow hard-wrapped Markdown, fix skills_install exit code
ptr727 Aug 13, 2026
3c0a66e
Fix stale wrapper names and repo-root usage examples
ptr727 Aug 13, 2026
a4eb06a
Fix test mock target, dist/skills-content staleness, report() robustness
ptr727 Aug 13, 2026
2805d08
Fix global-skills-directory safety, stale doc claims, dist edge cases
ptr727 Aug 13, 2026
a4ea517
Fix digest-stamp final newline and widen the dirty-check scope
ptr727 Aug 13, 2026
c0e84b6
Fix the actual CI failure: .source-digest wrote platform-default LF
ptr727 Aug 13, 2026
67063e2
Fix stamp shape/version validation, ~ expansion, Python 3.7+ checks
ptr727 Aug 13, 2026
026b49c
Fix skills_install.ps1: check each launcher's version during selection
ptr727 Aug 13, 2026
b3066ad
Track fleet-installed skills with a marker, retire orphaned copies
ptr727 Aug 13, 2026
362b9d4
Require SKILL.md for materialize_global_skills, matching skill_names()
ptr727 Aug 13, 2026
a6d4346
Reject symlinks in .agents/skills/, fix stale=None, drop backslash joins
ptr727 Aug 13, 2026
c77071b
Compare source and generated digests directly, not just SKILL.md pres…
ptr727 Aug 13, 2026
3f41bdc
Check the skill directory itself for being a symlink, not just its co…
ptr727 Aug 13, 2026
a97cf71
Skip symlinks in the retirement-cleanup scan instead of crashing on one
ptr727 Aug 13, 2026
07fcec9
Compare the full plugin manifest, not only its "skills" field
ptr727 Aug 13, 2026
a8dc4a9
Guard report() against a dict stamp carrying a non-dict "source"
ptr727 Aug 13, 2026
907ae4e
Check the generated tree's directory names, not only the expected ones
ptr727 Aug 13, 2026
7975e9d
Fix overclaiming comment and unrealistic test mocks for source_ref()
ptr727 Aug 13, 2026
b8dbd03
Sort by the hashed posix key, not by raw Path comparison
ptr727 Aug 13, 2026
028fb74
Wire the new self-tests and --check gate into CI
ptr727 Aug 13, 2026
81f3c44
Also check the stamp's own recorded dirty flag, not only the current one
ptr727 Aug 13, 2026
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
7 changes: 7 additions & 0 deletions .agents/skills/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Fleet Skills

Canonical source for the fleet's Claude Code / opencode / Codex Skills, one directory per skill: `<name>/SKILL.md` plus optional `scripts/` and `references/`. This is the only place a skill's content is hand-authored. Everything else derived from it is generated, never hand-edited.

Codex and opencode read this directory directly (`.agents/skills/<name>/SKILL.md`), no install step required, walking from a downstream repo's working directory up to its own repository root. Claude Code does not scan this path. `scripts/build_dist.py` generates a Claude-plugin-compatible copy at `.claude-plugin/fleet-skills/`, published through `.claude-plugin/marketplace.json`.

See `AGENTS.md` for how a repo depends on these skills and `scripts/README.md` for `build_dist.py` and the installer.
31 changes: 31 additions & 0 deletions .agents/skills/carried-instruction-file-guard/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
name: carried-instruction-file-guard
description: >-
Stops a blind overwrite of a downstream repo's AGENTS.md, GOVERNANCE.md, CODESTYLE.md, or WORKFLOW.md when resyncing or updating it to match the ptr727/ProjectTemplate hub template. Use this whenever about to edit, replace, re-vendor, or sync-to-match-the-hub any of those four files in a repository that is not ProjectTemplate itself, or whenever asked to bring a repo's instruction set up to date, run a conformance sweep, or fix drift against the hub. Triggers even when the request sounds routine, such as copying the hub's AGENTS.md over or resyncing a repo's docs, because that phrasing is exactly how a real incident happened, where a downstream repo's local rules were silently deleted by a full-file overwrite. Do not skip this just because the task looks mechanical.
---

# Carried Instruction File Guard

## Why this exists

A downstream repo's `AGENTS.md`/`GOVERNANCE.md`/`CODESTYLE.md`/`WORKFLOW.md` can hold two different kinds of content mixed in one file: sections that are stale copies of the hub's fleet-wide rules, and local rules the repo wrote for a fault the fleet has never seen elsewhere. Re-vendoring the hub's canonical version over the whole file deletes the second kind silently, because nothing about the diff looks wrong. This has actually happened: a resync replaced a repo's `AGENTS.md` wholesale with the hub's, and the repo's own local additions were gone with no error, no warning, and no review comment calling it out.

The fix is not "be careful." Being careful is what failed the first time. The fix is a mechanical check you run before any overwrite touches one of these four files, every time, regardless of how routine the request sounds.

## Before you touch any of these four files

1. **Check whether the file's content is declared `verbatim` or `intent`.** The hub's `spec/section-model.md` (fetch it from a hub checkout, `github.com/ptr727/ProjectTemplate`, if you don't have one) names, section by section, which parts of `AGENTS.md` and `GOVERNANCE.md` are universal fleet law (safe to byte-match against the hub) and which describe the repo itself (never safe to overwrite from another repo). `CODESTYLE.md` and `WORKFLOW.md` are carried whole at `intent` fidelity, judged by meaning, not hashed.
2. **If any part of the file is `intent`, or if the file predates a clean split into hub-governed sections, do not diff-and-replace. Probe instead.** For each rule or paragraph in the current file that is not obviously boilerplate:
- Pick the phrase in it that is most peculiar to this repo, not generic governance vocabulary. A rule about "always sign commits" is generic. A rule about "this repo's Docker image pins Alpine 3.19 because 3.20 broke the s6 supervisor" is peculiar.
- Grep the hub's canonical copy of the same file for that peculiar phrase.
- **Absent from the hub canonical means it is a local addition.** It is never dropped because it looks similar to something else, and never dropped because a merge or overwrite would be simpler without it.
3. **A local addition found by the probe gets a destination, not a deletion.** Either it names a rule that should apply fleet-wide (flag it for the maintainer to promote into the hub), or it is genuinely specific to this repo and moves to the repo's own topical doc before the carried file is touched: `CODESTYLE.md` for a language/formatting convention, `ARCHITECTURE.md` for a design decision, `OPERATIONS.md` for a runbook or operational note, `TODO.md` for backlog. Move it, confirm it is not lost, and only then proceed with the carry.
4. **Do not trust a similarity or word-overlap check for step 2.** A repo-specific rule written in ordinary governance language reads as a reworded duplicate of an unrelated hub rule to that kind of check, and it will confidently tell you the local content is redundant when it is not. Exact phrase presence or absence is the only check that has held up.

## What is actually safe to overwrite without this procedure

A section `spec/section-model.md` names as `verbatim`, in a file that is already cleanly split (the file carries only that declared section, nothing else mixed in), can be re-vendored directly: byte-matching it against the hub canonical is the point of `verbatim` fidelity, and the audit already checks it that way. The guard above is for everything else: `intent`-fidelity content, a file that has not been split yet, or any file you are not certain is clean.

## If you are not sure which case you are in

Stop and say so, rather than guessing. Naming the uncertainty costs one sentence. Silently overwriting the wrong thing costs someone's local rules with no way to notice until much later.
11 changes: 11 additions & 0 deletions .claude-plugin/fleet-skills/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "fleet-skills",
"version": "0.0.0",
"description": "Fleet-wide agent rules (comment style, PR-review conduct, resync safety) packaged as Claude Code Skills, generated from .agents/skills/.",
"author": {
"name": "ptr727"
},
"skills": [
"./skills/carried-instruction-file-guard"
]
}
1 change: 1 addition & 0 deletions .claude-plugin/fleet-skills/.source-digest
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
948c01300ae03ac7
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
name: carried-instruction-file-guard
description: >-
Stops a blind overwrite of a downstream repo's AGENTS.md, GOVERNANCE.md, CODESTYLE.md, or WORKFLOW.md when resyncing or updating it to match the ptr727/ProjectTemplate hub template. Use this whenever about to edit, replace, re-vendor, or sync-to-match-the-hub any of those four files in a repository that is not ProjectTemplate itself, or whenever asked to bring a repo's instruction set up to date, run a conformance sweep, or fix drift against the hub. Triggers even when the request sounds routine, such as copying the hub's AGENTS.md over or resyncing a repo's docs, because that phrasing is exactly how a real incident happened, where a downstream repo's local rules were silently deleted by a full-file overwrite. Do not skip this just because the task looks mechanical.
---

# Carried Instruction File Guard

## Why this exists

A downstream repo's `AGENTS.md`/`GOVERNANCE.md`/`CODESTYLE.md`/`WORKFLOW.md` can hold two different kinds of content mixed in one file: sections that are stale copies of the hub's fleet-wide rules, and local rules the repo wrote for a fault the fleet has never seen elsewhere. Re-vendoring the hub's canonical version over the whole file deletes the second kind silently, because nothing about the diff looks wrong. This has actually happened: a resync replaced a repo's `AGENTS.md` wholesale with the hub's, and the repo's own local additions were gone with no error, no warning, and no review comment calling it out.

The fix is not "be careful." Being careful is what failed the first time. The fix is a mechanical check you run before any overwrite touches one of these four files, every time, regardless of how routine the request sounds.

## Before you touch any of these four files

1. **Check whether the file's content is declared `verbatim` or `intent`.** The hub's `spec/section-model.md` (fetch it from a hub checkout, `github.com/ptr727/ProjectTemplate`, if you don't have one) names, section by section, which parts of `AGENTS.md` and `GOVERNANCE.md` are universal fleet law (safe to byte-match against the hub) and which describe the repo itself (never safe to overwrite from another repo). `CODESTYLE.md` and `WORKFLOW.md` are carried whole at `intent` fidelity, judged by meaning, not hashed.
2. **If any part of the file is `intent`, or if the file predates a clean split into hub-governed sections, do not diff-and-replace. Probe instead.** For each rule or paragraph in the current file that is not obviously boilerplate:
- Pick the phrase in it that is most peculiar to this repo, not generic governance vocabulary. A rule about "always sign commits" is generic. A rule about "this repo's Docker image pins Alpine 3.19 because 3.20 broke the s6 supervisor" is peculiar.
- Grep the hub's canonical copy of the same file for that peculiar phrase.
- **Absent from the hub canonical means it is a local addition.** It is never dropped because it looks similar to something else, and never dropped because a merge or overwrite would be simpler without it.
3. **A local addition found by the probe gets a destination, not a deletion.** Either it names a rule that should apply fleet-wide (flag it for the maintainer to promote into the hub), or it is genuinely specific to this repo and moves to the repo's own topical doc before the carried file is touched: `CODESTYLE.md` for a language/formatting convention, `ARCHITECTURE.md` for a design decision, `OPERATIONS.md` for a runbook or operational note, `TODO.md` for backlog. Move it, confirm it is not lost, and only then proceed with the carry.
4. **Do not trust a similarity or word-overlap check for step 2.** A repo-specific rule written in ordinary governance language reads as a reworded duplicate of an unrelated hub rule to that kind of check, and it will confidently tell you the local content is redundant when it is not. Exact phrase presence or absence is the only check that has held up.

## What is actually safe to overwrite without this procedure

A section `spec/section-model.md` names as `verbatim`, in a file that is already cleanly split (the file carries only that declared section, nothing else mixed in), can be re-vendored directly: byte-matching it against the hub canonical is the point of `verbatim` fidelity, and the audit already checks it that way. The guard above is for everything else: `intent`-fidelity content, a file that has not been split yet, or any file you are not certain is clean.

## If you are not sure which case you are in

Stop and say so, rather than guessing. Naming the uncertainty costs one sentence. Silently overwriting the wrong thing costs someone's local rules with no way to notice until much later.
14 changes: 14 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "projecttemplate-fleet",
"description": "Fleet-wide agent rules for the ptr727 repos, packaged as Claude Code Skills.",
"owner": {
"name": "ptr727"
},
"plugins": [
{
"name": "fleet-skills",
"description": "Fleet-wide agent rules (comment style, PR-review conduct, resync safety) packaged as Claude Code Skills.",
"source": "./.claude-plugin/fleet-skills"
}
]
}
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ scripts/test_pr_review.py text eol=lf
scripts/host_gate.py text eol=lf
scripts/test_host_gate.py text eol=lf
scripts/test_bootstrap.py text eol=lf
scripts/build_dist.py text eol=lf
scripts/test_build_dist.py text eol=lf
scripts/skills_install.py text eol=lf
scripts/test_skills_install.py text eol=lf

# The uv tool regenerates uv.lock with LF on every platform, so pin it here.
# That makes git enforce LF on checkout and renormalize, so a CRLF-default repo does not fight the tool on every `uv lock` or `uv sync`.
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/validate-task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,19 @@ jobs:
python3 scripts/test_prose_lint.py
python3 scripts/test_repo_gate.py
python3 scripts/test_pr_review.py
python3 scripts/test_build_dist.py
python3 scripts/test_skills_install.py
python3 spec/audit.py --selftest
python3 host-setup/agent-safety/gh-write-guard.py --selftest
python3 host-setup/agent-safety/test_install.py

- name: Check repo gates step
run: python3 scripts/repo_gate.py

# Read-only: fails if .claude-plugin/fleet-skills/ was not regenerated from .agents/skills/.
- name: Check fleet skills are current step
run: python3 scripts/build_dist.py --check

# Every default prose rule is clean tree-wide, so all but one block a change that adds a finding.
# `charset-unknown` is the exception and reports in the step below, for the reason given there.
- name: Check prose step
Expand Down
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,5 @@ Every rule below is a level-two section of [`GOVERNANCE.md`](./GOVERNANCE.md). R
| Editor settings and tasks | `Editor and Tasks` |
| The About panel, description, or repo toggles | `Repository Details` |
| Where a file belongs in the tree | `Repository Layout` |

Some of the rules above are also packaged as Claude Code / opencode / Codex Skills, hand-authored at [`.agents/skills/`](./.agents/skills/) in the hub, 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.
15 changes: 15 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ The steps below are followed in order rather than sampled.

## Work Clusters

### Default `.py` to LF Fleet-Wide, Retiring the Per-Path Pin List

One pull request changing [`GOVERNANCE.md`][governance] "Line Endings", [`.editorconfig`][editorconfig], and [`.gitattributes`][gitattributes] to pin `*.py` LF by extension, replacing the growing list of individually-pinned shebang scripts, plus the one-time renormalization it obliges fleet-wide.

**State** `decision`. **Touches** `GOVERNANCE.md` "Line Endings" (verbatim, so it re-vendors fleet-wide), `.editorconfig`, `.gitattributes`, and every downstream repo carrying a CRLF `.py` file. **Cost** one hub edit plus a renormalization pass per affected repo. The hub itself needs no renormalization, since every `.py` file it tracks is already LF.

- **Pin `*.py text eol=lf` by extension and drop the by-path list it replaces.** The by-path list exists because the fleet's default is CRLF for `.py` and only a shebang-executed script needs LF, so each new script has needed its own `.gitattributes` line and its own `.editorconfig` override. It reads 25 entries today, up from the roughly dozen it carried before this session added four more for two new scripts and their tests, which is the divergence outweighing the reason it was chosen.
- **Blocked by** - Nothing.
- **Issue** - None filed.
- **Checked** - `develop` at `7f9caaa` on 2026-08-12, where `.gitattributes` carries 25 LF pins (3 forward-declared) over 19 tracked `.py` files, and every one of those 19 is already LF (none is a vanilla CRLF `.py`), so the hub side of this change is comment-and-pattern-only.
- **Open** - Whether downstream repos with a vanilla (non-shebang) CRLF `.py` file need a coordinated renormalization pass or can pick it up on their own next resync. 5 repos in [`registry/repos.json`][repos] carry the `python` type (`aiopurpleair`, `homeassistant-purpleair`, `Financial-Modeling`, `PlexCleaner`, `ESPHome-Config`) and were not individually checked for CRLF `.py` content as part of writing this entry.
- **Settled** - The default was set to CRLF in #229 for editor compatibility on Windows, not because LF broke anything measured. The reason it is being revisited is that non-VSCode Windows editors were the concern, and the per-path pin list's growth now outweighs that concern's practical weight, per the maintainer.
- **Settled** - The by-path convention was reaffirmed in #503 ("Do not re-add a blanket `*.py text eol=lf`"), but that pull request only reshaped the two files' comment prose and did not re-examine the underlying policy, so it is not a second, independent rejection of this change.

### Giving the Fleet's Own Pins Something to Resolve Against

One pull request pointing a hub `uses:` at a hub-owned action, so that the resolvability pass added beside it has a reference under this owner to read. It is separated from that pass because it changes what a workflow runs, where the pass only changes what a gate reports.
Expand Down Expand Up @@ -583,6 +597,7 @@ Nothing is awaiting close today. [#578][issue-578] was the last entry here and c
[editorconfig]: ./.editorconfig
[fidelity-honesty]: ./spec/fidelity_honesty.py
[files]: ./spec/files.json
[gitattributes]: ./.gitattributes
[governance]: ./GOVERNANCE.md
[host-setup-doc]: ./docs/host-setup.md
[host-setup-windows]: ./host-setup/windows/
Expand Down
Loading