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
1 change: 1 addition & 0 deletions RESYNC.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ python3 spec/fidelity_honesty.py --report # regenerate reports/divergences
The order is load-bearing. Each step below either changes the rules the later steps are judged against, or removes something a later step would otherwise refresh.

1. **The instruction set first.** `CLAUDE.md`, then `AGENTS.md` and `GOVERNANCE.md` verbatim sections, then `CODESTYLE.md` and `WORKFLOW.md`. `CLAUDE.md` is the single `@AGENTS.md`-import file that gets `AGENTS.md` into a Claude Code session's context at all, so a repo carrying `AGENTS.md` without it is still a partial carry for that provider even though `spec/files.json` lists them as separate entries. `AGENTS.md`'s skill-dependency pointer paragraph, naming `scripts/skills_install.py` and where the fleet's Skills live, carries as one more verbatim unit in this same step, not a separate pass. These are the rules for producing every other file, so carrying them last means everything touched beforehand was judged against the previous revision. This is the same closing-window shape as [`STANDUP.md`][standup] section 1A, and the cost of getting it wrong is rework proportional to how much was changed first. **Run the `carried-instruction-file-guard` skill's distinctive-phrase probe before touching `AGENTS.md`, `GOVERNANCE.md`, `CODESTYLE.md`, or `WORKFLOW.md` in this step, every time, without exception, regardless of whether the update is a verbatim re-vendor or an intent-fidelity edit.** A diff that looks routine is exactly the shape the AGENTS.md-overwrite incident took, so the probe is not weighed against how routine the request sounds, it runs regardless. `CLAUDE.md` is outside that guard's scope entirely, the same as any file the guard was never written to cover: it carries no mixed or repo-specific content by design, so its own re-vendor is an ordinary verbatim-fidelity copy, no probe needed. `resync-a-repo` packages this whole step, and the rest of this section, as a Skill for a hub-context session driving the resync.
Run `python3 scripts/carry.py check-sections <Repo> --target /path/to/worktree` for the `AGENTS.md` and `GOVERNANCE.md` verbatim sections, then `apply-sections` once each reported region's diff has been read. It replaces only the regions the manifest declares verbatim and asserts afterwards that the preamble, the heading sequence, and every other region came through byte-identical, which is the assertion a hand-rolled replacer only gets when whoever wrote it happened to write one. It refuses rather than guessing where a declared section is absent from the repository, since that is a standup question rather than a drift question. It narrows what the `carried-instruction-file-guard` probe has to catch by hand without replacing it: a tool that can only touch declared verbatim regions cannot delete a repository's local additions, and the intent half is still nothing mechanical can judge. It requires a clean worktree and exempts no path from that check, which this step's own position satisfies: nothing else has written to the tree yet.
2. **Deletions second, before any re-vendor.** A `hub-only:` finding names a file the hub hosts rather than carries, and its remedy removes the file. Doing it after the re-vendors means refreshing a copy that is about to be deleted, which is wasted work that also reads as a deliberate update in the diff. See section 4, which is the whole of what deletion means here.
3. **Verbatim re-vendors.** Copy the current hub canonical down, whole file or the one named `## heading` region. A finding classified **stale** matches a past hub revision and needs no judgment. One classified **modified** matches no revision, so the repository changed fixed content and the change is read before it is overwritten, since it may be an improvement the hub should adopt instead.
Run `python3 scripts/carry.py check <Repo> --target /path/to/worktree` for manifest-owned trees. Read each modified-file diff and every extra path before running the same command with `apply`. The tree declaration supplies the prune authority, so the carried-instruction-file guard does not apply to this fully owned content.
Expand Down
2 changes: 1 addition & 1 deletion host-setup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ powershell -ExecutionPolicy Bypass -File menu.ps1
host-setup\menu.ps1 # from a checkout of this repository, or of any other repo in the fleet
```

It answers a different question than `bootstrap.sh`/`bootstrap.ps1` do. Those stand a host up and stop. The menu loops, because a person sitting down with it usually wants more than one thing done in a sitting, and it tells the hub apart from whichever repo it happens to be run from: a checkout of `ptr727/ProjectTemplate` gets the hub tasks (audit a cataloged repo, check the generated Skills distributions), a checkout of any other repo gets the downstream tasks too (check or pull the hub's verbatim-owned files into that repo's own worktree, per [`scripts/carry.py`][carry]), and every run gets the host tasks this directory's `linux/` or `windows/` tooling already provides. Only the downstream tasks need a repo to run from. The hub tasks fetch the hub themselves when there is no local checkout to reuse, so they still show and still work when the menu is run entirely standalone, off no checkout at all.
It answers a different question than `bootstrap.sh`/`bootstrap.ps1` do. Those stand a host up and stop. The menu loops, because a person sitting down with it usually wants more than one thing done in a sitting, and it tells the hub apart from whichever repo it happens to be run from: a checkout of `ptr727/ProjectTemplate` gets the hub tasks (audit a cataloged repo, check the generated Skills distributions), a checkout of any other repo gets the downstream tasks too (check or pull the hub's verbatim-owned files into that repo's own worktree, and the same pair again for the verbatim rule sections inside a file that repo also owns the rest of, per [`scripts/carry.py`][carry]), and every run gets the host tasks this directory's `linux/` or `windows/` tooling already provides. Only the downstream tasks need a repo to run from. The hub tasks fetch the hub themselves when there is no local checkout to reuse, so they still show and still work when the menu is run entirely standalone, off no checkout at all.

Reaching `spec/audit.py` and `scripts/carry.py` from outside a hub checkout means fetching one, the same "hosted and reached, never carried" model [`scripts/README.md`][scripts-readme] states for those tools generally. The menu clones fresh rather than reusing the bootstrap loader's tarball, since `scripts/carry.py` itself checks that its hub argument is a real git checkout on a freshly fetched `origin/main` with no local changes, and a full clone rather than a shallow one, since `spec/audit.py` walks the hub's own commit history to judge whether a carried copy is trailing the file it was copied from. Run from inside the hub itself, that same freshness is confirmed against the local checkout before a hub task uses it, falling back to a fresh clone when the local checkout has moved on, so an audit or a Skills-distribution check never silently reads a stale or feature-branch tree.

Expand Down
12 changes: 10 additions & 2 deletions host-setup/menu.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ Usage: menu.ps1 [options]

An interactive menu over this fleet's host and repo tooling: update the host tools, upgrade the
OS packages, install the fleet skills, audit a cataloged repo, and pull the hub's verbatim-owned
files into a downstream repo's own worktree. Run from a hub checkout or from any other repo. The
menu shows each the tasks that apply to it.
files, or just the verbatim rule sections inside a mixed file, into a downstream repo's own
worktree. Run from a hub checkout or from any other repo. Each is shown the tasks that apply
to it.

Options:
-y, -Yes Pass -Yes to each tool this menu runs, so a tool does not prompt. The menu's
Expand Down Expand Up @@ -497,6 +498,8 @@ function Show-Menu {
log 'Downstream, the repo this menu is run from:'
log ' 13 Check what the hub would change here, change nothing'
log " 14 Pull the hub's verbatim-owned files into this repo"
log " 15 Check the hub's verbatim rule sections inside this repo's mixed files"
log ' 16 Pull those rule sections in, leaving every other section alone'
}
log ''
log ' q Quit'
Expand Down Expand Up @@ -525,6 +528,11 @@ function Invoke-Dispatch {
if ($script:DRY_RUN) { return (Invoke-CarryAction 'check') }
return (Invoke-CarryAction 'apply')
}
'15' { return (Invoke-CarryAction 'check-sections') }
'16' {
if ($script:DRY_RUN) { return (Invoke-CarryAction 'check-sections') }
return (Invoke-CarryAction 'apply-sections')
}
{ $_ -in @('q', 'Q') } { $script:QUIT = $true; return 0 }
default {
warn 'Not one of the choices'
Expand Down
16 changes: 13 additions & 3 deletions host-setup/menu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ usage() {
Usage: menu.sh [options]

An interactive menu over this fleet's host and repo tooling: update the host tools, upgrade the
OS, install the fleet skills, audit a cataloged repo, and pull the hub's verbatim-owned files into
a downstream repo's own worktree. Run from a hub checkout or from any other repo. The menu shows
each the tasks that apply to it.
OS, install the fleet skills, audit a cataloged repo, and pull the hub's verbatim-owned files, or
just the verbatim rule sections inside a mixed file, into a downstream repo's own worktree. Run
from a hub checkout or from any other repo. Each is shown the tasks that apply to it.

Options:
-y, --yes Pass --yes to each tool this menu runs, so a tool does not prompt. The menu's
Expand Down Expand Up @@ -443,6 +443,8 @@ print_menu() {
log "Downstream, the repo this menu is run from:"
log " 12 Check what the hub would change here, change nothing"
log " 13 Pull the hub's verbatim-owned files into this repo"
log " 14 Check the hub's verbatim rule sections inside this repo's mixed files"
log " 15 Pull those rule sections in, leaving every other section alone"
fi
log ""
log " q Quit"
Expand Down Expand Up @@ -475,6 +477,14 @@ dispatch() {
carry_action apply
fi
;;
14) carry_action check-sections ;;
15)
if [[ $DRY_RUN == true ]]; then
carry_action check-sections
else
carry_action apply-sections
fi
;;
q | Q) QUIT=true ;;
*)
warn "Not one of the choices"
Expand Down
17 changes: 16 additions & 1 deletion scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ python3 scripts/tests/test_local_review.py
python3 scripts/tests/test_canonical_review.py
python3 scripts/tests/test_build_dist.py
python3 scripts/tests/test_skills_install.py
python3 scripts/tests/test_carry.py
python3 -m unittest discover -s scripts/tests # all of them, and exits 5 if the suite vanishes
uvx coverage@latest run --source=scripts,spec,host-setup -m unittest discover -s scripts/tests && uvx coverage@latest report
```
Expand Down Expand Up @@ -280,15 +281,27 @@ A skill has to read whole in isolation and a rule has one home, so the text a sk

