Scaffold fleet Skills: agents/skills source, dist build, installer - #676
Conversation
Phase 0 of converting the hub's instruction-heavy docs (comment style, PR-review conduct, resync safety) into Claude Code / opencode / Codex Skills, so the rules surface automatically instead of needing to be re-read every session. .agents/skills/ is the one hand-authored source, read natively by Codex and opencode. Claude Code needs a bridge: build_dist.py generates a Claude-plugin-compatible copy at .claude-plugin/fleet-skills/, published through .claude-plugin/marketplace.json. skills_install.py (with skills_install.sh/.ps1 thin wrappers, mirroring host-setup/agent-safety/install.py's shape) materializes the global Codex/opencode skills directory and registers the marketplace with Claude Code via the real `claude plugin marketplace add`/`install` CLI. No skills exist yet; this only builds the carrying machinery. AGENTS.md gains a pointer in "Where the Rules Live" naming the dependency and how to install it. Also logs a TODO.md cluster for defaulting `.py` to LF fleet-wide, found while pinning the two new scripts individually: the per-path pin list this session added four more lines to is the divergence outweighing the reason CRLF was chosen as the default. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Encodes spec/section-model.md's "Migrating a repo onto the split" procedure (the distinctive-phrase probe) as an active pre-flight check before any AGENTS.md/GOVERNANCE.md/CODESTYLE.md/WORKFLOW.md overwrite, rather than the prose-only advisory it was before. Highest-leverage skill in the plan: it directly targets the bug class that once silently deleted a downstream repo's local AGENTS.md additions during a resync. Verified live: prompted a session with the skill loaded (`claude --plugin-dir`) to blindly copy a canonical AGENTS.md over a scratch repo's AGENTS.md carrying a local addition. The skill triggered, identified the local section, refused the overwrite, and proposed relocating the content before carrying the rest, exactly the procedure it specifies. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Scaffolds a fleet Skills distribution and install mechanism so agent governance rules can be delivered as Skills across Claude Code, Codex, and opencode, starting with a carried-instruction-file guard skill.
Changes:
- Adds a canonical Skills source tree under
.agents/skills/and a generated Claude plugin distribution under.claude-plugin/fleet-skills/, withscripts/build_dist.pyto regenerate and--checkstaleness detection. - Adds a cross-platform installer (
scripts/skills_install.pyplus.sh/.ps1wrappers) with--reportstaleness reporting and Claude marketplace registration via theclaudeCLI. - Updates documentation and pointers (AGENTS + scripts/README) and pins LF line endings for the new Python scripts.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| TODO.md | Adds a backlog cluster entry about potentially pinning *.py to LF fleet-wide. |
| scripts/build_dist.py | Implements generator for .claude-plugin/fleet-skills/ plus --check staleness detection. |
| scripts/test_build_dist.py | Unit tests for build_dist.py regeneration and staleness behavior. |
| scripts/skills_install.py | Implements cross-platform installer and --report staleness reporting. |
| scripts/test_skills_install.py | Unit tests for skills_install.py materialization and report behavior. |
| scripts/skills_install.sh | Bash wrapper to run the installer with Python 3. |
| scripts/skills_install.ps1 | PowerShell wrapper to run the installer with Python 3 on Windows. |
| scripts/README.md | Documents the new generator and installer scripts and their read-only modes. |
| AGENTS.md | Adds a pointer explaining that some rules are packaged as Skills and how to install/report. |
| .gitattributes | Pins LF for the newly added Python scripts and their tests. |
| .claude-plugin/marketplace.json | Adds Claude marketplace manifest for the local plugin source. |
| .claude-plugin/fleet-skills/.claude-plugin/plugin.json | Adds generated Claude plugin manifest listing the packaged skills. |
| .claude-plugin/fleet-skills/.source-digest | Adds generated source digest stamp used for staleness detection. |
| .claude-plugin/fleet-skills/skills/carried-instruction-file-guard/SKILL.md | Adds generated copy of the carried instruction file guard skill. |
| .agents/skills/README.md | Documents .agents/skills/ as the canonical, hand-authored Skills source. |
| .agents/skills/carried-instruction-file-guard/SKILL.md | Adds the canonical carried instruction file guard skill content. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…string - source_digest(): hash .as_posix() instead of str(Path), so the digest agrees across OSes over identical bytes instead of differing by path separator. - source_ref(): pass a repo-relative pathspec to `git status --porcelain` instead of an absolute path, which under `git -C <repo>` silently and permanently reported dirty=False. - materialize_global_skills(): create the target's parent directory before copytree, and handle the target already being a stray file or symlink instead of only a directory. - skills_install.py docstring: fix the stale "dist/claude/" reference left over from the .claude-plugin/fleet-skills/ rename. Adds regression tests for the digest separator and the missing-parent case. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (6)
.agents/skills/README.md:10
- This paragraph is hard-wrapped. Per GOVERNANCE.md "Documentation Style Conventions" (Markdown) use one logical paragraph per line with no hard-wrap line-length limit.
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`.
.agents/skills/README.md:13
- This paragraph is hard-wrapped. Per GOVERNANCE.md "Documentation Style Conventions" (Markdown) use one logical paragraph per line with no hard-wrap line-length limit.
Empty today: this is Phase 0 scaffolding. See `AGENTS.md` for how a repo depends on these skills
and `scripts/README.md` for `build_dist.py` and the installer.
.agents/skills/README.md:5
- This paragraph is hard-wrapped. Per GOVERNANCE.md "Documentation Style Conventions" (Markdown) use one logical paragraph per line with no hard-wrap line-length limit.
This issue also appears in the following locations of the same file:
- line 7
- line 12
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.
AGENTS.md:96
- The new paragraph is hard-wrapped across multiple lines. Per GOVERNANCE.md "Documentation Style Conventions" (Markdown) use one logical paragraph per line with no hard-wrap line-length limit.
Some of the rules above are also packaged as Claude Code / opencode / Codex Skills, hand-authored
at [`.agents/skills/`](./.agents/skills/), so they surface automatically instead of needing to be
re-read every session. Run `scripts/skills_install.sh` (or `.ps1` on Windows) once per machine to
install them. `scripts/skills_install.py --report` 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.
scripts/skills_install.py:167
- If
claudeis available but marketplace registration / plugin install fails, the script still exits 0, which makes automation think the full install succeeded. Treat aclaudeinstall failure as a non-zero exit (while still writing the stamp) so callers can detect and handle it.
claude_registered = False
if claude_available():
claude_registered = register_claude_marketplace()
else:
print("`claude` not found on PATH, skipping Claude Code marketplace registration "
"(Codex/opencode global skills were still installed).", file=sys.stderr)
home.mkdir(parents=True, exist_ok=True)
stamp_path.write_text(json.dumps(build_stamp(claude_registered), indent=2) + "\n", encoding="utf-8")
print(f"Installed to {home / 'skills'}. Claude Code marketplace registered: {claude_registered}.")
return 0
.agents/skills/carried-instruction-file-guard/SKILL.md:23
- This Markdown file uses hard-wrapped prose (sentences/paragraphs split across multiple lines). Per GOVERNANCE.md "Documentation Style Conventions" (Markdown), use one logical paragraph per line with no hard-wrap line-length limit. Since this is the canonical skill source, please reflow the whole document and then regenerate the
.claude-plugin/fleet-skills/copy from it.
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`
GOVERNANCE.md "Documentation Style Conventions" (Markdown) requires one logical paragraph per line, no hard-wrap line-length limit. Three files this branch authored wrapped at ~95-100 chars instead: .agents/skills/README.md, the new AGENTS.md pointer paragraph, and the whole of carried-instruction-file-guard/SKILL.md. Reflowed all three to single-line paragraphs and regenerated the dist copy. skills_install.py's main() also exited 0 even when `claude` was present but marketplace registration failed, which would make a scripted caller think a real failure was a clean install. It now distinguishes that from the expected case of `claude` simply not being on PATH (a partial-but-fine install for a Codex/opencode-only machine): only the former is a real failure and exits 1. Adds 3 tests for the distinction. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Addressing the 6 suppressed findings from the round-2 review, fixed in dfdc428:
Correct on all 4 locations (
Correct. 🤖 Generated with Claude Code |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (4)
scripts/skills_install.py:24
- The Usage examples omit the
scripts/prefix even though this file lives underscripts/. This is inconsistent withAGENTS.md(which points toscripts/skills_install.py --report) and can confuse users running from repo root.
Usage: python3 skills_install.py (installs)
python3 skills_install.py --report (read-only: is this machine current?)
AGENTS_HOME=/x python3 skills_install.py (override the global skills target, for testing)
scripts/skills_install.py:15
- The docstring refers to wrapper names
install.sh/install.ps1, but the wrappers added in this PR areskills_install.sh/skills_install.ps1. Keeping these names accurate avoids sending users to non-existent files.
This issue also appears on line 22 of the same file.
Both wrappers (install.sh, install.ps1) call this, so every OS runs one tested code path.
scripts/skills_install.sh:6
- The wrapper's invocation examples assume running from within
scripts/(e.g.,./skills_install.sh), butAGENTS.mdinstructs runningscripts/skills_install.shfrom repo root. Aligning these examples avoids confusion.
# ./skills_install.sh installs the fleet skills for this user
# AGENTS_HOME=/x ./skills_install.sh overrides the target (testing)
scripts/skills_install.ps1:5
- The PowerShell wrapper's usage examples assume the current directory is
scripts/(e.g.,.\skills_install.ps1), butAGENTS.mdinstructs running it viascripts/skills_install.ps1from repo root. Aligning the examples avoids confusion.
# .\skills_install.ps1
# $env:AGENTS_HOME = "C:\path"; .\skills_install.ps1 # override the target (testing)
skills_install.py's docstring still said install.sh/install.ps1 (the
agent-safety pair's names) instead of skills_install.sh/skills_install.ps1.
All three scripts' usage examples also assumed running from inside
scripts/ (./skills_install.sh) rather than from repo root
(scripts/skills_install.sh), which is what AGENTS.md actually
instructs. Labels the bare commands ("Run:"/"Or:") rather than
capitalizing the path they open on, matching the existing
git-config-example precedent in .gitattributes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Addressing the 4 new suppressed findings from round 3, fixed in 3c0a66e:
Correct, fixed in the module docstring.
Correct, a leftover from copying the
Correct for both (The 6 earlier-round suppressed findings shown again in this round's digest were already answered in the previous comment on this PR.) 🤖 Generated with Claude Code |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
scripts/test_build_dist.py:27
- The test uses a
dist/claude/...path even though this PR intentionally moved the generated output under.claude-plugin/to avoid the repo'sdist/meaning. Using a matching temp path makes the test align with the documented layout and avoids reintroducing the old location in future edits.
self.tmp = Path(self.enterContext(__import__("tempfile").TemporaryDirectory()))
self.skills_src = self.tmp / ".agents" / "skills"
self.dist_plugin = self.tmp / "dist" / "claude" / "fleet-skills"
self.addCleanup(self._restore, build_dist.SKILLS_SRC, build_dist.DIST_PLUGIN,
build_dist.PLUGIN_MANIFEST, build_dist.DIGEST_STAMP)
build_dist.SKILLS_SRC = self.skills_src
build_dist.DIST_PLUGIN = self.dist_plugin
build_dist.PLUGIN_MANIFEST = self.dist_plugin / ".claude-plugin" / "plugin.json"
build_dist.DIGEST_STAMP = self.dist_plugin / ".source-digest"
scripts/build_dist.py:89
is_stale()only checks.source-digest. If the plugin manifest orskills/directory is missing/corrupted but the digest stamp remains,--checkcan incorrectly report the dist as current. Treat missingDIST_PLUGIN,plugin.json, orskills/as stale.
def is_stale():
"""Whether the generated plugin needs regenerating: missing, or built from different source bytes."""
if not DIGEST_STAMP.is_file():
return True
return DIGEST_STAMP.read_text(encoding="utf-8").strip() != source_digest(skill_names())
scripts/skills_install.py:141
report()will currently crash on an unreadable/invalid JSON stamp, and it reports "current" purely on commit equality even when either the stamp or this checkout is marked dirty. Handling decode/IO errors and treating dirty sources as stale makes--reportreliable for scripting.
stamp = json.loads(stamp_path.read_text(encoding="utf-8"))
current = source_ref()
stale = stamp.get("source", {}).get("commit") != current.get("commit")
print(json.dumps({"stamp": stamp, "currentCommit": current.get("commit"), "stale": stale}, indent=2))
return 1 if stale else 0
- test_skills_install.py: mock.patch.object on skills_install's "print" attribute raised AttributeError, since the module never defines one (print resolves from builtins). Patches builtins.print instead. - build_dist.py is_stale(): now also treats a missing plugin.json or a missing generated SKILL.md as stale, not only a missing or mismatched digest stamp, since a stamp surviving a partial deletion previously reported current over a plugin that no longer actually resolves. - skills_install.py report(): catches a corrupt/unreadable stamp instead of crashing, and treats a dirty checkout as stale even when the commit matches, since a caller trusting "current" there would be trusting bytes that were never actually installed. - test_build_dist.py: renamed a leftover dist/claude test path to .claude-plugin, matching the actual generated layout. 6 new tests covering all four fixes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Addressing the 3 new suppressed findings from round 4, fixed in a4eb06a:
Correct, a leftover variable-name mismatch from the rename. Renamed to
Correct. Now also checks that
Correct on both counts. (The 10 earlier-round suppressed findings shown again in this round's digest were already answered in the previous comments on this PR.) 🤖 Generated with Claude Code |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.
Suppressed comments (4)
.agents/skills/README.md:7
- README claims the directory is "Empty today" but this PR adds at least
carried-instruction-file-guard, so the description is now inaccurate.
Empty today: this is Phase 0 scaffolding. See `AGENTS.md` for how a repo depends on these skills and `scripts/README.md` for `build_dist.py` and the installer.
AGENTS.md:91
- This instructs running
scripts/skills_install.shfrom the current repo, butscripts/is explicitly hub-hosted and "never carried" (scripts/README.md), so downstream repos that carry AGENTS.md typically won't have this path. The instruction should say to run the installer from a ProjectTemplate hub checkout.
Some of the rules above are also packaged as Claude Code / opencode / Codex Skills, hand-authored at [`.agents/skills/`](./.agents/skills/), so they surface automatically instead of needing to be re-read every session. Run `scripts/skills_install.sh` (or `.ps1` on Windows) once per machine to install them. `scripts/skills_install.py --report` 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.
scripts/build_dist.py:75
regenerate()unconditionally callsshutil.rmtree(DIST_PLUGIN)when the path exists. If a stray file or symlink exists at.claude-plugin/fleet-skills, this will raise (NotADirectoryError) instead of regenerating.
if DIST_PLUGIN.exists():
shutil.rmtree(DIST_PLUGIN)
scripts/build_dist.py:89
is_stale()docstring says it "checks the manifest" beyond the digest stamp, but the implementation only checks thatPLUGIN_MANIFESTexists (not its contents). Either validate the manifest contents or adjust the docstring to match what is actually checked.
"""Whether the generated plugin needs regenerating: missing, corrupted, or built from
different source bytes. Checks the manifest and each skill's directory too, not only the
digest stamp, since a stamp surviving a partial deletion would otherwise report current over
a plugin that no longer actually resolves."""
- skills_install.py materialize_global_skills(): no longer wipes the entire ~/.agents/skills/ target before copying. That directory is a shared convention, not this fleet's own, so a blanket rmtree deleted any skill a machine had installed from another source as a side effect of installing this fleet's skills. Now overlays only this fleet's own skill names, leaving everything else in target alone. Adds a regression test asserting an unrelated skill survives. - AGENTS.md: the skills-install pointer instructed running scripts/skills_install.sh as if it were carried into every repo, but scripts/ is hub-hosted and reached, never carried (scripts/README.md). A downstream repo carrying AGENTS.md would not actually have that path. Now instructs reaching a hub checkout first, matching the existing Hub-Hosted Tooling convention. - .agents/skills/README.md: dropped the "Empty today" claim, stale since carried-instruction-file-guard already exists. - build_dist.py regenerate(): handles DIST_PLUGIN being a stray file or symlink instead of only a directory, which previously raised NotADirectoryError from shutil.rmtree. - build_dist.py is_stale(): also validates the manifest's own "skills" list content, not only that plugin.json exists, so a hand-edited or corrupted manifest is caught too. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Addressing the 4 new suppressed findings from round 5, fixed in 2805d08:
Correct, dropped the stale claim.
Correct catch, and an important one — my pointer text contradicted this repo's own existing "reach, don't carry" convention for
Correct. Now unlinks a stray file/symlink instead of only handling the directory case.
Correct — closed the gap rather than just fixing the docstring: (Earlier-round suppressed findings shown again in this round's digest were already answered in previous comments on this PR.) 🤖 Generated with Claude Code |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (2)
scripts/build_dist.py:83
- regenerate() writes
.source-digestwithout a trailing newline. This can violate the repo-wide.editorconfiginsert_final_newline = trueexpectation and can make a freshly-regenerated tree fail style/EOL checks.
DIGEST_STAMP.write_text(source_digest(names), encoding="utf-8")
scripts/skills_install.py:68
source_ref()claims to report whether "the tree is dirty", but the currentgit status --porcelaincall is scoped only to.agents/skills. That can reportdirty=Falseeven when other repo files affecting the install (e.g..claude-plugin/marketplace.json/ generated plugin content) have local modifications, which makes--reportassert "current" over bytes that were never installed from the recorded commit.
# A repo-relative pathspec, not str(SKILLS_SRC).
# With `git -C <repo>`, an absolute path can fail to match anything.
# That silently and permanently reports dirty=False.
status = git("status", "--porcelain", "--", SKILLS_SRC.relative_to(ROOT).as_posix())
ref["dirty"] = bool(status)
- build_dist.py: .source-digest now ends with a trailing newline, matching this repo's .editorconfig insert_final_newline default. - skills_install.py source_ref(): the dirty check was scoped only to .agents/skills/, missing a modified .claude-plugin/ (marketplace.json or generated plugin content), which is also read by the install. A change there alone silently reported dirty=False, so --report could assert "current" over bytes that were never installed. Confirmed live: editing marketplace.json now flips dirty to True, which it did not before this fix. Adds 2 tests: a final-newline assertion, and a mocked git-status call asserting both watched paths are in the pathspec. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Addressing the round-20 finding, fixed in b8dbd03:
Correct, fixed exactly as suggested — sorts by No regression test added, flagging honestly rather than keeping one that doesn't prove anything: I wrote a test mocking 🤖 Generated with Claude Code |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (1)
scripts/build_dist.py:13
- The PR introduces a staleness check mode (
--check) and new self-tests, but the existing CI workflow doesn’t invoke them. For example,.github/workflows/validate-task.ymlcurrently runsscripts/test_prose_lint.py,scripts/test_repo_gate.py, andscripts/test_pr_review.py, but notscripts/test_build_dist.py,scripts/test_skills_install.py, orpython3 scripts/build_dist.py --check. Without wiring these into CI, the generated.claude-plugin/fleet-skills/tree can drift undetected and the new installer/generator behavior won’t be continuously validated.
Usage: python3 scripts/build_dist.py regenerate the plugin from .agents/skills/
python3 scripts/build_dist.py --check read-only: exit 1 if the plugin is stale
"""
test_build_dist.py, test_skills_install.py, and build_dist.py --check existed but were never invoked by validate-task.yml, so a future regression in the generator or installer, or a .claude-plugin/fleet-skills/ tree that drifted from .agents/skills/, would go undetected. Added the two new self-tests to the existing "Run script self-tests step" alongside the others, and a new "Check fleet skills are current step" running build_dist.py --check, matching the shape of the existing "Check repo gates step". Verified the exact commands CI will run, locally: both test files pass and --check reports current on this tree. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Addressing the round-21 finding, fixed in 028fb74:
Correct — a real gap, not a nitpick, since it meant every fix earlier in this PR was only ever going to be checked by hand going forward. Added 🤖 Generated with Claude Code |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.
Suppressed comments (1)
scripts/skills_install.py:221
--reportcan incorrectly claim a machine is current when the installer was run from a dirty checkout and the working tree later becomes clean at the same commit. The stamp already recordssource.dirty, butstaleonly checks the current checkout’s dirty bit, not the stamp’s, so a dirty install can be treated as current solely because the commit SHA matches.
stale = bool(
current_commit is None
or stamp_commit != current_commit
or current.get("dirty")
)
report() checked whether the current checkout is dirty, but never read the stamp's own recorded "dirty" flag from install time. A machine installed from a dirty checkout has bytes on disk that were never any commit's clean tree. If the working tree later becomes clean at the same commit (by committing the changes, or by coincidence), the commit comparison alone would pass and the dirty install would be reported current, even though nothing about a later clean state proves what was actually materialized at install time. stale now also checks stamp_dirty (the stamp's own recorded flag), alongside the current checkout's. Verified the new test genuinely catches this before committing the fix: reverted to the pre-fix code, confirmed it failed (0 != 1, not just a passing assumption), then restored the fix and confirmed it passes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Addressing the round-22 finding, fixed in 81f3c44:
Correct. 🤖 Generated with Claude Code |
…677) Phase 2 of the hub-docs-to-Skills conversion (Phase 0/1 shipped in #676). ## What this adds - **`pr-review-conduct`** — the merge gate, expected review loop, the five finding-outcomes, triage, and suppressed-finding answering rules, extracted from `GOVERNANCE.md` "PR Review Etiquette". - **`copilot-instructions-keeper`** — new content (not derived from existing prose) for keeping a downstream repo's `.github/copilot-instructions.md` in sync with the hub canonical without losing that repo's own "Disproved Claims" ledger entries, which the file's own rules say are never carried between repos. ## What changed in existing files - `GOVERNANCE.md` "PR Review Etiquette" shrinks from the full contract text to a two-paragraph pointer at the `pr-review-conduct` skill, per this project's standing decision that a skill becomes the sole canonical content for its topic once one exists (see #676's plan doc). - `.github/copilot-instructions.md` is **untouched** — the Copilot review bot reads it directly and cannot read a Skill, so its runbook content stays fully intact by design. - `AGENTS.md` "Where the Rules Live" table and closing paragraph updated to route to both new skills. - `.claude-plugin/fleet-skills/` regenerated via `scripts/build_dist.py` to carry the two new skills alongside `carried-instruction-file-guard` from Phase 1. ## Verification performed - `scripts/prose_lint.py`, CRLF byte-integrity, `editorconfig-checker` (docker), `spec/validate.py`, `scripts/test_build_dist.py` + `scripts/test_skills_install.py` (46 tests) all clean. - `scripts/build_dist.py --check` confirms the generated plugin is current relative to `.agents/skills/`. - Live-triggered both skills via `claude --plugin-dir .claude-plugin/fleet-skills -p "..."` against realistic prompts ("it's all green, go ahead and merge" for `pr-review-conduct`; "just a routine copy-over, right?" for `copilot-instructions-keeper`) — both fired correctly and produced accurate, grounded answers without the rule being restated manually. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Package Comment and Doc Style as a Skill Phase 3 of the hub-docs-to-Skills conversion (Phase 0/1 shipped in #676, Phase 2 in #677). ## What this adds - comment-and-doc-style, extracted from CODESTYLE.md's General section (Tooling Names and Casing, Markdown and Spelling) plus GOVERNANCE.md's Documentation Style Conventions and Pull Request Title and Commit Message Conventions sections: naming tools in prose, Markdown linting and spelling, Markdown formatting, comment philosophy, the ASCII character-set tiers, line endings, the carried-file coordination reference ban, and PR title/commit message conventions. - Three references/ files for progressive disclosure: line-endings.md (the full CRLF/LF policy), carried-doc-references.md (the full coordination-reference rule and its two exceptions), and markdown-links.md (the full reference-style-link convention). ## What changed in existing files - CODESTYLE.md's General section: Tooling Names and Casing and Markdown and Spelling shrink to pointers at the skill. Clean-Compile Verification and Analyzer Diagnostics and Suppressions are untouched, they are build-gate topics, not comment or doc style. - GOVERNANCE.md's Pull Request Title and Commit Message Conventions and Documentation Style Conventions sections shrink to pointers, per this project's standing decision that a skill becomes the sole canonical content for its topic once one exists. The Comments, Character Set, and Line Endings headings stay in place (spec/files.json and spec/project-types.json anchor to them by name), their content is now a one-line pointer each. - AGENTS.md's Where the Rules Live table routes both rows to the new skill. - .claude-plugin/fleet-skills/ regenerated via scripts/build_dist.py to carry the new skill alongside the three from Phases 1 and 2. ## Verification performed - scripts/prose_lint.py, CRLF byte-integrity (all touched files remain 100% CRLF), editorconfig-checker (docker), spec/validate.py, markdownlint-cli2, scripts/test_build_dist.py + scripts/test_skills_install.py (46 tests) all clean. - scripts/build_dist.py --check confirms the generated plugin is current relative to .agents/skills/. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Phase 4 of the hub-docs-to-Skills conversion (see #676, #677, #678 for Phases 0-3). ## What this adds - **`resync-a-repo`** (hub-only): packages `RESYNC.md` section 3's apply order for a hub-context session driving a named repo's resync. The `carried-instruction-file-guard` skill's distinctive-phrase probe is now a mandatory, non-skippable step before any verbatim re-vendor of an instruction file, the direct structural fix for the AGENTS.md-overwrite bug class rather than a rewording of existing advisory language. - **`fleet-conformance-check`** (fleet-wide, `appliesTo: "*"`, new content): the downstream-facing counterpart. Runs from inside a repo's own session with no hub checkout and no named target other than the repo itself. Confirms the local Skills install is current, confirms `AGENTS.md`'s pointer text matches the hub, self-applies what is safe (re-running the installer), and escalates anything ambiguous (a genuine local addition, a settings/workflow drift) to a hub-driven `resync-a-repo` run rather than touching it. ## Supporting changes - `RESYNC.md` section 3 step 1 gains the mandatory probe requirement and the `AGENTS.md` skill-dependency pointer as one more verbatim-carried unit in the same step. - `AGENTS.md`'s "Where the Rules Live" table and pointer paragraph route to both new skills. - `.claude-plugin/fleet-skills/` rebuilt via `build_dist.py` (now 6 skills). ## Verification - `python3 -m unittest discover -s scripts -p "test_*.py"`: 628 tests, all pass. - `python3 scripts/prose_lint.py` on all touched files: clean. - `python3 spec/validate.py`: clean. - `docker run ... mstruebing/editorconfig-checker:latest`: clean. - CRLF preserved on all touched Markdown. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Phase 5 of the hub-docs-to-Skills conversion (Phase 0/1 shipped in #676, Phase 2 in #677, Phase 3 in #678, Phase 4 in #679). ## What this adds - `dotnet-codestyle` (appliesTo csharp), `python-codestyle` (appliesTo python), and `shell-codestyle` (appliesTo *, since a shell script can appear in any repo), extracted from CODESTYLE.md's .NET, Python, and Shell sections respectively: build/analyzer requirements, language and naming conventions, logging and testing conventions, the Python build-versus-lint-only profile split and toolchain, and the shell `set -Eeuo pipefail` / pipefail-early-reader / shellcheck rules. - This is the first phase gating a skill by `appliesTo` rather than fleet-wide `*`, expressed through each skill's own trigger wording (file extensions and project shape) rather than a mechanical field, since SKILL.md frontmatter carries no such field. ## What changed in existing files - CODESTYLE.md's .NET, Python, and Shell sections shrink to pointers, per this project's standing decision that a skill becomes the sole canonical content for its topic once one exists. No spec file or test references any subheading inside these three sections, so each collapses fully rather than keeping empty subheadings. Six now-orphaned link reference definitions (MD053) are removed with them. - CODESTYLE.md's and AGENTS.md's own descriptions of the file's structure are corrected to mention the pre-existing Shell section, which neither previously named. - `.claude-plugin/fleet-skills/` regenerated via `scripts/build_dist.py` to carry the three new skills alongside the six from Phases 1-4. ## Dropped - `docker-codestyle` (Phase 5b): a content-discovery pass found no dedicated Docker style section in CODESTYLE.md, only scattered linter-invocation mentions in OPERATIONS.md and release mechanics in GOVERNANCE.md, neither of which is Docker coding style. Not authored, per the plan's own don't-invent-content instruction. ## Verification performed - `scripts/prose_lint.py`, CRLF byte-integrity (all touched/new files 100% CRLF), `editorconfig-checker` (docker), `markdownlint-cli2` (docker, 72 files, 0 issues), `spec/validate.py`, `scripts/test_prose_lint.py` (223 tests), `scripts/test_build_dist.py` (19 tests), and `scripts/test_skills_install.py` all clean. - `scripts/build_dist.py --check` confirms the generated plugin is current relative to `.agents/skills/`. - Grepped `scripts/test_*.py` and `spec/*.json` for any coupling to the trimmed CODESTYLE.md subheadings before trimming: none found. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
… Skills (#681) ## Summary Phase 6 of the hub-docs-to-Skills conversion project (see #676-#680 for prior phases). Adds three fleet-wide Claude Code / opencode / Codex Skills, all `appliesTo: "*"`: - **`git-commit-conventions`**, derived from `GOVERNANCE.md` "Git and Commit Rules": staging vs. committing, commit-means-commit-and-push, signing verified not configured, identity verified not set, never force push, careful history rewrites, no destructive git commands. - **`operational-vs-release-workflow`**, derived from `GOVERNANCE.md` "Branching Model", "Release Model", and "Operational Repositories". One skill serves both workflow models: its own trigger description reads the registry's `workflowModel` field per repo rather than gating by repo type. Two `references/` files hold the deep CI-wiring detail (branch protection and promotion mechanics; release build and publish mechanics) via progressive disclosure. - **`upstream-contribution-workflow`** (new content, not derived from any `GOVERNANCE.md` section): the maintainer's own third-party-contribution workflow, a dirty work branch on his own fork for the actual work and review iteration, squashed once clean to a second branch that carries only the intended minimal history, that clean branch opened as the PR against the upstream repo, reviewer feedback applied to the dirty branch first and then re-squashed. Triggers independent of the target repo's own type or workflow model. `GOVERNANCE.md`'s four source sections are trimmed to short pointers, per the standing "skill becomes sole canonical content" decision. Their `## ` headings stay in place, since `spec/files.json` carries all four at `verbatim` fidelity by heading name, only the body content under each shrank. Checked before trimming: no `scripts/test_*.py` couples to any of the four sections' content or subheadings (the Phase 3 gotcha). `AGENTS.md`'s "Where the Rules Live" table gains "packaged as the `X` Skill" annotations on the four affected rows, and `upstream-contribution-workflow` is added to the explanatory paragraph below the table (new content goes there, not a new table row, per the Phase 2/4 precedent, since that table pairs a task with a real `GOVERNANCE.md` section name). ## Verification - `python3 scripts/build_dist.py`: regenerated `.claude-plugin/fleet-skills/` from 12 skills clean. - `python3 scripts/test_build_dist.py`, `python3 scripts/test_skills_install.py`: pass. - `python3 scripts/prose_lint.py . --diff HEAD`: 0 violations (after fixing one `dupword` false positive caused by awkward phrasing). - `python3 scripts/repo_gate.py --check eol`: 0 issues (also re-verified by the pre-commit hook). - `npx markdownlint-cli2` against every new/changed Markdown file: 0 issues. - Verified no `scripts/test_*.py` couples to any of the four trimmed `GOVERNANCE.md` sections' content before trimming. - Verified CRLF preserved on every touched file after every edit (byte-level check, not visual). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Why The fleet map's P1 phase closes the skills install model. A bootstrapped host ended with every tool and no fleet skills, a failed tool floor named no runnable remedy, a bare `host_gate.py` run silently skipped an enclosing repo's overlay, nothing stated the skills staleness cadence, and the register's G8 row described a CI gap that #676 had already closed before the register merged. ## What One commit per register row, each editing its row in `docs/fleet-map.md` per the maintenance rule: - **G8, recorded closed.** `validate-task.yml` has run `build_dist.py --check` since #676, which predates the register's merge, so the row's `Checked` claim was stale on arrival and the row now records the resolution and its provenance. - **G2, bare-run overlay warning.** `host_gate.py` warns when a bare run's working directory sits inside a repo carrying a `host-tools.json` overlay it did not read, naming the `--repo` re-run that counts it. An explicit `--repo` and `--no-local` stay silent. STANDUP section 0 states the residual case the warning cannot cover, a target that does not exist yet. - **G3, runnable remedy.** Each floored `spec/host-tools.json` entry carries a per-platform `remedy` command beside its `source`, printed as a `REMEDY:` line under a below-floor failure, with `host-setup/` paths resolved against the checkout so the command is runnable as printed. The schema and `spec/validate.py` require it on every hub floor, and `test_bootstrap.py` keeps the mapping total per platform (git-restore-mtime/windows is the recorded not-applicable exception) and pointing at tools the installers actually manage. - **G1, skills in the cold start.** The bootstrap host mode ends with an `install-skills` step on both platforms, a `--skills` action runs it alone, and the report mode reads the stamp beside the other status lines. The loaders hand their resolved commit to the installer via `SKILLS_SOURCE_COMMIT`, since a tarball tree has no `.git`, so the stamp stays checkable and `--report` can exit zero on a fresh host. `docs/host-setup.md` gains the "Fleet Skills Install" section and verify lines, and `README.md` "Using This Repo" names four deployed things. The `claude` CLI stays out of the tool catalog: the installer degrades without it, lands the overlay half, and records the partial install. The new drivers keep the loader single-path and no-Python invariants intact, and the two recorded exceptions (Python last-in-line, independent fetchability) are stated in `host-setup/README.md`. Cross-links #671 and #673, which touch the same scripts and stay open on their own tracks. - **G6, staleness cadence.** Stated at both trigger surfaces: `docs/host-setup.md` and the `fleet-conformance-check` skill's new "Refresh cadence" section (plugin regenerated via `build_dist.py`), routing the restated-rule symptom to `skills_install.py --report`. ## Verification Every `validate-task.yml` gate run locally and green: the ten script and spec self-tests, including new cases for the warning, the remedy mapping, and the handed-in commit, plus `repo_gate.py`, `prose_lint.py` with CI's check list, `spec/validate.py`, `build_dist.py --check`, markdownlint, cspell on README and HISTORY, shellcheck and PSScriptAnalyzer via the same pinned containers CI uses, and all seven fleet-map mermaid blocks parse.
…Python CI Gates (#718) Thirty-one squashes, `56f4d7d..d54862a`. 115 files, +20436/-5298. **Merge with a merge commit, never a squash, and never with `--delete-branch`.** This pull request's head is `develop` itself. ## What lands **Fleet Skills.** The `.agents/skills/` source tree, the generated `.claude-plugin/` distribution, `scripts/build_dist.py` with its `--check` gate, and `scripts/skills_install.py` with its host stamp (#676). Packaged as skills on top of the scaffold: PR review conduct and Copilot instructions upkeep (#677), comment and doc style (#678), resync-a-repo and fleet-conformance-check (#679), the per-language codestyles (#680), git commit conventions and operational vs release workflow (#681), stand up a repo (#683), and repo-worktree (#717). Coverage gaps closed in three passes (#690, #691, #692) plus the P4 sentence-length opt-in (#697). **Host setup.** The Windows host-setup tooling and its PowerShell gate (#674), the Windows bootstrap loader (#682), Docker install and upgrade on Linux and Windows with a version floor (#701, #705), a `uv` floor in `spec/host-tools.json` (#698), self-healing of a shadowing `uv`, `jq`, or `git-restore-mtime` copy (#689), node's real winget package id (#696), and a README for the Linux host-setup nuances (#710). **Python and CI.** Python tooling in CI with the script tests moved to `scripts/tests` (#704), `ruff format` adopted and gated (#709), and the PSScriptAnalyzer claim conditioned on repos that carry `.ps1` files (#686). **Conduct rules.** Triage-order and scope guardrails in pr-review-conduct (#684), `pr_review.py wait` requesting a review rather than only polling for one (#685), a tech-agnostic signed-commit verification (#708), execution rather than analogy to verify platform-specific code (#715), and a unique worktree for every task (#717). **Docs.** The fleet map and gap register with peer messaging declared (#687), mermaid flow diagrams in the kept-authority docs (#702), and the map pointed at the shipped diagrams and current tooling (#703). ## Issues this promotion closes Each landed on `develop` on its own pull request. The keyword fires only on a merge into `main`, so it sits here rather than on the feature pull requests. Closes #700 Closes #707 Closes #711 Closes #712 Closes #714 Closes #688 #699 stays open on purpose: #717 shipped the layout convention and the skill, and the physical migration of existing checkouts is still tracked there. ## Review record Every squash closed its own Copilot loop on its own pull request before merging to `develop`. This promotion carries no new content of its own, so its review is the merged tree as a whole. ## Consequence worth stating The `GOVERNANCE.md` and `AGENTS.md` sections these squashes changed become the canonical the moment this reaches `main`, and every carrying repository reads as drifted from that point until it resyncs. That is the ordinary consequence of a canonical moving rather than a defect. The Skills installer added here is also how a machine picks the new skills up, so a session that keeps restating a rule already packaged as a skill is the signal to run it.
What
Phase 0 and Phase 1 of a multi-phase plan to convert the hub's instruction-heavy docs (comment
style, PR-review conduct, resync safety) into Claude Code / opencode / Codex Skills, so the rules
surface automatically instead of needing to be re-read every session. Opening now, early, to get a
read on the foundation (the distribution mechanism, the installer) before building the remaining
~6 phases on top of it. Expect further commits landing on this same PR as later phases ship.
Phase 0 (scaffolding, no skill content yet):
.agents/skills/— canonical, hand-authored home for fleet skills. Codex and opencode read thispath natively, zero config.
scripts/build_dist.py— generates a Claude-plugin-compatible copy at.claude-plugin/fleet-skills/(Claude Code does not scan.agents/skills/directly), with--checkfor CI staleness gating.scripts/skills_install.py(+skills_install.sh/.ps1thin wrappers, mirroringhost-setup/agent-safety/install.py's shape) — materializes~/.agents/skillsfor Codex/opencodeand registers the marketplace with Claude Code via the real
claude plugin marketplace add/installCLI, with--reportfor staleness checks..claude-plugin/marketplace.json— the Claude Code entry point.AGENTS.mdpointer (in "Where the Rules Live", verbatim/carried) naming the dependency.Phase 1:
carried-instruction-file-guardskill. Encodesspec/section-model.md's"Migrating a repo onto the split" procedure (the distinctive-phrase probe) as an active pre-flight
check before any
AGENTS.md/GOVERNANCE.md/CODESTYLE.md/WORKFLOW.mdoverwrite, rather thanthe prose-only advisory it was before. This is the highest-leverage skill in the plan: it directly
targets the bug class that once silently deleted a downstream repo's local
AGENTS.mdadditionsduring a resync.
Why nested under
.claude-plugin/instead of a top-leveldist/First draft generated output at
dist/claude/, which collided with this repo's existingPython-build-artifact
dist/gitignore entry. Moved to.claude-plugin/fleet-skills/instead,avoiding the name clash entirely rather than punching a hole in a blanket ignore.
Verification
AGENTS_HOME, confirmed viaclaude plugin marketplace list/plugin listthat registration and install actually worked, then cleaned up.claude plugin validatepasses clean on both manifests.repo_gate.py(eol,eol-coverage),prose_lint.py(diff),spec/validate.pyall clean.claude --plugin-dir, prompted asession to blindly copy a canonical
AGENTS.mdover a scratch repo'sAGENTS.mdcarrying alocal addition. It triggered, identified the local section, refused the overwrite, and proposed
relocating the content before carrying the rest — the exact procedure it specifies.
Also logs a
TODO.mdcluster (unrelated to this PR's diff otherwise) for defaulting.pyto LFfleet-wide, found while pinning the two new scripts individually.
🤖 Generated with Claude Code