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
46 changes: 46 additions & 0 deletions .agents/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
1 change: 1 addition & 0 deletions .claude-plugin/fleet-skills/.claude-plugin/plugin.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
"name": "ptr727"
},
"skills": [
"./skills/add-host-tool",
"./skills/agent-conduct",
"./skills/audit-a-repo",
"./skills/carried-instruction-file-guard",
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/fleet-skills/.source-digest
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
cf555123c5d2982d
0209e62c88c2a759
46 changes: 46 additions & 0 deletions .claude-plugin/fleet-skills/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
46 changes: 46 additions & 0 deletions .github/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -101,3 +101,5 @@ Every rule below is a level-two section of [`GOVERNANCE.md`](./GOVERNANCE.md). R
A row above with no Skill annotation is doc-only by decision, not by omission. A Skill surfaces rules at a trigger moment, and each unannotated section either binds always or carries no moment narrower than reading it: `Foundational Principles` is rationale read once rather than a procedure, `Repository Boundaries and Write Safety` and `Representative Data in Agent-Authored Text` are always-on law that must bind even when no Skill fires (the `gh-write-guard` hook and the host-wide instruction blocks the agent-safety installer maintains are their enforcement layer, and the one moment in the boundaries section narrow enough to surface, isolating into a worktree at task start, gets the `repo-worktree` Skill on top of that law rather than instead of it), and `Hub-Hosted Tooling`, `Supported Development Platforms`, `Devcontainer`, `Editor and Tasks`, `Repository Details`, and `Repository Layout` are short reference sections a task reads at the moment it touches their subject, each already routed to by the procedures and Skills that need it.

Some of the rules above are also packaged as Claude Code / opencode / Codex Skills, hand-authored at `.agents/skills/` in the hub (not a repo-relative link here, since that path is hub-local and not carried into every fleet repo), so they surface automatically instead of needing to be re-read every session. `scripts/` is hub-hosted and reached rather than carried, per "Hub-Hosted Tooling", so run the installer from a hub checkout: `python3 scripts/skills_install.py` (or the `.sh`/`.ps1` wrapper) once per machine, from `github.com/ptr727/ProjectTemplate`, installs them for every repo touched from that machine. `python3 scripts/skills_install.py --report`, also from a hub checkout, says whether this machine is current. A rule that keeps needing to be restated is a sign the install is missing or stale, not that the rule does not exist. Keeping a repo's own carried `.github/copilot-instructions.md` in sync with the hub, without losing that repo's own "Disproved Claims" ledger entries in the process, is `copilot-instructions-keeper`, a skill about maintaining that file rather than a rule extracted from it, since the file itself is read directly by the Copilot bot and stays fully intact everywhere it is carried. Checking, from inside this repo's own session with no operator watching, whether this repo and this machine are actually current against the hub is `fleet-conformance-check`, new content rather than a rule extracted from a section, the counterpart to `resync-a-repo` that needs no standing hub checkout or named target beyond the repo the session is already in, even though its own check fetches a hub checkout to reach `scripts/skills_install.py`. Opening a pull request against a repository outside this fleet, one the maintainer does not control, follows a different workflow entirely, new content rather than a rule extracted from a section, packaged as `upstream-contribution-workflow` and independent of the target repo's own type or workflow model. Isolating a task into its own worktree before its first file edit, with the base-branch choice, the layout convention, and the cleanup mechanics, is `repo-worktree`, the task-start surface of the `Repository Boundaries and Write Safety` law, which keeps the rule. Creating, changing, or retiring one of these skills is itself packaged as `skill-lifecycle`, hub-context only, since `.agents/skills/` exists only in the hub and the generated plugin tree is never hand-edited.

Adding or changing a managed host tool is packaged as `add-host-tool`. It keeps the cross-platform contract, installer, documentation, test, and native-verification surfaces together.
3 changes: 3 additions & 0 deletions docs/host-setup.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ This section is the **contract**: which tools a host needs and which repo proced
| `gh` | the PR and review loop, `gh api` queries, `repo-config/configure.sh` | `gh --version` | **2.47.0**, measured |
| Python 3 | `scripts/` and `spec/` (standard library only, no packages to install) | `python3 --version`, or `py -3 --version` on native Windows | **3.13**, target |
| `jq` | the ruleset normalizer in `repo-config/configure.sh`, the ruleset diff in [`AUDIT.md`][audit] section 6, and payload regeneration | `jq --version` | **1.7**, target |
| Ripgrep | repository searches and file discovery by coding agents | `rg --version` | **13.0.0**, target |
| `docker` | the four linters, which run as pinned images rather than local installs | `docker --version` | **29.6.2**, target |
| `uv` / `uvx` | coverage runs, and the Python toolchain (`ruff`, `pyright` or `mypy`) in a Python repo | `uv --version` | **0.12.2**, target |

Expand All@@ -45,6 +46,8 @@ Presence is the weaker half of this contract. Both host defects this fleet has a

**The rest of the table takes the distribution's package, and one more does not.** `git` and the Python interpreter come from the distribution, because each keeps up well enough that a second source buys nothing and costs a repository to trust. `jq` is the same on a current Debian or Ubuntu, which carries a version at or above the floor, and the upstream release binary is the answer only where it does not. `uv` is published by its authors as a release archive and packaged by neither distribution, so upstream is the only source there is. Where a repository needs `node`, the distribution's package trails upstream by whole release lines, so it comes from the NodeSource repository on the line upstream currently carries as long term support. Where a repository needs `dotnet`, the distribution's feed is preferred where it carries an SDK and Microsoft's feed is the fallback, because mixing the two is what breaks a host rather than either one alone, and Microsoft's carries `amd64` only. Where a repository needs `pwsh`, that same feed is the only source, since no distribution packages it, and the tooling installs it as the `powershell` tool under `--optional`, with `pwsh` the command that tool provides.

Ripgrep also comes from the distribution, where every supported current release meets its floor. It provides `rg` search and `rg --files` discovery for coding agents.

Neither `node`, `dotnet`, nor `pwsh` is in the table above, deliberately: they serve the repositories that need them rather than the fleet contract, and a repository needing one declares it in a `host-tools.json` of its own, which [`scripts/host_gate.py`][host-gate] merges over this one. The merge tightens only, so a repository may raise a floor or add one and may not lower or remove one.

**A host being stood up needs no Python.** The tooling under [`host-setup/`][host-setup-dir] is shell and PowerShell, deliberately, because requiring an interpreter to upgrade a package or install a tool would make the first step of standing a host up depend on the thing that step exists to provide. The Python floor above is a development requirement, meaning [`scripts/`][scripts-dir] and [`spec/`][spec-dir], and a host that only runs services never has to meet it. `bootstrap.sh` needs `curl` and `tar`, both of which a base install carries or can install without a network tool of its own. `bootstrap.ps1` needs only `tar.exe`, which has shipped with Windows since 1803, and installs its one further dependency, PowerShell 7, itself through `winget`. The one exception is the skills step at the end of a stand-up, which drives the Python installer in [`scripts/`][scripts-dir], and it runs last for exactly that reason: `install-tools` has provided the interpreter by then, and run alone on a host without one it stops and names the tools step as its prerequisite.
Expand Down
2 changes: 1 addition & 1 deletion host-setup/linux/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@ Each script is LF with a shebang, and its executable bit is tracked in git. A fr

The Windows registry has one source because `winget` tracks upstream. Here the distro package trails upstream on `gh`, on `node`, and on `uv`, so a tool comes from whichever source keeps up:

- **The distro**, for `git` and `python`, where apt's own package is current enough.
- **The distro**, for `git`, `python`, and Ripgrep, where apt's own package is current enough.
- **An upstream apt repository**, for `gh`, `node`, `docker`, and `powershell`, and for `dotnet` as a fallback, where upstream publishes one.
- **A released binary into `/usr/local/bin`**, for `jq`, `uv`, and `git-restore-mtime`, where upstream publishes no repository.

Expand Down
48 changes: 45 additions & 3 deletions host-setup/linux/install-tools.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,7 @@ readonly SUDO_TIMESTAMP_TIMEOUT=60

# Managed tools, in dependency order: node asks jq to read the upstream release index.
# The one optional member, powershell, joins the default selection only under --optional.
readonly TOOLS=(git gh jq git-restore-mtime node python uv docker dotnet powershell)
readonly TOOLS=(git gh jq git-restore-mtime node python ripgrep uv docker dotnet powershell)

# Package sets.
# The default set is what a tool needs to be useful, and the optional set is what is useful often enough to name but not always wanted, installed only with --optional.
Expand DownExpand Up@@ -508,6 +508,37 @@ python_install() {
apt_install "${packages[@]}"
}

# --- ripgrep ---

ripgrep_source() { printf 'distro'; }
ripgrep_version() { apt_installed_version ripgrep; }
ripgrep_target() { apt_candidate_version ripgrep; }

ripgrep_download_path() {
local resolved
resolved=$(type -P rg 2> /dev/null || true)
[[ $resolved == /* ]] || return 0
dpkg-query -S "$resolved" > /dev/null 2>&1 || printf '%s' "$resolved"
}

ripgrep_remove_download() {
local resolved
while true; do
resolved=$(ripgrep_download_path)
[[ -n $resolved ]] || break
log "ripgrep: removing downloaded copy at $resolved before the distro package install"
confirm " Remove $resolved?" || die "Declined, downloaded Ripgrep left as it is"
run_root rm -f "$resolved"
[[ $DRY_RUN == true ]] && break
hash -r
done
}

ripgrep_install() {
ripgrep_remove_download
apt_install ripgrep
}

# --- uv ---

uv_source() { printf 'astral-sh/uv'; }
Expand DownExpand Up@@ -870,6 +901,13 @@ tool_note() {
note "python" "optional set not selected: ${PYTHON_OPTIONAL[*]}"
fi
;;
ripgrep)
local resolved
resolved=$(ripgrep_download_path)
if [[ -n $resolved ]]; then
note "ripgrep" "$resolved is an unowned downloaded copy and an install or upgrade removes it before apt installs Ripgrep"
fi
;;
dotnet)
local -a sdks=()
readarray -t sdks < <(dotnet_sdk_packages)
Expand DownExpand Up@@ -950,13 +988,17 @@ report() {
done
}

# Remove a copy of a managed tool found earlier on PATH than $BIN_DIR, so the managed copy is what PATH resolves to afterward.
# Only jq, uv, and git-restore-mtime install as loose binaries outside apt, and uv's companion uvx is unshadowed alongside it.
# Remove a copy of a managed tool found earlier on PATH than its managed destination, so the managed copy is what PATH resolves to afterward.
# Ripgrep migrates an unowned download to apt, while jq, uv, and git-restore-mtime install as loose binaries in $BIN_DIR.
tool_unshadow() {
local tool="$1"
local -a names=()
case "$tool" in
jq | git-restore-mtime) names=("$tool") ;;
ripgrep)
ripgrep_remove_download
return 0
;;
uv) names=(uv uvx) ;;
*) return 0 ;;
esac
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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
46 changes: 46 additions & 0 deletions .agents/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
1 change: 1 addition & 0 deletions .claude-plugin/fleet-skills/.claude-plugin/plugin.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
"name": "ptr727"
},
"skills": [
"./skills/add-host-tool",
"./skills/agent-conduct",
"./skills/audit-a-repo",
"./skills/carried-instruction-file-guard",
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/fleet-skills/.source-digest
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
cf555123c5d2982d
0209e62c88c2a759
46 changes: 46 additions & 0 deletions .claude-plugin/fleet-skills/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
46 changes: 46 additions & 0 deletions .github/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -101,3 +101,5 @@ Every rule below is a level-two section of [`GOVERNANCE.md`](./GOVERNANCE.md). R
A row above with no Skill annotation is doc-only by decision, not by omission. A Skill surfaces rules at a trigger moment, and each unannotated section either binds always or carries no moment narrower than reading it: `Foundational Principles` is rationale read once rather than a procedure, `Repository Boundaries and Write Safety` and `Representative Data in Agent-Authored Text` are always-on law that must bind even when no Skill fires (the `gh-write-guard` hook and the host-wide instruction blocks the agent-safety installer maintains are their enforcement layer, and the one moment in the boundaries section narrow enough to surface, isolating into a worktree at task start, gets the `repo-worktree` Skill on top of that law rather than instead of it), and `Hub-Hosted Tooling`, `Supported Development Platforms`, `Devcontainer`, `Editor and Tasks`, `Repository Details`, and `Repository Layout` are short reference sections a task reads at the moment it touches their subject, each already routed to by the procedures and Skills that need it.

Some of the rules above are also packaged as Claude Code / opencode / Codex Skills, hand-authored at `.agents/skills/` in the hub (not a repo-relative link here, since that path is hub-local and not carried into every fleet repo), so they surface automatically instead of needing to be re-read every session. `scripts/` is hub-hosted and reached rather than carried, per "Hub-Hosted Tooling", so run the installer from a hub checkout: `python3 scripts/skills_install.py` (or the `.sh`/`.ps1` wrapper) once per machine, from `github.com/ptr727/ProjectTemplate`, installs them for every repo touched from that machine. `python3 scripts/skills_install.py --report`, also from a hub checkout, says whether this machine is current. A rule that keeps needing to be restated is a sign the install is missing or stale, not that the rule does not exist. Keeping a repo's own carried `.github/copilot-instructions.md` in sync with the hub, without losing that repo's own "Disproved Claims" ledger entries in the process, is `copilot-instructions-keeper`, a skill about maintaining that file rather than a rule extracted from it, since the file itself is read directly by the Copilot bot and stays fully intact everywhere it is carried. Checking, from inside this repo's own session with no operator watching, whether this repo and this machine are actually current against the hub is `fleet-conformance-check`, new content rather than a rule extracted from a section, the counterpart to `resync-a-repo` that needs no standing hub checkout or named target beyond the repo the session is already in, even though its own check fetches a hub checkout to reach `scripts/skills_install.py`. Opening a pull request against a repository outside this fleet, one the maintainer does not control, follows a different workflow entirely, new content rather than a rule extracted from a section, packaged as `upstream-contribution-workflow` and independent of the target repo's own type or workflow model. Isolating a task into its own worktree before its first file edit, with the base-branch choice, the layout convention, and the cleanup mechanics, is `repo-worktree`, the task-start surface of the `Repository Boundaries and Write Safety` law, which keeps the rule. Creating, changing, or retiring one of these skills is itself packaged as `skill-lifecycle`, hub-context only, since `.agents/skills/` exists only in the hub and the generated plugin tree is never hand-edited.

Adding or changing a managed host tool is packaged as `add-host-tool`. It keeps the cross-platform contract, installer, documentation, test, and native-verification surfaces together.
3 changes: 3 additions & 0 deletions docs/host-setup.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ This section is the **contract**: which tools a host needs and which repo proced
| `gh` | the PR and review loop, `gh api` queries, `repo-config/configure.sh` | `gh --version` | **2.47.0**, measured |
| Python 3 | `scripts/` and `spec/` (standard library only, no packages to install) | `python3 --version`, or `py -3 --version` on native Windows | **3.13**, target |
| `jq` | the ruleset normalizer in `repo-config/configure.sh`, the ruleset diff in [`AUDIT.md`][audit] section 6, and payload regeneration | `jq --version` | **1.7**, target |
| Ripgrep | repository searches and file discovery by coding agents | `rg --version` | **13.0.0**, target |
| `docker` | the four linters, which run as pinned images rather than local installs | `docker --version` | **29.6.2**, target |
| `uv` / `uvx` | coverage runs, and the Python toolchain (`ruff`, `pyright` or `mypy`) in a Python repo | `uv --version` | **0.12.2**, target |

Expand All@@ -45,6 +46,8 @@ Presence is the weaker half of this contract. Both host defects this fleet has a

**The rest of the table takes the distribution's package, and one more does not.** `git` and the Python interpreter come from the distribution, because each keeps up well enough that a second source buys nothing and costs a repository to trust. `jq` is the same on a current Debian or Ubuntu, which carries a version at or above the floor, and the upstream release binary is the answer only where it does not. `uv` is published by its authors as a release archive and packaged by neither distribution, so upstream is the only source there is. Where a repository needs `node`, the distribution's package trails upstream by whole release lines, so it comes from the NodeSource repository on the line upstream currently carries as long term support. Where a repository needs `dotnet`, the distribution's feed is preferred where it carries an SDK and Microsoft's feed is the fallback, because mixing the two is what breaks a host rather than either one alone, and Microsoft's carries `amd64` only. Where a repository needs `pwsh`, that same feed is the only source, since no distribution packages it, and the tooling installs it as the `powershell` tool under `--optional`, with `pwsh` the command that tool provides.

Ripgrep also comes from the distribution, where every supported current release meets its floor. It provides `rg` search and `rg --files` discovery for coding agents.

Neither `node`, `dotnet`, nor `pwsh` is in the table above, deliberately: they serve the repositories that need them rather than the fleet contract, and a repository needing one declares it in a `host-tools.json` of its own, which [`scripts/host_gate.py`][host-gate] merges over this one. The merge tightens only, so a repository may raise a floor or add one and may not lower or remove one.

**A host being stood up needs no Python.** The tooling under [`host-setup/`][host-setup-dir] is shell and PowerShell, deliberately, because requiring an interpreter to upgrade a package or install a tool would make the first step of standing a host up depend on the thing that step exists to provide. The Python floor above is a development requirement, meaning [`scripts/`][scripts-dir] and [`spec/`][spec-dir], and a host that only runs services never has to meet it. `bootstrap.sh` needs `curl` and `tar`, both of which a base install carries or can install without a network tool of its own. `bootstrap.ps1` needs only `tar.exe`, which has shipped with Windows since 1803, and installs its one further dependency, PowerShell 7, itself through `winget`. The one exception is the skills step at the end of a stand-up, which drives the Python installer in [`scripts/`][scripts-dir], and it runs last for exactly that reason: `install-tools` has provided the interpreter by then, and run alone on a host without one it stops and names the tools step as its prerequisite.
Expand Down
2 changes: 1 addition & 1 deletion host-setup/linux/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@ Each script is LF with a shebang, and its executable bit is tracked in git. A fr

The Windows registry has one source because `winget` tracks upstream. Here the distro package trails upstream on `gh`, on `node`, and on `uv`, so a tool comes from whichever source keeps up:

- **The distro**, for `git` and `python`, where apt's own package is current enough.
- **The distro**, for `git`, `python`, and Ripgrep, where apt's own package is current enough.
- **An upstream apt repository**, for `gh`, `node`, `docker`, and `powershell`, and for `dotnet` as a fallback, where upstream publishes one.
- **A released binary into `/usr/local/bin`**, for `jq`, `uv`, and `git-restore-mtime`, where upstream publishes no repository.

Expand Down
48 changes: 45 additions & 3 deletions host-setup/linux/install-tools.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,7 @@ readonly SUDO_TIMESTAMP_TIMEOUT=60

# Managed tools, in dependency order: node asks jq to read the upstream release index.
# The one optional member, powershell, joins the default selection only under --optional.
readonly TOOLS=(git gh jq git-restore-mtime node python uv docker dotnet powershell)
readonly TOOLS=(git gh jq git-restore-mtime node python ripgrep uv docker dotnet powershell)

# Package sets.
# The default set is what a tool needs to be useful, and the optional set is what is useful often enough to name but not always wanted, installed only with --optional.
Expand DownExpand Up@@ -508,6 +508,37 @@ python_install() {
apt_install "${packages[@]}"
}

# --- ripgrep ---

ripgrep_source() { printf 'distro'; }
ripgrep_version() { apt_installed_version ripgrep; }
ripgrep_target() { apt_candidate_version ripgrep; }

ripgrep_download_path() {
local resolved
resolved=$(type -P rg 2> /dev/null || true)
[[ $resolved == /* ]] || return 0
dpkg-query -S "$resolved" > /dev/null 2>&1 || printf '%s' "$resolved"
}

ripgrep_remove_download() {
local resolved
while true; do
resolved=$(ripgrep_download_path)
[[ -n $resolved ]] || break
log "ripgrep: removing downloaded copy at $resolved before the distro package install"
confirm " Remove $resolved?" || die "Declined, downloaded Ripgrep left as it is"
run_root rm -f "$resolved"
[[ $DRY_RUN == true ]] && break
hash -r
done
}

ripgrep_install() {
ripgrep_remove_download
apt_install ripgrep
}

# --- uv ---

uv_source() { printf 'astral-sh/uv'; }
Expand DownExpand Up@@ -870,6 +901,13 @@ tool_note() {
note "python" "optional set not selected: ${PYTHON_OPTIONAL[*]}"
fi
;;
ripgrep)
local resolved
resolved=$(ripgrep_download_path)
if [[ -n $resolved ]]; then
note "ripgrep" "$resolved is an unowned downloaded copy and an install or upgrade removes it before apt installs Ripgrep"
fi
;;
dotnet)
local -a sdks=()
readarray -t sdks < <(dotnet_sdk_packages)
Expand DownExpand Up@@ -950,13 +988,17 @@ report() {
done
}

# Remove a copy of a managed tool found earlier on PATH than $BIN_DIR, so the managed copy is what PATH resolves to afterward.
# Only jq, uv, and git-restore-mtime install as loose binaries outside apt, and uv's companion uvx is unshadowed alongside it.
# Remove a copy of a managed tool found earlier on PATH than its managed destination, so the managed copy is what PATH resolves to afterward.
# Ripgrep migrates an unowned download to apt, while jq, uv, and git-restore-mtime install as loose binaries in $BIN_DIR.
tool_unshadow() {
local tool="$1"
local -a names=()
case "$tool" in
jq | git-restore-mtime) names=("$tool") ;;
ripgrep)
ripgrep_remove_download
return 0
;;
uv) names=(uv uvx) ;;
*) return 0 ;;
esac
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
46 changes: 46 additions & 0 deletions .agents/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
1 change: 1 addition & 0 deletions .claude-plugin/fleet-skills/.claude-plugin/plugin.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
"name": "ptr727"
},
"skills": [
"./skills/add-host-tool",
"./skills/agent-conduct",
"./skills/audit-a-repo",
"./skills/carried-instruction-file-guard",
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/fleet-skills/.source-digest
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
cf555123c5d2982d
0209e62c88c2a759
46 changes: 46 additions & 0 deletions .claude-plugin/fleet-skills/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
46 changes: 46 additions & 0 deletions .github/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -101,3 +101,5 @@ Every rule below is a level-two section of [`GOVERNANCE.md`](./GOVERNANCE.md). R
A row above with no Skill annotation is doc-only by decision, not by omission. A Skill surfaces rules at a trigger moment, and each unannotated section either binds always or carries no moment narrower than reading it: `Foundational Principles` is rationale read once rather than a procedure, `Repository Boundaries and Write Safety` and `Representative Data in Agent-Authored Text` are always-on law that must bind even when no Skill fires (the `gh-write-guard` hook and the host-wide instruction blocks the agent-safety installer maintains are their enforcement layer, and the one moment in the boundaries section narrow enough to surface, isolating into a worktree at task start, gets the `repo-worktree` Skill on top of that law rather than instead of it), and `Hub-Hosted Tooling`, `Supported Development Platforms`, `Devcontainer`, `Editor and Tasks`, `Repository Details`, and `Repository Layout` are short reference sections a task reads at the moment it touches their subject, each already routed to by the procedures and Skills that need it.

Some of the rules above are also packaged as Claude Code / opencode / Codex Skills, hand-authored at `.agents/skills/` in the hub (not a repo-relative link here, since that path is hub-local and not carried into every fleet repo), so they surface automatically instead of needing to be re-read every session. `scripts/` is hub-hosted and reached rather than carried, per "Hub-Hosted Tooling", so run the installer from a hub checkout: `python3 scripts/skills_install.py` (or the `.sh`/`.ps1` wrapper) once per machine, from `github.com/ptr727/ProjectTemplate`, installs them for every repo touched from that machine. `python3 scripts/skills_install.py --report`, also from a hub checkout, says whether this machine is current. A rule that keeps needing to be restated is a sign the install is missing or stale, not that the rule does not exist. Keeping a repo's own carried `.github/copilot-instructions.md` in sync with the hub, without losing that repo's own "Disproved Claims" ledger entries in the process, is `copilot-instructions-keeper`, a skill about maintaining that file rather than a rule extracted from it, since the file itself is read directly by the Copilot bot and stays fully intact everywhere it is carried. Checking, from inside this repo's own session with no operator watching, whether this repo and this machine are actually current against the hub is `fleet-conformance-check`, new content rather than a rule extracted from a section, the counterpart to `resync-a-repo` that needs no standing hub checkout or named target beyond the repo the session is already in, even though its own check fetches a hub checkout to reach `scripts/skills_install.py`. Opening a pull request against a repository outside this fleet, one the maintainer does not control, follows a different workflow entirely, new content rather than a rule extracted from a section, packaged as `upstream-contribution-workflow` and independent of the target repo's own type or workflow model. Isolating a task into its own worktree before its first file edit, with the base-branch choice, the layout convention, and the cleanup mechanics, is `repo-worktree`, the task-start surface of the `Repository Boundaries and Write Safety` law, which keeps the rule. Creating, changing, or retiring one of these skills is itself packaged as `skill-lifecycle`, hub-context only, since `.agents/skills/` exists only in the hub and the generated plugin tree is never hand-edited.

Adding or changing a managed host tool is packaged as `add-host-tool`. It keeps the cross-platform contract, installer, documentation, test, and native-verification surfaces together.
3 changes: 3 additions & 0 deletions docs/host-setup.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ This section is the **contract**: which tools a host needs and which repo proced
| `gh` | the PR and review loop, `gh api` queries, `repo-config/configure.sh` | `gh --version` | **2.47.0**, measured |
| Python 3 | `scripts/` and `spec/` (standard library only, no packages to install) | `python3 --version`, or `py -3 --version` on native Windows | **3.13**, target |
| `jq` | the ruleset normalizer in `repo-config/configure.sh`, the ruleset diff in [`AUDIT.md`][audit] section 6, and payload regeneration | `jq --version` | **1.7**, target |
| Ripgrep | repository searches and file discovery by coding agents | `rg --version` | **13.0.0**, target |
| `docker` | the four linters, which run as pinned images rather than local installs | `docker --version` | **29.6.2**, target |
| `uv` / `uvx` | coverage runs, and the Python toolchain (`ruff`, `pyright` or `mypy`) in a Python repo | `uv --version` | **0.12.2**, target |

Expand All@@ -45,6 +46,8 @@ Presence is the weaker half of this contract. Both host defects this fleet has a

**The rest of the table takes the distribution's package, and one more does not.** `git` and the Python interpreter come from the distribution, because each keeps up well enough that a second source buys nothing and costs a repository to trust. `jq` is the same on a current Debian or Ubuntu, which carries a version at or above the floor, and the upstream release binary is the answer only where it does not. `uv` is published by its authors as a release archive and packaged by neither distribution, so upstream is the only source there is. Where a repository needs `node`, the distribution's package trails upstream by whole release lines, so it comes from the NodeSource repository on the line upstream currently carries as long term support. Where a repository needs `dotnet`, the distribution's feed is preferred where it carries an SDK and Microsoft's feed is the fallback, because mixing the two is what breaks a host rather than either one alone, and Microsoft's carries `amd64` only. Where a repository needs `pwsh`, that same feed is the only source, since no distribution packages it, and the tooling installs it as the `powershell` tool under `--optional`, with `pwsh` the command that tool provides.

Ripgrep also comes from the distribution, where every supported current release meets its floor. It provides `rg` search and `rg --files` discovery for coding agents.

Neither `node`, `dotnet`, nor `pwsh` is in the table above, deliberately: they serve the repositories that need them rather than the fleet contract, and a repository needing one declares it in a `host-tools.json` of its own, which [`scripts/host_gate.py`][host-gate] merges over this one. The merge tightens only, so a repository may raise a floor or add one and may not lower or remove one.

**A host being stood up needs no Python.** The tooling under [`host-setup/`][host-setup-dir] is shell and PowerShell, deliberately, because requiring an interpreter to upgrade a package or install a tool would make the first step of standing a host up depend on the thing that step exists to provide. The Python floor above is a development requirement, meaning [`scripts/`][scripts-dir] and [`spec/`][spec-dir], and a host that only runs services never has to meet it. `bootstrap.sh` needs `curl` and `tar`, both of which a base install carries or can install without a network tool of its own. `bootstrap.ps1` needs only `tar.exe`, which has shipped with Windows since 1803, and installs its one further dependency, PowerShell 7, itself through `winget`. The one exception is the skills step at the end of a stand-up, which drives the Python installer in [`scripts/`][scripts-dir], and it runs last for exactly that reason: `install-tools` has provided the interpreter by then, and run alone on a host without one it stops and names the tools step as its prerequisite.
Expand Down
2 changes: 1 addition & 1 deletion host-setup/linux/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@ Each script is LF with a shebang, and its executable bit is tracked in git. A fr

The Windows registry has one source because `winget` tracks upstream. Here the distro package trails upstream on `gh`, on `node`, and on `uv`, so a tool comes from whichever source keeps up:

- **The distro**, for `git` and `python`, where apt's own package is current enough.
- **The distro**, for `git`, `python`, and Ripgrep, where apt's own package is current enough.
- **An upstream apt repository**, for `gh`, `node`, `docker`, and `powershell`, and for `dotnet` as a fallback, where upstream publishes one.
- **A released binary into `/usr/local/bin`**, for `jq`, `uv`, and `git-restore-mtime`, where upstream publishes no repository.

Expand Down
48 changes: 45 additions & 3 deletions host-setup/linux/install-tools.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,7 @@ readonly SUDO_TIMESTAMP_TIMEOUT=60

# Managed tools, in dependency order: node asks jq to read the upstream release index.
# The one optional member, powershell, joins the default selection only under --optional.
readonly TOOLS=(git gh jq git-restore-mtime node python uv docker dotnet powershell)
readonly TOOLS=(git gh jq git-restore-mtime node python ripgrep uv docker dotnet powershell)

# Package sets.
# The default set is what a tool needs to be useful, and the optional set is what is useful often enough to name but not always wanted, installed only with --optional.
Expand DownExpand Up@@ -508,6 +508,37 @@ python_install() {
apt_install "${packages[@]}"
}

# --- ripgrep ---

ripgrep_source() { printf 'distro'; }
ripgrep_version() { apt_installed_version ripgrep; }
ripgrep_target() { apt_candidate_version ripgrep; }

ripgrep_download_path() {
local resolved
resolved=$(type -P rg 2> /dev/null || true)
[[ $resolved == /* ]] || return 0
dpkg-query -S "$resolved" > /dev/null 2>&1 || printf '%s' "$resolved"
}

ripgrep_remove_download() {
local resolved
while true; do
resolved=$(ripgrep_download_path)
[[ -n $resolved ]] || break
log "ripgrep: removing downloaded copy at $resolved before the distro package install"
confirm " Remove $resolved?" || die "Declined, downloaded Ripgrep left as it is"
run_root rm -f "$resolved"
[[ $DRY_RUN == true ]] && break
hash -r
done
}

ripgrep_install() {
ripgrep_remove_download
apt_install ripgrep
}

# --- uv ---

uv_source() { printf 'astral-sh/uv'; }
Expand DownExpand Up@@ -870,6 +901,13 @@ tool_note() {
note "python" "optional set not selected: ${PYTHON_OPTIONAL[*]}"
fi
;;
ripgrep)
local resolved
resolved=$(ripgrep_download_path)
if [[ -n $resolved ]]; then
note "ripgrep" "$resolved is an unowned downloaded copy and an install or upgrade removes it before apt installs Ripgrep"
fi
;;
dotnet)
local -a sdks=()
readarray -t sdks < <(dotnet_sdk_packages)
Expand DownExpand Up@@ -950,13 +988,17 @@ report() {
done
}

# Remove a copy of a managed tool found earlier on PATH than $BIN_DIR, so the managed copy is what PATH resolves to afterward.
# Only jq, uv, and git-restore-mtime install as loose binaries outside apt, and uv's companion uvx is unshadowed alongside it.
# Remove a copy of a managed tool found earlier on PATH than its managed destination, so the managed copy is what PATH resolves to afterward.
# Ripgrep migrates an unowned download to apt, while jq, uv, and git-restore-mtime install as loose binaries in $BIN_DIR.
tool_unshadow() {
local tool="$1"
local -a names=()
case "$tool" in
jq | git-restore-mtime) names=("$tool") ;;
ripgrep)
ripgrep_remove_download
return 0
;;
uv) names=(uv uvx) ;;
*) return 0 ;;
esac
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
46 changes: 46 additions & 0 deletions .agents/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
1 change: 1 addition & 0 deletions .claude-plugin/fleet-skills/.claude-plugin/plugin.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
"name": "ptr727"
},
"skills": [
"./skills/add-host-tool",
"./skills/agent-conduct",
"./skills/audit-a-repo",
"./skills/carried-instruction-file-guard",
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/fleet-skills/.source-digest
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
cf555123c5d2982d
0209e62c88c2a759
46 changes: 46 additions & 0 deletions .claude-plugin/fleet-skills/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
46 changes: 46 additions & 0 deletions .github/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -101,3 +101,5 @@ Every rule below is a level-two section of [`GOVERNANCE.md`](./GOVERNANCE.md). R
A row above with no Skill annotation is doc-only by decision, not by omission. A Skill surfaces rules at a trigger moment, and each unannotated section either binds always or carries no moment narrower than reading it: `Foundational Principles` is rationale read once rather than a procedure, `Repository Boundaries and Write Safety` and `Representative Data in Agent-Authored Text` are always-on law that must bind even when no Skill fires (the `gh-write-guard` hook and the host-wide instruction blocks the agent-safety installer maintains are their enforcement layer, and the one moment in the boundaries section narrow enough to surface, isolating into a worktree at task start, gets the `repo-worktree` Skill on top of that law rather than instead of it), and `Hub-Hosted Tooling`, `Supported Development Platforms`, `Devcontainer`, `Editor and Tasks`, `Repository Details`, and `Repository Layout` are short reference sections a task reads at the moment it touches their subject, each already routed to by the procedures and Skills that need it.

Some of the rules above are also packaged as Claude Code / opencode / Codex Skills, hand-authored at `.agents/skills/` in the hub (not a repo-relative link here, since that path is hub-local and not carried into every fleet repo), so they surface automatically instead of needing to be re-read every session. `scripts/` is hub-hosted and reached rather than carried, per "Hub-Hosted Tooling", so run the installer from a hub checkout: `python3 scripts/skills_install.py` (or the `.sh`/`.ps1` wrapper) once per machine, from `github.com/ptr727/ProjectTemplate`, installs them for every repo touched from that machine. `python3 scripts/skills_install.py --report`, also from a hub checkout, says whether this machine is current. A rule that keeps needing to be restated is a sign the install is missing or stale, not that the rule does not exist. Keeping a repo's own carried `.github/copilot-instructions.md` in sync with the hub, without losing that repo's own "Disproved Claims" ledger entries in the process, is `copilot-instructions-keeper`, a skill about maintaining that file rather than a rule extracted from it, since the file itself is read directly by the Copilot bot and stays fully intact everywhere it is carried. Checking, from inside this repo's own session with no operator watching, whether this repo and this machine are actually current against the hub is `fleet-conformance-check`, new content rather than a rule extracted from a section, the counterpart to `resync-a-repo` that needs no standing hub checkout or named target beyond the repo the session is already in, even though its own check fetches a hub checkout to reach `scripts/skills_install.py`. Opening a pull request against a repository outside this fleet, one the maintainer does not control, follows a different workflow entirely, new content rather than a rule extracted from a section, packaged as `upstream-contribution-workflow` and independent of the target repo's own type or workflow model. Isolating a task into its own worktree before its first file edit, with the base-branch choice, the layout convention, and the cleanup mechanics, is `repo-worktree`, the task-start surface of the `Repository Boundaries and Write Safety` law, which keeps the rule. Creating, changing, or retiring one of these skills is itself packaged as `skill-lifecycle`, hub-context only, since `.agents/skills/` exists only in the hub and the generated plugin tree is never hand-edited.

Adding or changing a managed host tool is packaged as `add-host-tool`. It keeps the cross-platform contract, installer, documentation, test, and native-verification surfaces together.
3 changes: 3 additions & 0 deletions docs/host-setup.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ This section is the **contract**: which tools a host needs and which repo proced
| `gh` | the PR and review loop, `gh api` queries, `repo-config/configure.sh` | `gh --version` | **2.47.0**, measured |
| Python 3 | `scripts/` and `spec/` (standard library only, no packages to install) | `python3 --version`, or `py -3 --version` on native Windows | **3.13**, target |
| `jq` | the ruleset normalizer in `repo-config/configure.sh`, the ruleset diff in [`AUDIT.md`][audit] section 6, and payload regeneration | `jq --version` | **1.7**, target |
| Ripgrep | repository searches and file discovery by coding agents | `rg --version` | **13.0.0**, target |
| `docker` | the four linters, which run as pinned images rather than local installs | `docker --version` | **29.6.2**, target |
| `uv` / `uvx` | coverage runs, and the Python toolchain (`ruff`, `pyright` or `mypy`) in a Python repo | `uv --version` | **0.12.2**, target |

Expand All@@ -45,6 +46,8 @@ Presence is the weaker half of this contract. Both host defects this fleet has a

**The rest of the table takes the distribution's package, and one more does not.** `git` and the Python interpreter come from the distribution, because each keeps up well enough that a second source buys nothing and costs a repository to trust. `jq` is the same on a current Debian or Ubuntu, which carries a version at or above the floor, and the upstream release binary is the answer only where it does not. `uv` is published by its authors as a release archive and packaged by neither distribution, so upstream is the only source there is. Where a repository needs `node`, the distribution's package trails upstream by whole release lines, so it comes from the NodeSource repository on the line upstream currently carries as long term support. Where a repository needs `dotnet`, the distribution's feed is preferred where it carries an SDK and Microsoft's feed is the fallback, because mixing the two is what breaks a host rather than either one alone, and Microsoft's carries `amd64` only. Where a repository needs `pwsh`, that same feed is the only source, since no distribution packages it, and the tooling installs it as the `powershell` tool under `--optional`, with `pwsh` the command that tool provides.

Ripgrep also comes from the distribution, where every supported current release meets its floor. It provides `rg` search and `rg --files` discovery for coding agents.

Neither `node`, `dotnet`, nor `pwsh` is in the table above, deliberately: they serve the repositories that need them rather than the fleet contract, and a repository needing one declares it in a `host-tools.json` of its own, which [`scripts/host_gate.py`][host-gate] merges over this one. The merge tightens only, so a repository may raise a floor or add one and may not lower or remove one.

**A host being stood up needs no Python.** The tooling under [`host-setup/`][host-setup-dir] is shell and PowerShell, deliberately, because requiring an interpreter to upgrade a package or install a tool would make the first step of standing a host up depend on the thing that step exists to provide. The Python floor above is a development requirement, meaning [`scripts/`][scripts-dir] and [`spec/`][spec-dir], and a host that only runs services never has to meet it. `bootstrap.sh` needs `curl` and `tar`, both of which a base install carries or can install without a network tool of its own. `bootstrap.ps1` needs only `tar.exe`, which has shipped with Windows since 1803, and installs its one further dependency, PowerShell 7, itself through `winget`. The one exception is the skills step at the end of a stand-up, which drives the Python installer in [`scripts/`][scripts-dir], and it runs last for exactly that reason: `install-tools` has provided the interpreter by then, and run alone on a host without one it stops and names the tools step as its prerequisite.
Expand Down
2 changes: 1 addition & 1 deletion host-setup/linux/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@ Each script is LF with a shebang, and its executable bit is tracked in git. A fr

The Windows registry has one source because `winget` tracks upstream. Here the distro package trails upstream on `gh`, on `node`, and on `uv`, so a tool comes from whichever source keeps up:

- **The distro**, for `git` and `python`, where apt's own package is current enough.
- **The distro**, for `git`, `python`, and Ripgrep, where apt's own package is current enough.
- **An upstream apt repository**, for `gh`, `node`, `docker`, and `powershell`, and for `dotnet` as a fallback, where upstream publishes one.
- **A released binary into `/usr/local/bin`**, for `jq`, `uv`, and `git-restore-mtime`, where upstream publishes no repository.

Expand Down
48 changes: 45 additions & 3 deletions host-setup/linux/install-tools.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,7 @@ readonly SUDO_TIMESTAMP_TIMEOUT=60

# Managed tools, in dependency order: node asks jq to read the upstream release index.
# The one optional member, powershell, joins the default selection only under --optional.
readonly TOOLS=(git gh jq git-restore-mtime node python uv docker dotnet powershell)
readonly TOOLS=(git gh jq git-restore-mtime node python ripgrep uv docker dotnet powershell)

# Package sets.
# The default set is what a tool needs to be useful, and the optional set is what is useful often enough to name but not always wanted, installed only with --optional.
Expand DownExpand Up@@ -508,6 +508,37 @@ python_install() {
apt_install "${packages[@]}"
}

# --- ripgrep ---

ripgrep_source() { printf 'distro'; }
ripgrep_version() { apt_installed_version ripgrep; }
ripgrep_target() { apt_candidate_version ripgrep; }

ripgrep_download_path() {
local resolved
resolved=$(type -P rg 2> /dev/null || true)
[[ $resolved == /* ]] || return 0
dpkg-query -S "$resolved" > /dev/null 2>&1 || printf '%s' "$resolved"
}

ripgrep_remove_download() {
local resolved
while true; do
resolved=$(ripgrep_download_path)
[[ -n $resolved ]] || break
log "ripgrep: removing downloaded copy at $resolved before the distro package install"
confirm " Remove $resolved?" || die "Declined, downloaded Ripgrep left as it is"
run_root rm -f "$resolved"
[[ $DRY_RUN == true ]] && break
hash -r
done
}

ripgrep_install() {
ripgrep_remove_download
apt_install ripgrep
}

# --- uv ---

uv_source() { printf 'astral-sh/uv'; }
Expand DownExpand Up@@ -870,6 +901,13 @@ tool_note() {
note "python" "optional set not selected: ${PYTHON_OPTIONAL[*]}"
fi
;;
ripgrep)
local resolved
resolved=$(ripgrep_download_path)
if [[ -n $resolved ]]; then
note "ripgrep" "$resolved is an unowned downloaded copy and an install or upgrade removes it before apt installs Ripgrep"
fi
;;
dotnet)
local -a sdks=()
readarray -t sdks < <(dotnet_sdk_packages)
Expand DownExpand Up@@ -950,13 +988,17 @@ report() {
done
}

# Remove a copy of a managed tool found earlier on PATH than $BIN_DIR, so the managed copy is what PATH resolves to afterward.
# Only jq, uv, and git-restore-mtime install as loose binaries outside apt, and uv's companion uvx is unshadowed alongside it.
# Remove a copy of a managed tool found earlier on PATH than its managed destination, so the managed copy is what PATH resolves to afterward.
# Ripgrep migrates an unowned download to apt, while jq, uv, and git-restore-mtime install as loose binaries in $BIN_DIR.
tool_unshadow() {
local tool="$1"
local -a names=()
case "$tool" in
jq | git-restore-mtime) names=("$tool") ;;
ripgrep)
ripgrep_remove_download
return 0
;;
uv) names=(uv uvx) ;;
*) return 0 ;;
esac
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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
46 changes: 46 additions & 0 deletions .agents/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
1 change: 1 addition & 0 deletions .claude-plugin/fleet-skills/.claude-plugin/plugin.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
"name": "ptr727"
},
"skills": [
"./skills/add-host-tool",
"./skills/agent-conduct",
"./skills/audit-a-repo",
"./skills/carried-instruction-file-guard",
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/fleet-skills/.source-digest
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
cf555123c5d2982d
0209e62c88c2a759
46 changes: 46 additions & 0 deletions .claude-plugin/fleet-skills/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
46 changes: 46 additions & 0 deletions .github/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -101,3 +101,5 @@ Every rule below is a level-two section of [`GOVERNANCE.md`](./GOVERNANCE.md). R
A row above with no Skill annotation is doc-only by decision, not by omission. A Skill surfaces rules at a trigger moment, and each unannotated section either binds always or carries no moment narrower than reading it: `Foundational Principles` is rationale read once rather than a procedure, `Repository Boundaries and Write Safety` and `Representative Data in Agent-Authored Text` are always-on law that must bind even when no Skill fires (the `gh-write-guard` hook and the host-wide instruction blocks the agent-safety installer maintains are their enforcement layer, and the one moment in the boundaries section narrow enough to surface, isolating into a worktree at task start, gets the `repo-worktree` Skill on top of that law rather than instead of it), and `Hub-Hosted Tooling`, `Supported Development Platforms`, `Devcontainer`, `Editor and Tasks`, `Repository Details`, and `Repository Layout` are short reference sections a task reads at the moment it touches their subject, each already routed to by the procedures and Skills that need it.

Some of the rules above are also packaged as Claude Code / opencode / Codex Skills, hand-authored at `.agents/skills/` in the hub (not a repo-relative link here, since that path is hub-local and not carried into every fleet repo), so they surface automatically instead of needing to be re-read every session. `scripts/` is hub-hosted and reached rather than carried, per "Hub-Hosted Tooling", so run the installer from a hub checkout: `python3 scripts/skills_install.py` (or the `.sh`/`.ps1` wrapper) once per machine, from `github.com/ptr727/ProjectTemplate`, installs them for every repo touched from that machine. `python3 scripts/skills_install.py --report`, also from a hub checkout, says whether this machine is current. A rule that keeps needing to be restated is a sign the install is missing or stale, not that the rule does not exist. Keeping a repo's own carried `.github/copilot-instructions.md` in sync with the hub, without losing that repo's own "Disproved Claims" ledger entries in the process, is `copilot-instructions-keeper`, a skill about maintaining that file rather than a rule extracted from it, since the file itself is read directly by the Copilot bot and stays fully intact everywhere it is carried. Checking, from inside this repo's own session with no operator watching, whether this repo and this machine are actually current against the hub is `fleet-conformance-check`, new content rather than a rule extracted from a section, the counterpart to `resync-a-repo` that needs no standing hub checkout or named target beyond the repo the session is already in, even though its own check fetches a hub checkout to reach `scripts/skills_install.py`. Opening a pull request against a repository outside this fleet, one the maintainer does not control, follows a different workflow entirely, new content rather than a rule extracted from a section, packaged as `upstream-contribution-workflow` and independent of the target repo's own type or workflow model. Isolating a task into its own worktree before its first file edit, with the base-branch choice, the layout convention, and the cleanup mechanics, is `repo-worktree`, the task-start surface of the `Repository Boundaries and Write Safety` law, which keeps the rule. Creating, changing, or retiring one of these skills is itself packaged as `skill-lifecycle`, hub-context only, since `.agents/skills/` exists only in the hub and the generated plugin tree is never hand-edited.

Adding or changing a managed host tool is packaged as `add-host-tool`. It keeps the cross-platform contract, installer, documentation, test, and native-verification surfaces together.
3 changes: 3 additions & 0 deletions docs/host-setup.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ This section is the **contract**: which tools a host needs and which repo proced
| `gh` | the PR and review loop, `gh api` queries, `repo-config/configure.sh` | `gh --version` | **2.47.0**, measured |
| Python 3 | `scripts/` and `spec/` (standard library only, no packages to install) | `python3 --version`, or `py -3 --version` on native Windows | **3.13**, target |
| `jq` | the ruleset normalizer in `repo-config/configure.sh`, the ruleset diff in [`AUDIT.md`][audit] section 6, and payload regeneration | `jq --version` | **1.7**, target |
| Ripgrep | repository searches and file discovery by coding agents | `rg --version` | **13.0.0**, target |
| `docker` | the four linters, which run as pinned images rather than local installs | `docker --version` | **29.6.2**, target |
| `uv` / `uvx` | coverage runs, and the Python toolchain (`ruff`, `pyright` or `mypy`) in a Python repo | `uv --version` | **0.12.2**, target |

Expand All@@ -45,6 +46,8 @@ Presence is the weaker half of this contract. Both host defects this fleet has a

**The rest of the table takes the distribution's package, and one more does not.** `git` and the Python interpreter come from the distribution, because each keeps up well enough that a second source buys nothing and costs a repository to trust. `jq` is the same on a current Debian or Ubuntu, which carries a version at or above the floor, and the upstream release binary is the answer only where it does not. `uv` is published by its authors as a release archive and packaged by neither distribution, so upstream is the only source there is. Where a repository needs `node`, the distribution's package trails upstream by whole release lines, so it comes from the NodeSource repository on the line upstream currently carries as long term support. Where a repository needs `dotnet`, the distribution's feed is preferred where it carries an SDK and Microsoft's feed is the fallback, because mixing the two is what breaks a host rather than either one alone, and Microsoft's carries `amd64` only. Where a repository needs `pwsh`, that same feed is the only source, since no distribution packages it, and the tooling installs it as the `powershell` tool under `--optional`, with `pwsh` the command that tool provides.

Ripgrep also comes from the distribution, where every supported current release meets its floor. It provides `rg` search and `rg --files` discovery for coding agents.

Neither `node`, `dotnet`, nor `pwsh` is in the table above, deliberately: they serve the repositories that need them rather than the fleet contract, and a repository needing one declares it in a `host-tools.json` of its own, which [`scripts/host_gate.py`][host-gate] merges over this one. The merge tightens only, so a repository may raise a floor or add one and may not lower or remove one.

**A host being stood up needs no Python.** The tooling under [`host-setup/`][host-setup-dir] is shell and PowerShell, deliberately, because requiring an interpreter to upgrade a package or install a tool would make the first step of standing a host up depend on the thing that step exists to provide. The Python floor above is a development requirement, meaning [`scripts/`][scripts-dir] and [`spec/`][spec-dir], and a host that only runs services never has to meet it. `bootstrap.sh` needs `curl` and `tar`, both of which a base install carries or can install without a network tool of its own. `bootstrap.ps1` needs only `tar.exe`, which has shipped with Windows since 1803, and installs its one further dependency, PowerShell 7, itself through `winget`. The one exception is the skills step at the end of a stand-up, which drives the Python installer in [`scripts/`][scripts-dir], and it runs last for exactly that reason: `install-tools` has provided the interpreter by then, and run alone on a host without one it stops and names the tools step as its prerequisite.
Expand Down
2 changes: 1 addition & 1 deletion host-setup/linux/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@ Each script is LF with a shebang, and its executable bit is tracked in git. A fr

The Windows registry has one source because `winget` tracks upstream. Here the distro package trails upstream on `gh`, on `node`, and on `uv`, so a tool comes from whichever source keeps up:

- **The distro**, for `git` and `python`, where apt's own package is current enough.
- **The distro**, for `git`, `python`, and Ripgrep, where apt's own package is current enough.
- **An upstream apt repository**, for `gh`, `node`, `docker`, and `powershell`, and for `dotnet` as a fallback, where upstream publishes one.
- **A released binary into `/usr/local/bin`**, for `jq`, `uv`, and `git-restore-mtime`, where upstream publishes no repository.

Expand Down
48 changes: 45 additions & 3 deletions host-setup/linux/install-tools.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,7 @@ readonly SUDO_TIMESTAMP_TIMEOUT=60

# Managed tools, in dependency order: node asks jq to read the upstream release index.
# The one optional member, powershell, joins the default selection only under --optional.
readonly TOOLS=(git gh jq git-restore-mtime node python uv docker dotnet powershell)
readonly TOOLS=(git gh jq git-restore-mtime node python ripgrep uv docker dotnet powershell)

# Package sets.
# The default set is what a tool needs to be useful, and the optional set is what is useful often enough to name but not always wanted, installed only with --optional.
Expand DownExpand Up@@ -508,6 +508,37 @@ python_install() {
apt_install "${packages[@]}"
}

# --- ripgrep ---

ripgrep_source() { printf 'distro'; }
ripgrep_version() { apt_installed_version ripgrep; }
ripgrep_target() { apt_candidate_version ripgrep; }

ripgrep_download_path() {
local resolved
resolved=$(type -P rg 2> /dev/null || true)
[[ $resolved == /* ]] || return 0
dpkg-query -S "$resolved" > /dev/null 2>&1 || printf '%s' "$resolved"
}

ripgrep_remove_download() {
local resolved
while true; do
resolved=$(ripgrep_download_path)
[[ -n $resolved ]] || break
log "ripgrep: removing downloaded copy at $resolved before the distro package install"
confirm " Remove $resolved?" || die "Declined, downloaded Ripgrep left as it is"
run_root rm -f "$resolved"
[[ $DRY_RUN == true ]] && break
hash -r
done
}

ripgrep_install() {
ripgrep_remove_download
apt_install ripgrep
}

# --- uv ---

uv_source() { printf 'astral-sh/uv'; }
Expand DownExpand Up@@ -870,6 +901,13 @@ tool_note() {
note "python" "optional set not selected: ${PYTHON_OPTIONAL[*]}"
fi
;;
ripgrep)
local resolved
resolved=$(ripgrep_download_path)
if [[ -n $resolved ]]; then
note "ripgrep" "$resolved is an unowned downloaded copy and an install or upgrade removes it before apt installs Ripgrep"
fi
;;
dotnet)
local -a sdks=()
readarray -t sdks < <(dotnet_sdk_packages)
Expand DownExpand Up@@ -950,13 +988,17 @@ report() {
done
}

# Remove a copy of a managed tool found earlier on PATH than $BIN_DIR, so the managed copy is what PATH resolves to afterward.
# Only jq, uv, and git-restore-mtime install as loose binaries outside apt, and uv's companion uvx is unshadowed alongside it.
# Remove a copy of a managed tool found earlier on PATH than its managed destination, so the managed copy is what PATH resolves to afterward.
# Ripgrep migrates an unowned download to apt, while jq, uv, and git-restore-mtime install as loose binaries in $BIN_DIR.
tool_unshadow() {
local tool="$1"
local -a names=()
case "$tool" in
jq | git-restore-mtime) names=("$tool") ;;
ripgrep)
ripgrep_remove_download
return 0
;;
uv) names=(uv uvx) ;;
*) return 0 ;;
esac
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
46 changes: 46 additions & 0 deletions .agents/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
1 change: 1 addition & 0 deletions .claude-plugin/fleet-skills/.claude-plugin/plugin.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
"name": "ptr727"
},
"skills": [
"./skills/add-host-tool",
"./skills/agent-conduct",
"./skills/audit-a-repo",
"./skills/carried-instruction-file-guard",
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/fleet-skills/.source-digest
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
cf555123c5d2982d
0209e62c88c2a759
46 changes: 46 additions & 0 deletions .claude-plugin/fleet-skills/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
46 changes: 46 additions & 0 deletions .github/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -101,3 +101,5 @@ Every rule below is a level-two section of [`GOVERNANCE.md`](./GOVERNANCE.md). R
A row above with no Skill annotation is doc-only by decision, not by omission. A Skill surfaces rules at a trigger moment, and each unannotated section either binds always or carries no moment narrower than reading it: `Foundational Principles` is rationale read once rather than a procedure, `Repository Boundaries and Write Safety` and `Representative Data in Agent-Authored Text` are always-on law that must bind even when no Skill fires (the `gh-write-guard` hook and the host-wide instruction blocks the agent-safety installer maintains are their enforcement layer, and the one moment in the boundaries section narrow enough to surface, isolating into a worktree at task start, gets the `repo-worktree` Skill on top of that law rather than instead of it), and `Hub-Hosted Tooling`, `Supported Development Platforms`, `Devcontainer`, `Editor and Tasks`, `Repository Details`, and `Repository Layout` are short reference sections a task reads at the moment it touches their subject, each already routed to by the procedures and Skills that need it.

Some of the rules above are also packaged as Claude Code / opencode / Codex Skills, hand-authored at `.agents/skills/` in the hub (not a repo-relative link here, since that path is hub-local and not carried into every fleet repo), so they surface automatically instead of needing to be re-read every session. `scripts/` is hub-hosted and reached rather than carried, per "Hub-Hosted Tooling", so run the installer from a hub checkout: `python3 scripts/skills_install.py` (or the `.sh`/`.ps1` wrapper) once per machine, from `github.com/ptr727/ProjectTemplate`, installs them for every repo touched from that machine. `python3 scripts/skills_install.py --report`, also from a hub checkout, says whether this machine is current. A rule that keeps needing to be restated is a sign the install is missing or stale, not that the rule does not exist. Keeping a repo's own carried `.github/copilot-instructions.md` in sync with the hub, without losing that repo's own "Disproved Claims" ledger entries in the process, is `copilot-instructions-keeper`, a skill about maintaining that file rather than a rule extracted from it, since the file itself is read directly by the Copilot bot and stays fully intact everywhere it is carried. Checking, from inside this repo's own session with no operator watching, whether this repo and this machine are actually current against the hub is `fleet-conformance-check`, new content rather than a rule extracted from a section, the counterpart to `resync-a-repo` that needs no standing hub checkout or named target beyond the repo the session is already in, even though its own check fetches a hub checkout to reach `scripts/skills_install.py`. Opening a pull request against a repository outside this fleet, one the maintainer does not control, follows a different workflow entirely, new content rather than a rule extracted from a section, packaged as `upstream-contribution-workflow` and independent of the target repo's own type or workflow model. Isolating a task into its own worktree before its first file edit, with the base-branch choice, the layout convention, and the cleanup mechanics, is `repo-worktree`, the task-start surface of the `Repository Boundaries and Write Safety` law, which keeps the rule. Creating, changing, or retiring one of these skills is itself packaged as `skill-lifecycle`, hub-context only, since `.agents/skills/` exists only in the hub and the generated plugin tree is never hand-edited.

Adding or changing a managed host tool is packaged as `add-host-tool`. It keeps the cross-platform contract, installer, documentation, test, and native-verification surfaces together.
3 changes: 3 additions & 0 deletions docs/host-setup.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ This section is the **contract**: which tools a host needs and which repo proced
| `gh` | the PR and review loop, `gh api` queries, `repo-config/configure.sh` | `gh --version` | **2.47.0**, measured |
| Python 3 | `scripts/` and `spec/` (standard library only, no packages to install) | `python3 --version`, or `py -3 --version` on native Windows | **3.13**, target |
| `jq` | the ruleset normalizer in `repo-config/configure.sh`, the ruleset diff in [`AUDIT.md`][audit] section 6, and payload regeneration | `jq --version` | **1.7**, target |
| Ripgrep | repository searches and file discovery by coding agents | `rg --version` | **13.0.0**, target |
| `docker` | the four linters, which run as pinned images rather than local installs | `docker --version` | **29.6.2**, target |
| `uv` / `uvx` | coverage runs, and the Python toolchain (`ruff`, `pyright` or `mypy`) in a Python repo | `uv --version` | **0.12.2**, target |

Expand All@@ -45,6 +46,8 @@ Presence is the weaker half of this contract. Both host defects this fleet has a

**The rest of the table takes the distribution's package, and one more does not.** `git` and the Python interpreter come from the distribution, because each keeps up well enough that a second source buys nothing and costs a repository to trust. `jq` is the same on a current Debian or Ubuntu, which carries a version at or above the floor, and the upstream release binary is the answer only where it does not. `uv` is published by its authors as a release archive and packaged by neither distribution, so upstream is the only source there is. Where a repository needs `node`, the distribution's package trails upstream by whole release lines, so it comes from the NodeSource repository on the line upstream currently carries as long term support. Where a repository needs `dotnet`, the distribution's feed is preferred where it carries an SDK and Microsoft's feed is the fallback, because mixing the two is what breaks a host rather than either one alone, and Microsoft's carries `amd64` only. Where a repository needs `pwsh`, that same feed is the only source, since no distribution packages it, and the tooling installs it as the `powershell` tool under `--optional`, with `pwsh` the command that tool provides.

Ripgrep also comes from the distribution, where every supported current release meets its floor. It provides `rg` search and `rg --files` discovery for coding agents.

Neither `node`, `dotnet`, nor `pwsh` is in the table above, deliberately: they serve the repositories that need them rather than the fleet contract, and a repository needing one declares it in a `host-tools.json` of its own, which [`scripts/host_gate.py`][host-gate] merges over this one. The merge tightens only, so a repository may raise a floor or add one and may not lower or remove one.

**A host being stood up needs no Python.** The tooling under [`host-setup/`][host-setup-dir] is shell and PowerShell, deliberately, because requiring an interpreter to upgrade a package or install a tool would make the first step of standing a host up depend on the thing that step exists to provide. The Python floor above is a development requirement, meaning [`scripts/`][scripts-dir] and [`spec/`][spec-dir], and a host that only runs services never has to meet it. `bootstrap.sh` needs `curl` and `tar`, both of which a base install carries or can install without a network tool of its own. `bootstrap.ps1` needs only `tar.exe`, which has shipped with Windows since 1803, and installs its one further dependency, PowerShell 7, itself through `winget`. The one exception is the skills step at the end of a stand-up, which drives the Python installer in [`scripts/`][scripts-dir], and it runs last for exactly that reason: `install-tools` has provided the interpreter by then, and run alone on a host without one it stops and names the tools step as its prerequisite.
Expand Down
2 changes: 1 addition & 1 deletion host-setup/linux/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@ Each script is LF with a shebang, and its executable bit is tracked in git. A fr

The Windows registry has one source because `winget` tracks upstream. Here the distro package trails upstream on `gh`, on `node`, and on `uv`, so a tool comes from whichever source keeps up:

- **The distro**, for `git` and `python`, where apt's own package is current enough.
- **The distro**, for `git`, `python`, and Ripgrep, where apt's own package is current enough.
- **An upstream apt repository**, for `gh`, `node`, `docker`, and `powershell`, and for `dotnet` as a fallback, where upstream publishes one.
- **A released binary into `/usr/local/bin`**, for `jq`, `uv`, and `git-restore-mtime`, where upstream publishes no repository.

Expand Down
48 changes: 45 additions & 3 deletions host-setup/linux/install-tools.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,7 @@ readonly SUDO_TIMESTAMP_TIMEOUT=60

# Managed tools, in dependency order: node asks jq to read the upstream release index.
# The one optional member, powershell, joins the default selection only under --optional.
readonly TOOLS=(git gh jq git-restore-mtime node python uv docker dotnet powershell)
readonly TOOLS=(git gh jq git-restore-mtime node python ripgrep uv docker dotnet powershell)

# Package sets.
# The default set is what a tool needs to be useful, and the optional set is what is useful often enough to name but not always wanted, installed only with --optional.
Expand DownExpand Up@@ -508,6 +508,37 @@ python_install() {
apt_install "${packages[@]}"
}

# --- ripgrep ---

ripgrep_source() { printf 'distro'; }
ripgrep_version() { apt_installed_version ripgrep; }
ripgrep_target() { apt_candidate_version ripgrep; }

ripgrep_download_path() {
local resolved
resolved=$(type -P rg 2> /dev/null || true)
[[ $resolved == /* ]] || return 0
dpkg-query -S "$resolved" > /dev/null 2>&1 || printf '%s' "$resolved"
}

ripgrep_remove_download() {
local resolved
while true; do
resolved=$(ripgrep_download_path)
[[ -n $resolved ]] || break
log "ripgrep: removing downloaded copy at $resolved before the distro package install"
confirm " Remove $resolved?" || die "Declined, downloaded Ripgrep left as it is"
run_root rm -f "$resolved"
[[ $DRY_RUN == true ]] && break
hash -r
done
}

ripgrep_install() {
ripgrep_remove_download
apt_install ripgrep
}

# --- uv ---

uv_source() { printf 'astral-sh/uv'; }
Expand DownExpand Up@@ -870,6 +901,13 @@ tool_note() {
note "python" "optional set not selected: ${PYTHON_OPTIONAL[*]}"
fi
;;
ripgrep)
local resolved
resolved=$(ripgrep_download_path)
if [[ -n $resolved ]]; then
note "ripgrep" "$resolved is an unowned downloaded copy and an install or upgrade removes it before apt installs Ripgrep"
fi
;;
dotnet)
local -a sdks=()
readarray -t sdks < <(dotnet_sdk_packages)
Expand DownExpand Up@@ -950,13 +988,17 @@ report() {
done
}

# Remove a copy of a managed tool found earlier on PATH than $BIN_DIR, so the managed copy is what PATH resolves to afterward.
# Only jq, uv, and git-restore-mtime install as loose binaries outside apt, and uv's companion uvx is unshadowed alongside it.
# Remove a copy of a managed tool found earlier on PATH than its managed destination, so the managed copy is what PATH resolves to afterward.
# Ripgrep migrates an unowned download to apt, while jq, uv, and git-restore-mtime install as loose binaries in $BIN_DIR.
tool_unshadow() {
local tool="$1"
local -a names=()
case "$tool" in
jq | git-restore-mtime) names=("$tool") ;;
ripgrep)
ripgrep_remove_download
return 0
;;
uv) names=(uv uvx) ;;
*) return 0 ;;
esac
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
46 changes: 46 additions & 0 deletions .agents/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
1 change: 1 addition & 0 deletions .claude-plugin/fleet-skills/.claude-plugin/plugin.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
"name": "ptr727"
},
"skills": [
"./skills/add-host-tool",
"./skills/agent-conduct",
"./skills/audit-a-repo",
"./skills/carried-instruction-file-guard",
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/fleet-skills/.source-digest
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
cf555123c5d2982d
0209e62c88c2a759
46 changes: 46 additions & 0 deletions .claude-plugin/fleet-skills/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
46 changes: 46 additions & 0 deletions .github/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -101,3 +101,5 @@ Every rule below is a level-two section of [`GOVERNANCE.md`](./GOVERNANCE.md). R
A row above with no Skill annotation is doc-only by decision, not by omission. A Skill surfaces rules at a trigger moment, and each unannotated section either binds always or carries no moment narrower than reading it: `Foundational Principles` is rationale read once rather than a procedure, `Repository Boundaries and Write Safety` and `Representative Data in Agent-Authored Text` are always-on law that must bind even when no Skill fires (the `gh-write-guard` hook and the host-wide instruction blocks the agent-safety installer maintains are their enforcement layer, and the one moment in the boundaries section narrow enough to surface, isolating into a worktree at task start, gets the `repo-worktree` Skill on top of that law rather than instead of it), and `Hub-Hosted Tooling`, `Supported Development Platforms`, `Devcontainer`, `Editor and Tasks`, `Repository Details`, and `Repository Layout` are short reference sections a task reads at the moment it touches their subject, each already routed to by the procedures and Skills that need it.

Some of the rules above are also packaged as Claude Code / opencode / Codex Skills, hand-authored at `.agents/skills/` in the hub (not a repo-relative link here, since that path is hub-local and not carried into every fleet repo), so they surface automatically instead of needing to be re-read every session. `scripts/` is hub-hosted and reached rather than carried, per "Hub-Hosted Tooling", so run the installer from a hub checkout: `python3 scripts/skills_install.py` (or the `.sh`/`.ps1` wrapper) once per machine, from `github.com/ptr727/ProjectTemplate`, installs them for every repo touched from that machine. `python3 scripts/skills_install.py --report`, also from a hub checkout, says whether this machine is current. A rule that keeps needing to be restated is a sign the install is missing or stale, not that the rule does not exist. Keeping a repo's own carried `.github/copilot-instructions.md` in sync with the hub, without losing that repo's own "Disproved Claims" ledger entries in the process, is `copilot-instructions-keeper`, a skill about maintaining that file rather than a rule extracted from it, since the file itself is read directly by the Copilot bot and stays fully intact everywhere it is carried. Checking, from inside this repo's own session with no operator watching, whether this repo and this machine are actually current against the hub is `fleet-conformance-check`, new content rather than a rule extracted from a section, the counterpart to `resync-a-repo` that needs no standing hub checkout or named target beyond the repo the session is already in, even though its own check fetches a hub checkout to reach `scripts/skills_install.py`. Opening a pull request against a repository outside this fleet, one the maintainer does not control, follows a different workflow entirely, new content rather than a rule extracted from a section, packaged as `upstream-contribution-workflow` and independent of the target repo's own type or workflow model. Isolating a task into its own worktree before its first file edit, with the base-branch choice, the layout convention, and the cleanup mechanics, is `repo-worktree`, the task-start surface of the `Repository Boundaries and Write Safety` law, which keeps the rule. Creating, changing, or retiring one of these skills is itself packaged as `skill-lifecycle`, hub-context only, since `.agents/skills/` exists only in the hub and the generated plugin tree is never hand-edited.

Adding or changing a managed host tool is packaged as `add-host-tool`. It keeps the cross-platform contract, installer, documentation, test, and native-verification surfaces together.
3 changes: 3 additions & 0 deletions docs/host-setup.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ This section is the **contract**: which tools a host needs and which repo proced
| `gh` | the PR and review loop, `gh api` queries, `repo-config/configure.sh` | `gh --version` | **2.47.0**, measured |
| Python 3 | `scripts/` and `spec/` (standard library only, no packages to install) | `python3 --version`, or `py -3 --version` on native Windows | **3.13**, target |
| `jq` | the ruleset normalizer in `repo-config/configure.sh`, the ruleset diff in [`AUDIT.md`][audit] section 6, and payload regeneration | `jq --version` | **1.7**, target |
| Ripgrep | repository searches and file discovery by coding agents | `rg --version` | **13.0.0**, target |
| `docker` | the four linters, which run as pinned images rather than local installs | `docker --version` | **29.6.2**, target |
| `uv` / `uvx` | coverage runs, and the Python toolchain (`ruff`, `pyright` or `mypy`) in a Python repo | `uv --version` | **0.12.2**, target |

Expand All@@ -45,6 +46,8 @@ Presence is the weaker half of this contract. Both host defects this fleet has a

**The rest of the table takes the distribution's package, and one more does not.** `git` and the Python interpreter come from the distribution, because each keeps up well enough that a second source buys nothing and costs a repository to trust. `jq` is the same on a current Debian or Ubuntu, which carries a version at or above the floor, and the upstream release binary is the answer only where it does not. `uv` is published by its authors as a release archive and packaged by neither distribution, so upstream is the only source there is. Where a repository needs `node`, the distribution's package trails upstream by whole release lines, so it comes from the NodeSource repository on the line upstream currently carries as long term support. Where a repository needs `dotnet`, the distribution's feed is preferred where it carries an SDK and Microsoft's feed is the fallback, because mixing the two is what breaks a host rather than either one alone, and Microsoft's carries `amd64` only. Where a repository needs `pwsh`, that same feed is the only source, since no distribution packages it, and the tooling installs it as the `powershell` tool under `--optional`, with `pwsh` the command that tool provides.

Ripgrep also comes from the distribution, where every supported current release meets its floor. It provides `rg` search and `rg --files` discovery for coding agents.

Neither `node`, `dotnet`, nor `pwsh` is in the table above, deliberately: they serve the repositories that need them rather than the fleet contract, and a repository needing one declares it in a `host-tools.json` of its own, which [`scripts/host_gate.py`][host-gate] merges over this one. The merge tightens only, so a repository may raise a floor or add one and may not lower or remove one.

**A host being stood up needs no Python.** The tooling under [`host-setup/`][host-setup-dir] is shell and PowerShell, deliberately, because requiring an interpreter to upgrade a package or install a tool would make the first step of standing a host up depend on the thing that step exists to provide. The Python floor above is a development requirement, meaning [`scripts/`][scripts-dir] and [`spec/`][spec-dir], and a host that only runs services never has to meet it. `bootstrap.sh` needs `curl` and `tar`, both of which a base install carries or can install without a network tool of its own. `bootstrap.ps1` needs only `tar.exe`, which has shipped with Windows since 1803, and installs its one further dependency, PowerShell 7, itself through `winget`. The one exception is the skills step at the end of a stand-up, which drives the Python installer in [`scripts/`][scripts-dir], and it runs last for exactly that reason: `install-tools` has provided the interpreter by then, and run alone on a host without one it stops and names the tools step as its prerequisite.
Expand Down
2 changes: 1 addition & 1 deletion host-setup/linux/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@ Each script is LF with a shebang, and its executable bit is tracked in git. A fr

The Windows registry has one source because `winget` tracks upstream. Here the distro package trails upstream on `gh`, on `node`, and on `uv`, so a tool comes from whichever source keeps up:

- **The distro**, for `git` and `python`, where apt's own package is current enough.
- **The distro**, for `git`, `python`, and Ripgrep, where apt's own package is current enough.
- **An upstream apt repository**, for `gh`, `node`, `docker`, and `powershell`, and for `dotnet` as a fallback, where upstream publishes one.
- **A released binary into `/usr/local/bin`**, for `jq`, `uv`, and `git-restore-mtime`, where upstream publishes no repository.

Expand Down
48 changes: 45 additions & 3 deletions host-setup/linux/install-tools.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,7 @@ readonly SUDO_TIMESTAMP_TIMEOUT=60

# Managed tools, in dependency order: node asks jq to read the upstream release index.
# The one optional member, powershell, joins the default selection only under --optional.
readonly TOOLS=(git gh jq git-restore-mtime node python uv docker dotnet powershell)
readonly TOOLS=(git gh jq git-restore-mtime node python ripgrep uv docker dotnet powershell)

# Package sets.
# The default set is what a tool needs to be useful, and the optional set is what is useful often enough to name but not always wanted, installed only with --optional.
Expand DownExpand Up@@ -508,6 +508,37 @@ python_install() {
apt_install "${packages[@]}"
}

# --- ripgrep ---

ripgrep_source() { printf 'distro'; }
ripgrep_version() { apt_installed_version ripgrep; }
ripgrep_target() { apt_candidate_version ripgrep; }

ripgrep_download_path() {
local resolved
resolved=$(type -P rg 2> /dev/null || true)
[[ $resolved == /* ]] || return 0
dpkg-query -S "$resolved" > /dev/null 2>&1 || printf '%s' "$resolved"
}

ripgrep_remove_download() {
local resolved
while true; do
resolved=$(ripgrep_download_path)
[[ -n $resolved ]] || break
log "ripgrep: removing downloaded copy at $resolved before the distro package install"
confirm " Remove $resolved?" || die "Declined, downloaded Ripgrep left as it is"
run_root rm -f "$resolved"
[[ $DRY_RUN == true ]] && break
hash -r
done
}

ripgrep_install() {
ripgrep_remove_download
apt_install ripgrep
}

# --- uv ---

uv_source() { printf 'astral-sh/uv'; }
Expand DownExpand Up@@ -870,6 +901,13 @@ tool_note() {
note "python" "optional set not selected: ${PYTHON_OPTIONAL[*]}"
fi
;;
ripgrep)
local resolved
resolved=$(ripgrep_download_path)
if [[ -n $resolved ]]; then
note "ripgrep" "$resolved is an unowned downloaded copy and an install or upgrade removes it before apt installs Ripgrep"
fi
;;
dotnet)
local -a sdks=()
readarray -t sdks < <(dotnet_sdk_packages)
Expand DownExpand Up@@ -950,13 +988,17 @@ report() {
done
}

# Remove a copy of a managed tool found earlier on PATH than $BIN_DIR, so the managed copy is what PATH resolves to afterward.
# Only jq, uv, and git-restore-mtime install as loose binaries outside apt, and uv's companion uvx is unshadowed alongside it.
# Remove a copy of a managed tool found earlier on PATH than its managed destination, so the managed copy is what PATH resolves to afterward.
# Ripgrep migrates an unowned download to apt, while jq, uv, and git-restore-mtime install as loose binaries in $BIN_DIR.
tool_unshadow() {
local tool="$1"
local -a names=()
case "$tool" in
jq | git-restore-mtime) names=("$tool") ;;
ripgrep)
ripgrep_remove_download
return 0
;;
uv) names=(uv uvx) ;;
*) return 0 ;;
esac
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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
46 changes: 46 additions & 0 deletions .agents/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
1 change: 1 addition & 0 deletions .claude-plugin/fleet-skills/.claude-plugin/plugin.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
"name": "ptr727"
},
"skills": [
"./skills/add-host-tool",
"./skills/agent-conduct",
"./skills/audit-a-repo",
"./skills/carried-instruction-file-guard",
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/fleet-skills/.source-digest
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
cf555123c5d2982d
0209e62c88c2a759
46 changes: 46 additions & 0 deletions .claude-plugin/fleet-skills/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
46 changes: 46 additions & 0 deletions .github/skills/add-host-tool/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
name: add-host-tool
description: >-
Adds or changes a managed host tool across the ptr727/ProjectTemplate fleet contract, Linux and
Windows installers, platform documentation, and tests. Use this whenever adding, removing,
renaming, or changing the source, probe, version floor, install, report, upgrade, or dry-run
behavior of a tool in host-setup or spec/host-tools.json. Triggers even when the request names
only one platform, because a required fleet tool needs an executable remedy everywhere it
applies and native verification must stay on the platform being tested.
---

# Add Host Tool

## Establish the Contract

1. Read the issue and all follow-up comments before choosing a source or package identifier.
2. Add the tool to `spec/host-tools.json` in name order.
3. Use the executable's real version banner for the probe and pattern.
4. Set a floor only when it is measured or anchored to every supported distribution.
5. Provide `source` and executable `remedy` entries for every applicable platform.

## Implement Each Platform

- Keep the existing named-tool interface and default selection behavior.
- Prefer the distribution package when it meets the floor.
- Use the platform's established package manager and official package identifier.
- Keep install and upgrade idempotent.
- Before an apt-managed install, detect and remove an unowned downloaded copy that shadows it.
- Before a downloaded install, detect and remove a conflicting package-managed copy.
- Preserve report, list, explicit selection, install, upgrade, reinstall, and dry-run behavior.
- Do not test a Windows mutation on Linux or a Linux mutation on Windows.

When a platform is unavailable, verify its registry and tests without claiming a native install. Hand off the exact native commands and expected observations to the operator.

## Update the Complete Surface

Update the platform installers, `spec/host-tools.json`, `docs/host-setup.md`, and the applicable platform READMEs. Update installer and host-gate tests for selection, reporting, installation, upgrade, and dry-run behavior. Sweep prose that describes tool sources or the managed set.

## Verify

1. Run the spec validator and the focused installer and host-gate tests.
2. Run the repository's formatting, lint, type, and test gates required by the changed files.
3. On the current native platform, exercise list and report first.
4. Exercise install and upgrade dry runs.
5. Apply the install, repeat it to prove idempotence, and run the host gate.
6. Record untested platforms explicitly and leave cross-platform verification open.
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -101,3 +101,5 @@ Every rule below is a level-two section of [`GOVERNANCE.md`](./GOVERNANCE.md). R
A row above with no Skill annotation is doc-only by decision, not by omission. A Skill surfaces rules at a trigger moment, and each unannotated section either binds always or carries no moment narrower than reading it: `Foundational Principles` is rationale read once rather than a procedure, `Repository Boundaries and Write Safety` and `Representative Data in Agent-Authored Text` are always-on law that must bind even when no Skill fires (the `gh-write-guard` hook and the host-wide instruction blocks the agent-safety installer maintains are their enforcement layer, and the one moment in the boundaries section narrow enough to surface, isolating into a worktree at task start, gets the `repo-worktree` Skill on top of that law rather than instead of it), and `Hub-Hosted Tooling`, `Supported Development Platforms`, `Devcontainer`, `Editor and Tasks`, `Repository Details`, and `Repository Layout` are short reference sections a task reads at the moment it touches their subject, each already routed to by the procedures and Skills that need it.

Some of the rules above are also packaged as Claude Code / opencode / Codex Skills, hand-authored at `.agents/skills/` in the hub (not a repo-relative link here, since that path is hub-local and not carried into every fleet repo), so they surface automatically instead of needing to be re-read every session. `scripts/` is hub-hosted and reached rather than carried, per "Hub-Hosted Tooling", so run the installer from a hub checkout: `python3 scripts/skills_install.py` (or the `.sh`/`.ps1` wrapper) once per machine, from `github.com/ptr727/ProjectTemplate`, installs them for every repo touched from that machine. `python3 scripts/skills_install.py --report`, also from a hub checkout, says whether this machine is current. A rule that keeps needing to be restated is a sign the install is missing or stale, not that the rule does not exist. Keeping a repo's own carried `.github/copilot-instructions.md` in sync with the hub, without losing that repo's own "Disproved Claims" ledger entries in the process, is `copilot-instructions-keeper`, a skill about maintaining that file rather than a rule extracted from it, since the file itself is read directly by the Copilot bot and stays fully intact everywhere it is carried. Checking, from inside this repo's own session with no operator watching, whether this repo and this machine are actually current against the hub is `fleet-conformance-check`, new content rather than a rule extracted from a section, the counterpart to `resync-a-repo` that needs no standing hub checkout or named target beyond the repo the session is already in, even though its own check fetches a hub checkout to reach `scripts/skills_install.py`. Opening a pull request against a repository outside this fleet, one the maintainer does not control, follows a different workflow entirely, new content rather than a rule extracted from a section, packaged as `upstream-contribution-workflow` and independent of the target repo's own type or workflow model. Isolating a task into its own worktree before its first file edit, with the base-branch choice, the layout convention, and the cleanup mechanics, is `repo-worktree`, the task-start surface of the `Repository Boundaries and Write Safety` law, which keeps the rule. Creating, changing, or retiring one of these skills is itself packaged as `skill-lifecycle`, hub-context only, since `.agents/skills/` exists only in the hub and the generated plugin tree is never hand-edited.

Adding or changing a managed host tool is packaged as `add-host-tool`. It keeps the cross-platform contract, installer, documentation, test, and native-verification surfaces together.
3 changes: 3 additions & 0 deletions docs/host-setup.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ This section is the **contract**: which tools a host needs and which repo proced
| `gh` | the PR and review loop, `gh api` queries, `repo-config/configure.sh` | `gh --version` | **2.47.0**, measured |
| Python 3 | `scripts/` and `spec/` (standard library only, no packages to install) | `python3 --version`, or `py -3 --version` on native Windows | **3.13**, target |
| `jq` | the ruleset normalizer in `repo-config/configure.sh`, the ruleset diff in [`AUDIT.md`][audit] section 6, and payload regeneration | `jq --version` | **1.7**, target |
| Ripgrep | repository searches and file discovery by coding agents | `rg --version` | **13.0.0**, target |
| `docker` | the four linters, which run as pinned images rather than local installs | `docker --version` | **29.6.2**, target |
| `uv` / `uvx` | coverage runs, and the Python toolchain (`ruff`, `pyright` or `mypy`) in a Python repo | `uv --version` | **0.12.2**, target |

Expand All@@ -45,6 +46,8 @@ Presence is the weaker half of this contract. Both host defects this fleet has a

**The rest of the table takes the distribution's package, and one more does not.** `git` and the Python interpreter come from the distribution, because each keeps up well enough that a second source buys nothing and costs a repository to trust. `jq` is the same on a current Debian or Ubuntu, which carries a version at or above the floor, and the upstream release binary is the answer only where it does not. `uv` is published by its authors as a release archive and packaged by neither distribution, so upstream is the only source there is. Where a repository needs `node`, the distribution's package trails upstream by whole release lines, so it comes from the NodeSource repository on the line upstream currently carries as long term support. Where a repository needs `dotnet`, the distribution's feed is preferred where it carries an SDK and Microsoft's feed is the fallback, because mixing the two is what breaks a host rather than either one alone, and Microsoft's carries `amd64` only. Where a repository needs `pwsh`, that same feed is the only source, since no distribution packages it, and the tooling installs it as the `powershell` tool under `--optional`, with `pwsh` the command that tool provides.

Ripgrep also comes from the distribution, where every supported current release meets its floor. It provides `rg` search and `rg --files` discovery for coding agents.

Neither `node`, `dotnet`, nor `pwsh` is in the table above, deliberately: they serve the repositories that need them rather than the fleet contract, and a repository needing one declares it in a `host-tools.json` of its own, which [`scripts/host_gate.py`][host-gate] merges over this one. The merge tightens only, so a repository may raise a floor or add one and may not lower or remove one.

**A host being stood up needs no Python.** The tooling under [`host-setup/`][host-setup-dir] is shell and PowerShell, deliberately, because requiring an interpreter to upgrade a package or install a tool would make the first step of standing a host up depend on the thing that step exists to provide. The Python floor above is a development requirement, meaning [`scripts/`][scripts-dir] and [`spec/`][spec-dir], and a host that only runs services never has to meet it. `bootstrap.sh` needs `curl` and `tar`, both of which a base install carries or can install without a network tool of its own. `bootstrap.ps1` needs only `tar.exe`, which has shipped with Windows since 1803, and installs its one further dependency, PowerShell 7, itself through `winget`. The one exception is the skills step at the end of a stand-up, which drives the Python installer in [`scripts/`][scripts-dir], and it runs last for exactly that reason: `install-tools` has provided the interpreter by then, and run alone on a host without one it stops and names the tools step as its prerequisite.
Expand Down
2 changes: 1 addition & 1 deletion host-setup/linux/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@ Each script is LF with a shebang, and its executable bit is tracked in git. A fr

The Windows registry has one source because `winget` tracks upstream. Here the distro package trails upstream on `gh`, on `node`, and on `uv`, so a tool comes from whichever source keeps up:

- **The distro**, for `git` and `python`, where apt's own package is current enough.
- **The distro**, for `git`, `python`, and Ripgrep, where apt's own package is current enough.
- **An upstream apt repository**, for `gh`, `node`, `docker`, and `powershell`, and for `dotnet` as a fallback, where upstream publishes one.
- **A released binary into `/usr/local/bin`**, for `jq`, `uv`, and `git-restore-mtime`, where upstream publishes no repository.

Expand Down
48 changes: 45 additions & 3 deletions host-setup/linux/install-tools.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,7 @@ readonly SUDO_TIMESTAMP_TIMEOUT=60

# Managed tools, in dependency order: node asks jq to read the upstream release index.
# The one optional member, powershell, joins the default selection only under --optional.
readonly TOOLS=(git gh jq git-restore-mtime node python uv docker dotnet powershell)
readonly TOOLS=(git gh jq git-restore-mtime node python ripgrep uv docker dotnet powershell)

# Package sets.
# The default set is what a tool needs to be useful, and the optional set is what is useful often enough to name but not always wanted, installed only with --optional.
Expand DownExpand Up@@ -508,6 +508,37 @@ python_install() {
apt_install "${packages[@]}"
}

# --- ripgrep ---

ripgrep_source() { printf 'distro'; }
ripgrep_version() { apt_installed_version ripgrep; }
ripgrep_target() { apt_candidate_version ripgrep; }

ripgrep_download_path() {
local resolved
resolved=$(type -P rg 2> /dev/null || true)
[[ $resolved == /* ]] || return 0
dpkg-query -S "$resolved" > /dev/null 2>&1 || printf '%s' "$resolved"
}

ripgrep_remove_download() {
local resolved
while true; do
resolved=$(ripgrep_download_path)
[[ -n $resolved ]] || break
log "ripgrep: removing downloaded copy at $resolved before the distro package install"
confirm " Remove $resolved?" || die "Declined, downloaded Ripgrep left as it is"
run_root rm -f "$resolved"
[[ $DRY_RUN == true ]] && break
hash -r
done
}

ripgrep_install() {
ripgrep_remove_download
apt_install ripgrep
}

# --- uv ---

uv_source() { printf 'astral-sh/uv'; }
Expand DownExpand Up@@ -870,6 +901,13 @@ tool_note() {
note "python" "optional set not selected: ${PYTHON_OPTIONAL[*]}"
fi
;;
ripgrep)
local resolved
resolved=$(ripgrep_download_path)
if [[ -n $resolved ]]; then
note "ripgrep" "$resolved is an unowned downloaded copy and an install or upgrade removes it before apt installs Ripgrep"
fi
;;
dotnet)
local -a sdks=()
readarray -t sdks < <(dotnet_sdk_packages)
Expand DownExpand Up@@ -950,13 +988,17 @@ report() {
done
}

# Remove a copy of a managed tool found earlier on PATH than $BIN_DIR, so the managed copy is what PATH resolves to afterward.
# Only jq, uv, and git-restore-mtime install as loose binaries outside apt, and uv's companion uvx is unshadowed alongside it.
# Remove a copy of a managed tool found earlier on PATH than its managed destination, so the managed copy is what PATH resolves to afterward.
# Ripgrep migrates an unowned download to apt, while jq, uv, and git-restore-mtime install as loose binaries in $BIN_DIR.
tool_unshadow() {
local tool="$1"
local -a names=()
case "$tool" in
jq | git-restore-mtime) names=("$tool") ;;
ripgrep)
ripgrep_remove_download
return 0
;;
uv) names=(uv uvx) ;;
*) return 0 ;;
esac
Expand Down
Loading