## `carry.py`

Copies manifest-owned trees from a freshly fetched ProjectTemplate `main` checkout into an isolated downstream feature worktree. `check` reports the canonical commit, repository identity, applicable declarations, path differences, and tree digests without changing the target. `apply` reports each write or removal and repeats the comparison before it succeeds.
Copies manifest-owned trees, and the verbatim `## heading` regions inside a mixed file, from a freshly fetched ProjectTemplate `main` checkout into an isolated downstream feature worktree. `check` reports the canonical commit, repository identity, applicable declarations, path differences, and tree digests without changing the target. `apply` reports each write or removal and repeats the comparison before it succeeds.

```shell
python3 scripts/carry.py check PhotoCleaner --target /path/to/worktree
python3 scripts/carry.py apply PhotoCleaner --target /path/to/worktree
python3 scripts/carry.py check-sections PhotoCleaner --target /path/to/worktree
python3 scripts/carry.py apply-sections PhotoCleaner --target /path/to/worktree
```

The tool validates the registry identity, target origin, feature-branch worktree, development-branch ancestry, unrelated changes, root containment, and symlink-free trees. A pruned declaration authorizes removal only beneath its target root.

The section modes close the gap between the two things that already existed: a tree mode owns a whole tree, and [`spec/audit.py`][audit] names a stale section precisely without writing anything, so the one apply step in [`RESYNC.md`][resync] with no tested tool behind it was the step the AGENTS.md-overwrite incident happened on. `check-sections` reports each declared verbatim section and which of them are stale, and exits `1` while any is. `apply-sections` replaces only those regions and then re-reads the file to assert three things: the level-two heading sequence is unchanged, every line outside the replaced regions is byte-identical to what was there before, and each declared section now matches the hub's canonical. The second of those is the one that earns its keep, since the defect it exists for, one blank line dropped between the preamble and the first heading, is invisible in a diff review, breaks no renderer, and no gate in this repository flags it.

Region boundaries come from `spec/audit.py`'s own fence reading rather than a second reading of CommonMark, so a level-two heading marker shown inside a code sample cannot end a region for this tool and not for the check that judges the result. The replacement carries the terminator the section's own heading line carried, so a CRLF repository keeps its endings and the file is never rewritten to one ending it may not use throughout.

Every refusal is raised while planning, before the first file is written, so a refused run leaves nothing re-vendored. What refuses: a section the manifest declares that the repository does not carry (where it belongs is a standup question), a file absent from the target, a file that is not UTF-8 or that carries a bare carriage return, which the fidelity comparison reads as a line break and this tool does not, a file declaring both placeholders and verbatim sections (a verbatim unit carries none, per [`spec/fidelity-model.md`][fidelity-model]), a region holding content the fidelity comparison normalizes away, such as a Dependabot-owned action pin, whose bytes the repository rather than the hub owns, and two declared names resolving to one region. A failure after that point is the write itself or its assertion, and it names the file it was working on, because an earlier file may already hold its re-vendored content.

A region runs to the next level-two heading, so the blank line before that heading sits inside the region and a section ending its file has none. `spec/audit.py`'s `extract_section` reads those two as the same content, since it splits the document on LF and joins the region back with LF, so a file-final newline and a blank line before a following heading both render as one trailing empty element. The comparison therefore uses that form, and the write reconciles the boundary to what the target document needs rather than copying it from the hub: a section that ends the hub's file keeps a blank line before whatever follows it downstream, and one that ends the target's file gains no trailing blank line.

The target is required to be clean, with no owned root exempted from the unrelated-changes check, unlike a tree carry. A tree's root holds nothing but hub-owned content, so exempting it gives up nothing, where these files hold the repository's own sections too and exempting one would let a write land on top of an uncommitted edit to a section the tool never touches.

## `skills_install.py`

Installs the fleet's Skills for the current machine, cross-platform and idempotent, mirroring [`host-setup/agent-safety/claude/install.py`][agent-safety-install]'s shape: `skills_install.sh` and `skills_install.ps1` are thin wrappers that locate a Python 3 interpreter and hand off, so every OS runs one tested code path. Two independent things happen on a run, since the three tools this fleet targets discover skills differently: `.agents/skills/` is materialized (not symlinked) to `$HOME/.agents/skills/`, so Codex and opencode's global scan covers every repo on the machine rather than only the one that happens to be open, and this repo's marketplace is registered with the `claude` CLI (`claude plugin marketplace add`, `claude plugin install`) so Claude Code loads the same content the other two read directly. The marketplace/plugin registration goes through the `claude` CLI's own commands rather than writing its internal `known_marketplaces.json` by hand, because that file's shape is the CLI's state, not a documented contract, and a hand-written copy risks drifting from what the CLI expects on its next release.
Expand All @@ -307,6 +320,7 @@ Installs the fleet's Skills for the current machine, cross-platform and idempote
[copilot-instructions]: ../.github/copilot-instructions.md
[divergences]: ../spec/divergences.json
[editorconfig]: ../.editorconfig
[fidelity-model]: ../spec/fidelity-model.md
[files]: ../spec/files.json
[fleet-skills-dist]: ../.claude-plugin/fleet-skills/
[github-skills-dist]: ../.github/skills/
Expand All @@ -323,6 +337,7 @@ Installs the fleet's Skills for the current machine, cross-platform and idempote
[prose-gate-action]: ../.github/actions/prose-gate/action.yml
[record-lock-issue]: https://github.com/ptr727/ProjectTemplate/issues/1151
[repos]: ../registry/repos.json
[resync]: ../RESYNC.md
[section-model]: ../spec/section-model.md
[tests]: ./tests/
[validate-hook]: ../.github/actions/validate/action.yml
Loading