Dev env setup
Skill mickzijdel/dev-hooks/plugins/dev-hooks/skills/dev-env-setup
Audit a repo against an opinionated dev-environment standard (mise pinning tools, an hk pre-commit hook running linters/tests + gitleaks, a GitHub Actions workflow that mirrors those checks, and project docs — README.md + CLAUDE.md recording pinned package versions) and set it up or upgrade it. Use when a repo is missing the standard setup, when the dev-env-reminder hook flags a gap, when the user mentions hk/mise/gitleaks/"my dev setup", when starting a new repo, or to backfill every standard-tracking repo after a version bump (fleet mode). Tracks a standard version via DEV_ENV_VERSION in mise.toml and upgrades behind repos using references/upgrade-guide.md.From its SKILL.md
npx -y skills add mickzijdel/dev-hooks --skill dev-env-setupAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
31.3 KB, ~8.3k tokens by cl100k_base, as published. Nobody here has run it
dev-env-setup
Bring a repo up to an opinionated dev-environment standard and keep it there. It covers both Python and Rails (Ruby) project types.
The standard (v22)
A repo is compliant at v22 when it has all of:
mise.toml— tools pinned (hk,pkl, stack tool,gitleaks,zizmor,actionlint,nodefor jscpd),[settings] lockfile = trueandminimum_release_age = "4d", and the[env]version stampDEV_ENV_VERSION = "22".mise.lock(committed) — reproducible, checksum-verified tool installs. See "Lockfile & supply-chain verification"..jscpd.json— duplication config (minTokens 70,threshold 0, path excludes underignore— neverignorePattern, inert in jscpd v5).scripts/run-jscpd.sh(added in v14) — the shared jscpd runner holding the version-cooldown policy; both the hk step and CI's audit job call it (CI with--require) so the two gates can't drift. Copied verbatim from the template (repo formatters may re-indent it; never hand-edit the logic).hk.pkl— per-stack linters plus the dead-code + duplication audits, theexec-bit-scriptsgate, theactionlint+zizmorGitHub Actions checks,gitleaks, andcheck-added-large-files, in onelintersmapping shared by thepre-commit/fix/checkhooks. Where a hand-rolled step exactly matched an hk built-in it's now the built-in (gitleaks,check_added_large_files,zizmor,actionlint, and — in the shell stack, whereruffis a mise tool —ruff/ruff_format); stacks that run a tool through their package manager (uv run ruff,npx prettier,bundle exec rubocop,golangci-lint) keep custom steps, as the bare-command built-ins would bypass the project-pinned version.- Executable-bit gate (added in v15) — the hk
exec-bit-scriptsstep + a CI lint-job mirror fail when any tracked shebang file is index mode100644(a fresh clone/plugin install would get a script that dies with exit 126). Fix:git update-index --chmod=+x <file>. See "Executable bits on shipped scripts" inreferences/standard.md— including why the check is a single awk (hk's internal shell aborts onwhile readinside$(...)). .gitleaks.toml— allowlists gitignored runtime/secret paths so the whole-treegitleaks dirscan doesn't fail on a local.env/log/. See "gitleaks whole-tree allowlist"..github/workflows/ci.yml— mirrors the hk checks plus anauditjob; gitleaks runs as the MIT-licensed CLI via mise (nevergitleaks/gitleaks-action, which needs a paid license on org repos).- SHA-pinned actions + read-only token (added in v16) — in every file under
.github/workflows/(not justci.yml), everyuses:pins a full commit SHA with the release tag in a trailing comment (owner/repo@<sha> # vX.Y.Z; tags are mutable and a takeover repoints them — tj-actions, Trivy), and each workflow declarespermissions: { contents: read }so a compromised step can't write (a deploy that needspages/id-token: writekeeps its own wider block). Checker-enforced across all workflow files (has_sha_pinned_ci). See "Keeping GitHub Actions current" and the [[github-actions]] skill's security checklist. - GitHub Actions checks: actionlint + zizmor (added in v18) — hk steps (
Builtins.actionlintandBuiltins.zizmor, glob-gated to workflow/action.ymlfiles) plus one CIactions-lintjob run both over every workflow. actionlint catches correctness — schema/typo errors, bad${{ }}expressions, undefinedneeds:(run with-shellcheck=so its shellcheck-of-run:pass doesn't double up with the dedicated shellcheck step). zizmor catches security — credential persistence, template injection, over-broadGITHUB_TOKENpermissions. As part of zizmor'sartipackedfinding, everyactions/checkoutsetspersist-credentials: false(keeps the repo token out of.git/configon the runner). Needszizmor+actionlintinmise.toml; checker-enforced (has_zizmor,has_actionlint). See the [[github-actions]] skill. README.md+CLAUDE.md— both present, both recording current key-package versions. See "Project docs (README + CLAUDE.md)".- Dependency cooldown — Python repos pin a 4-day uv cooldown (checker-enforced); other stacks get the same window via their package manager (recommended). See "Dependency cooldown (supply-chain)".
Recommended, advisory (added v19): a mise-driven dev container. Having a .devcontainer/
is optional and never gates compliance — but if a repo ships one, it must be mise-driven (the
image installs only mise + OS libs; mise install in the postCreate setup.sh provisions the
toolchain from the bind-mounted mise.toml/mise.lock — no hardcoded ruby:/node: base, no
npm install -g pnpm). The checker flags drift advisorily (devcontainer_mise_driven=0). Scaffold
from references/templates/devcontainer/; see "Dev container (mise-driven, advisory)" in
references/standard.md and the [[dockerfile]] skill.
The full specification — per-artifact requirements, the per-stack linter/audit matrix, the
jscpd version policy and exclusion gotchas, extensionless-script (shebang) linting, the
flay-vs-jscpd rationale, the extra requirements for Claude Code plugin / script-bundle repos,
and the .gitignore gotchas — lives in
references/standard.md. Read it before writing or editing any of
the standard's files.
Applicability: the standard applies to a repo with a recognized stack
(pyproject.toml/Gemfile/package.json) or any scripts (*.sh, bin/*, *.py) —
including Claude Code plugin repos. A prose/skills-only repo with no scripts is exempt.
Why this shape (keep / drop vs. Nate's setup)
This is trimmed from Nate Berkopec's dev-env-setup
to the subset this standard actually uses. Kept: mise-as-tool-manager, hk parallel pre-commit,
ruff/pytest + rubocop/rails-test, CI-mirrors-hk. Added: gitleaks (defense-in-depth with
[[env-to-fnox]]) and shellcheck/shfmt for shell/plugin repos. Dropped (and why) lives in
references/dropped-from-nate.md — revisit those before
proposing additions.
Workflow
-
Audit. Run the checker and read its output:
bash "$CLAUDE_PLUGIN_ROOT/skills/dev-env-setup/scripts/dev_env_check.sh" .(
$CLAUDE_PLUGIN_ROOTis set when run as a plugin; otherwise use the skill dir path.) It printsstatus=not-applicable|needs-setup|needs-upgrade|compliant, plusstack,repo_version, andcurrent_version.not-applicableorcompliant→ stop; tell the user, change nothing.needs-setup→ go to step 2 (fresh setup).needs-upgrade→ go to step 4 (upgrade).
-
Fresh setup — detect the stack (the checker reports it) and confirm with the user if ambiguous (e.g. a plugin that is also a Python package). Recognized stacks:
pyproject.toml→python,Gemfile→ruby,package.json→javascript,go.mod→go; scripts-only repos →shell. -
Fresh setup — write the config from
references/templates/for the stack: copymise.<stack>.toml→mise.toml,hk.<stack>.pkl→hk.pkl,ci.<stack>.yml→.github/workflows/ci.yml, and (all stacks).jscpd.json,run-jscpd.sh→scripts/run-jscpd.sh(chmod +x— and if the repo hascore.fileMode = false,git update-index --chmod=+x scripts/run-jscpd.shafter staging, or the v15 exec-bit gate will flag it; the shared jscpd runner both the hk step and CI call), and.gitleaks.toml→ repo root (the latter keepsgitleaks dir's whole-tree scan from failing on gitignored.env/log//*.key— see "gitleaks whole-tree allowlist"). Adjust specifics (default branch name, Python version). Extensionless CLIs likebin/fooare covered automatically by the shell template's shebang companion steps — no hk-glob surgery; just point[tool.ruff] extend-includeat the Python ones (see the extensionless note inreferences/standard.md). The templates already includeDEV_ENV_VERSION, gitleaks, and the audit checks. Add the audit deps the templates assume:vultureto the Python dev group; for Ruby (v17) the dev-group gemsflay,debride,herb,brakeman,bundler-audit,fasterer,database_consistency(allrequire: false) plus a rubocop testing pluginrubocop-minitest(orrubocop-rspec) and the house-cops gemrubocop-mick(github: "mickzijdel/rubocop-mick",require: false), both enabled via.rubocop.yml'splugins:key (v22 addsrubocop-mickto every Ruby repo; theplugins:line alone activates its cops — the gem ships their defaults, noinherit_gem:) — omakase repos stop there (omakase already bundles+disables rails/performance, so re-adding them is inert); a plain-rubocop repo also addsrubocop-rails+rubocop-performance— andstrong_migrationsas a runtime gem in the main Gemfile (ungrouped, notrequire: false) — its initializer references theStrongMigrationsconstant in every env, so a:development-only gem crashes the test/prod boot (mise pullsnodefor jscpd +herb lint, which run vianpx, on all jscpd stacks incl. Ruby); JS repos need no extra audit deps (jscpd runs vianpx,nodeis already the stack tool). For a Ruby repo, also copy.fasterer.yml→ repo root — itsexclude_pathskeep fasterer off the bundler-cachevendor/bundlegems (debride/flay are scoped in the ci/hk commands instead). For a Go repo, also copygolangci.go.yml→.golangci.yml(the v2 configgolangci-lint run/fmtboth read); no extra audit deps are needed — golangci-lint'sunusedcovers dead code (so no vulture), andgolangci-lintitself is mise-pinned. The Go template carriesshellcheck/shfmtfor any shipped shell script (it shipsscripts/run-jscpd.sh). For a shell/plugin repo, also add the [readoc]-style dev project (pyproject.plugin.toml→pyproject.toml, fill in the name) and atests/suite (test_scripts.example.pyas a starting point) so every bundled script is exercised, and give each Python script theuv run --scriptshebang + PEP 723 block. Before writing the workflow, pin every GitHub Action to its current latest version — the template versions are a snapshot and go stale. See "Keeping GitHub Actions current". After the tools are written, generate the lockfile (mise install && mise lock) and commitmise.lock— see "Lockfile & supply-chain verification".Offer a mise-driven dev container (opt-in). Mention the optional
.devcontainer/scaffold and set one up only if the user wants it — don't add Docker files unprompted. If they do, copyreferences/templates/devcontainer/*→.devcontainer/and adapt the# KNOB:markers for the detected stack: the base image (match the project's prod image's Debian release — invariant 1), the OS build deps, the accessory service(s), and the dep-install + DB step insetup.sh. Keepsetup.shexecutable in the git index (git update-index --chmod=+x .devcontainer/setup.sh), and copytasks.json.example→.vscode/tasks.jsonif the project uses the dev-server/DB tasks. See "Dev container (mise-driven, advisory)" inreferences/standard.md. -
Upgrade — apply the guide. Read
references/upgrade-guide.mdand apply every section strictly newer thanrepo_version, in order. Then setDEV_ENV_VERSIONinmise.tomltocurrent_version. (Existing v0 repos — hk/mise/CI that predate this standard — add the stamp + gitleaks + large-file + the dead-code/duplication audits per the v0 → v1 section.) Also audit the existing workflow's Actions and bump any that are behind latest (same recipe below). -
Ensure project docs (README.md + CLAUDE.md) — required from v3. The checker reports
has_readme/has_claude. If either is missing (or, on a substantive setup/upgrade, looks stale), dispatch a subagent (viaTask) to write it rather than doing it inline — see "Project docs (README + CLAUDE.md)" below for the exact brief. The docs must record the current versions of the project's key packages, read from the manifests/lockfiles you just set up (not from memory). -
Repo ownership. If the repo is the user's own, commit
mise.toml/hk.pkl/ci.yml. If it is someone else's repo you only contribute to, do not commit them — addhk.pklandmise.tomlto.git/info/excludeinstead. (This mirrors the dev-env-reminder hook's ownership rule.) -
Nudge env-to-fnox if secrets are in use. If the checker reports
suggests_fnox=1(the repo has a non-empty.env/.env.local, a Rails master key, or source references to credentials, and nofnox.tomlyet), surface it in the final report: recommend running the [[env-to-fnox]] skill to migrate the plaintext secrets to fnox + Bitwarden Secrets Manager. Advisory only — don't auto-run it, and it never blocks compliance.Likewise, if the checker reports
devcontainer_mise_driven=0(a.devcontainer/exists but has drifted from the mise toolchain — hardcoded base, nodesource, global pnpm, …), surface that in the report and offer to fix it per the v18 → v19 upgrade-guide steps. Advisory only — it never blocks compliance. -
Verify (do this, don't assume):
mise trustfirst (gate on the user). A freshly written or clonedmise.tomlis untrusted, somise installfails with "Config files … are not trusted" until you runmise trustonce for the repo. Trusting a config lets it run arbitrary task/env code, so ask the user to approve before runningmise trust— don't auto-trust.mise trust # one-time, only after the user approves (see note above) mise install # provision hk, pkl, gitleaks, etc. (verifies against mise.lock) mise lock # ensure mise.lock carries all-platform checksums; commit it hk install # install the git hooks hk run check # all steps, including gitleaks, must pass bash "$CLAUDE_PLUGIN_ROOT/skills/dev-env-setup/scripts/dev_env_check.sh" . # → status=compliant bash "$CLAUDE_PLUGIN_ROOT/skills/dev-env-setup/scripts/check_action_refs.sh" .github/workflows # every uses: pin resolvesConfirm
mise.lockis present and committed, that.gitleaks.tomlis at the root (has_gitleaks_config=1), and thatREADME.md+CLAUDE.mdexist (has_readme=1 has_claude=1). A good.gitleaks.tomlsmoke test: with a local.env/log/present,hk run checkmust still pass (no leaks found). Run the project's own tests too (uv run pytest/bin/rails test/go test ./...). Report real output.If you scaffolded a dev container, also verify it boots — don't assume:
docker compose -f .devcontainer/compose.yaml build # image builds # then run the postCreate inside the container, e.g.: docker compose -f .devcontainer/compose.yaml run --rm app .devcontainer/setup.sh # → mise install resolves the toolchain, deps install, the container comes up green bash "$CLAUDE_PLUGIN_ROOT/skills/dev-env-setup/scripts/dev_env_check.sh" . # → devcontainer_mise_driven=1
Fleet mode ("backfill all my repos after a bump")
A standard bump only matters once every tracking repo carries it — backfill the fleet right after bumping, while the template changes are fresh. Mirror [[dependency-upgrade]]'s fleet cadence, with the dev-env twists below:
- Enumerate live + confirm. Run the roster script — never a remembered repo list (any
memory of the fleet holds per-repo quirks at best; it is not the roster source):
It prints one line perbash "$CLAUDE_PLUGIN_ROOT/skills/dev-env-setup/scripts/fleet_roster.sh" # or: fleet_roster.sh ROOT ...DEV_ENV_VERSION-stamped repo — path,version,branch,dirty,behind— plus a summary. Show the user the roster and confirm the target set before touching anything. To retire an abandoned repo (or a fork you don't own) from discovery without touching the repo itself, add its basename toreferences/fleet-ignore.txt— the roster skips listed names. - Ask disposition up front, before upgrading anything: (a) which repos to exclude entirely; (b) what to do with dirty repos — the rule has varied round to round (upgrade-but-don't-commit-and-report one time, skip-entirely another), so ask, don't assume. And if the bump has plausible companion tooling (the actionlint-next-to-zizmor kind), propose it now — folding a second tool in after the fleet has been swept means a second sweep.
- Canary first. Upgrade one repo by hand end-to-end (upgrade → verify → commit) before fanning out; a template bug or an environment gotcha caught on the canary costs one repo, not the whole fleet.
- Fan out one isolated agent per repo (worktree isolation), each running the single-repo
Workflow above: apply only the upgrade-guide sections strictly newer than that repo's
version, verify (checker →status=compliant,check_action_refs.sh, run the changed command locally), commit with a consistent message, and push to the repo's own default branch — the roster'sbranch=field tells you; several repos are onmaster, notmain. - Report one line per repo: upgraded / skipped / deferred, pushed (or left uncommitted, for the dirty-repo disposition that asks for it), and any deviation from the canary recipe.
Project docs (README + CLAUDE.md)
From v3, a compliant repo has both a README.md and a CLAUDE.md at its root, and both record the current versions of the project's key packages (main framework, Tailwind, Bootstrap, etc.). Humans read the README; Claude reads CLAUDE.md — and both drift from the manifests fast, so the version numbers are the point.
When a doc is missing, dispatch a subagent to write it (use the Task tool — don't write
these inline; doc-writing is a self-contained job and the README in particular benefits from a
focused pass). Give the subagent this brief:
- Read the real versions first. Pull key-package versions from the resolved manifests/
lockfiles in this repo —
uv.lock/pyproject.toml,Gemfile.lock,package.json— never from memory (training data goes stale). For a Rails app that means Rails, Ruby, and any CSS/JS framework (Tailwind, Bootstrap, Hotwire/Turbo); for Python, the framework + Python version; for a JS app, the framework + Tailwind/Bootstrap. - README.md — use the [[github-readme]] skill for structure and tone. Include a short "Built with" / tech-stack section listing those key packages with their pinned versions.
- CLAUDE.md — project instructions for Claude: how to run the app, test, and lint (mirror the
hk/mise setup just written), plus the same key-package-versions list so Claude doesn't guess.
If a
/initexists in the environment it's a fine starting point, but the versions must come from the manifests. - Keep both in sync with each other and with the manifests; updating both is preferred.
If both docs already exist, leave them — only refresh the version numbers if they're visibly
stale relative to the manifests. The latest-deps-reminder hook keeps them current on
subsequent manifest edits, so this skill only bootstraps them.
Keeping GitHub Actions current
From v16 every uses: is pinned to a full commit SHA with the release tag in a trailing
comment — owner/repo@<40-hex-sha> # vX.Y.Z. Tags are mutable, so an action takeover can
repoint @v4 to malicious code that every downstream run picks up silently (tj-actions, Trivy);
a SHA can't be moved. The pins in references/templates/ci.*.yml are a snapshot and drift, so
whenever you write or touch a workflow (fresh setup and upgrade) bump every action to its
latest release SHA. See the [[github-actions]] skill for the full security checklist and the
fleet-wide bump.
The mechanical bump is pinact (install with mise use -g pinact): pinact run pins any
tag refs to SHAs with version comments, pinact run -u also updates pinned SHAs to the latest
release. Without pinact, resolve a single action by hand:
a=actions/checkout
tag=$(gh release view --repo "$a" --json tagName -q .tagName) # e.g. v7.0.0
sha=$(gh api "repos/$a/commits/$tag" --jq .sha) # dereferences annotated tags
echo "uses: $a@$sha # $tag"
Do this for every action a repo uses. If gh/pinact is unavailable or unauthenticated, say
so and ask the user — never guess a SHA or a version.
Always verify the pins before finishing. The bundled checker resolves each pin's # vX.Y.Z
comment on the remote and fails if the tag is missing or its commit doesn't match the pinned
SHA (a lying / stale pin), and also flags any ref left as a mutable tag:
bash "$CLAUDE_PLUGIN_ROOT/skills/dev-env-setup/scripts/check_action_refs.sh" .github/workflows
# → "N ok, 0 unresolved" and exit 0; any FAIL line is a pin that lies or won't resolve.
(The ci-action-ref-reminder hook nudges you to run this whenever a workflow is edited.)
Caching npm in CI
actions/setup-node ships a built-in npm cache (with: { cache: npm }) and the JS template
(ci.js.yml) uses it. But in a polyglot repo where node is provisioned by mise
(jdx/mise-action, so the workflow has no setup-node step), that cache isn't available —
every npm ci does a cold, network-bound install. Add an explicit cache, keyed on the
lockfile, to each job that runs npm ci (setup-node's cache is per-job too, so the
fan-out is expected):
- name: Cache npm downloads
uses: actions/cache@v4
with:
path: ~/.npm # npm's download cache — the same dir setup-node caches
key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
restore-keys: npm-${{ runner.os }}-
Point hashFiles at the real lockfile — package-lock.json at the root, or
frontend/package-lock.json in a backend+frontend layout. Restoring ~/.npm skips the
network on a warm key (npm ci still rebuilds node_modules, but from cache). Relatedly,
keep independent test suites in separate jobs (e.g. backend-test + frontend-test)
rather than one sequential job, so a failure in one suite doesn't short-circuit the run
before the other reports.
Lockfile & supply-chain verification
The mise toolchain is pinned reproducibly via a committed mise.lock plus
[settings] lockfile = true in mise.toml. Tools stay spec'd "latest"; the lock records what
that resolved to, so every machine and CI install the same artifacts and verify them.
Three layers (the latter two apply per the tool's aqua-registry entry — most registry tools, incl.
gitleaks, are aqua-backed):
- Checksums (always) — mise stores each artifact's SHA per OS/arch in
mise.lockand re-checks on every install; a mismatch fails the install. Defends against tampered/swapped downloads. - Cosign signatures — verifies the artifact was signed by the project's expected (keyless/OIDC) identity. Defends against a forged release.
- SLSA provenance / attestations — verifies the artifact was built by the expected pipeline from the expected source. Defends against a compromised build system.
Day-to-day flow:
| Action | Command | Effect |
|---|---|---|
| Reproduce | mise install | installs exactly what mise.lock says, verifying checksums |
| Record | mise lock | backfills all-platform checksums for the current specs |
| Upgrade | mise upgrade | re-resolves "latest", rewrites mise.lock — commit the diff |
So upgrades are explicit (mise upgrade + a reviewable mise.lock diff) rather than silent drift,
while the spec stays "latest" so the latest-deps-reminder hook still nudges toward current
versions. Commit mise.lock alongside mise.toml.
Gap: this covers only mise-managed tools. The npx jscpd audit step isn't mise-pinned (jscpd
5.x ships only as npm-distributed Rust platform packages — no clean mise backend); instead it
tracks latest on a 4-day cooldown floored at v5 (see the jscpd version-policy note in
references/standard.md). Project deps lock separately via uv.lock / Gemfile.lock.
Tool-upgrade cooldown (v13): minimum_release_age = "4d" in [settings] extends the 4-day
supply-chain window to mise upgrade. When re-resolving "latest", mise only considers tool
versions published at least 4 days ago, giving the community time to catch and yank a malicious
release before it lands here. mise install is unaffected — it always reproduces the exact
version pinned in mise.lock.
Dependency cooldown (supply-chain)
Hold off on freshly-published package versions for 4 days before resolving them. Most malicious
releases (the 2026 Shai-Hulud npm waves, the axios / litellm incidents) are detected and yanked
within hours-to-days of publication, so a short cooldown means you are never the one who installs a
compromised version in the window before the community catches it. The standard already applies this
to its own tooling — the jscpd audit step resolves via npx --before=<4 days ago> (see the jscpd
version policy in references/standard.md) — and this section extends the same 4-day window to a
repo's own runtime / dev dependencies.
Set it per repo so every developer and CI run enforce the same window, and (if not already set)
once machine-wide as a default. An existing lockfile (Gemfile.lock, uv.lock,
package-lock.json, pnpm-lock.yaml, yarn.lock) is honoured as-is, so adding a cooldown never
disturbs already-locked versions — it only gates new resolutions.
Repo guard — pick the row matching the repo's package manager (detect by lockfile):
| Manager | Where | Setting (4-day window) |
|---|---|---|
| uv (Python, ≥ 0.9.17) | pyproject.toml [tool.uv] | exclude-newer = "4 days" |
| Bundler (Ruby, ≥ 4.0.13) | Gemfile source line | source "https://rubygems.org", cooldown: 4 |
| npm (≥ 11.10.0) | .npmrc | min-release-age=4 |
| pnpm (11+) | pnpm-workspace.yaml | minimumReleaseAge: 5760 (minutes) |
| yarn (Berry 4.10+) | .yarnrc.yml | npmMinimalAgeGate: 5760 (minutes — use a raw count; the 4d suffix has a parse bug) |
| pip (≥ 26.1, no uv) | per install | --uploaded-prior-to=P4D (or pip.conf [install]/[global]) |
Only uv is checker-enforced for Python repos (see "The standard"); the rest are recommended
and applied by the setup/upgrade flow when the matching lockfile is present. exclude-newer accepts
a rolling duration ("4 days" / "P4D") or an absolute date — a duration is the right choice here
so the window moves forward with the current date.
Global guard (if missing) — a machine-wide default so a repo that hasn't opted in still gets a floor:
| Manager | Command | Writes |
|---|---|---|
| uv | add exclude-newer = "4 days" to ~/.config/uv/uv.toml | user uv config |
| Bundler | bundle config set --global cooldown 4 | ~/.bundle/config (BUNDLE_COOLDOWN) |
| npm | npm config set min-release-age 4 --location=user | ~/.npmrc |
| pnpm | pnpm config set minimumReleaseAge 5760 --global | ~/.config/pnpm/config.yaml (pnpm 11 moved global settings to YAML) |
| yarn | yarn config set --home npmMinimalAgeGate 5760 | ~/.yarnrc.yml |
The per-repo setting takes precedence over the global one, so a repo can still override it — e.g.
drop to 0 (or cooldown: 0, exclude-newer unset) to take a fresh release immediately during an
urgent upgrade. For one-off exceptions inside the window, uv has exclude-newer-package, pnpm
minimumReleaseAgeExclude, and yarn npmPreapprovedPackages (npm has no per-package exclude yet).
Sources: RubyGems blog, Jun 2026 ·
uv exclude-newer ·
minimum release age across npm/pnpm/yarn (gist) ·
cooldowns.dev
gitleaks whole-tree allowlist (.gitleaks.toml)
The hk gitleaks step (["gitleaks"] = Builtins.gitleaks) runs gitleaks dir, which scans the
entire working tree — dir has no respect-gitignore flag, so it reads gitignored files too.
Without an allowlist, a local .env, log/, tmp/cache/, or a Rails
config/credentials/*.key makes the scan fail on those gitignored artifacts, blocking every
commit and keeping hk run check red (none of them are tracked, so CI — whose gitleaks git
job scans history — still passes, masking the problem).
references/templates/.gitleaks.toml is the fix: it [extend]s the default ruleset
(useDefault = true) and [allowlist]s the gitignored runtime/secret paths (.env, log/,
tmp/, .venv/, node_modules/, vendor/, config/credentials/*.key). gitleaks auto-loads
.gitleaks.toml from the scan root, so the hk builtin and CI's gitleaks git job both pick it
up with no --config flag. The allowlist is path-scoped to gitignored locations only, so
a secret hardcoded in app//source is still caught.
Caveat: because CI's gitleaks job reads the same file, a secret force-added (git add -f)
into one of these paths wouldn't be caught by CI either. That's an accepted trade-off — the paths
are gitignored, so defeating it takes a deliberate -f against .gitignore. The complementary
path is to get the plaintext secret out of the repo entirely: when the checker detects secrets in
use without a fnox.toml, it emits suggests_fnox=1 and the setup/upgrade report nudges the
[[env-to-fnox]] skill.
Notes
- The version stamp is the single source of truth in
references/../VERSION; the reminder hook reads the same file, so a repo on an older stamp gets flagged automatically. - Never auto-run this from a hook — it's invoked by the user (or offered by the reminder), and it writes commit-tracked config, so confirm before committing.
- The standard ships no
.gitignoretemplate — Claude's defaults are usually right, but check the gotchas inreferences/standard.md(".gitignore gotchas"): keep.envignored AND allowlisted in.gitleaks.toml, commit lockfiles andmise.toml, keep.venv/ignored.
What ships with it: 37 files
206.7 KB alongside SKILL.md, 6 of them executable
references/
- dropped-from-nate.md5.9 KB
- fleet-ignore.txt341 B
- standard.md34.9 KB
- templates/ci.go.yml5.9 KB
- templates/ci.js.yml4.4 KB
- templates/ci.python.yml5.6 KB
- templates/ci.ruby.yml7.7 KB
- templates/ci.shell.yml6.6 KB
- templates/devcontainer/compose.yaml2.5 KB
- templates/devcontainer/devcontainer.json350 B
- templates/devcontainer/Dockerfile.dev5.0 KB
- templates/devcontainer/.gitignore199 B
- templates/devcontainer/setup.shruns2.7 KB
- templates/devcontainer/tasks.json.example1.4 KB
- templates/.fasterer.yml345 B
- templates/.gitleaks.toml1.7 KB
- templates/golangci.go.yml537 B
- templates/herb.ruby.yml1.3 KB
- templates/hk.go.pkl4.5 KB
- templates/hk.js.pkl3.8 KB
- templates/hk.python.pkl4.9 KB
- templates/hk.ruby.pkl7.3 KB
- templates/hk.shell.pkl7.0 KB
- templates/.jscpd.json323 B
- templates/mise.go.toml1.8 KB
- templates/mise.js.toml1.7 KB
- templates/mise.python.toml1.4 KB
- templates/mise.ruby.toml1.5 KB
- templates/mise.shell.toml1.6 KB
- templates/pyproject.plugin.toml1.5 KB
- templates/run-jscpd.shruns1.9 KB
- templates/test_scripts.example.pyruns1.2 KB
- upgrade-guide.md51.4 KB
scripts/
- check_action_refs.shruns7.5 KB
- dev_env_check.shruns17.2 KB
- fleet_roster.shruns3.0 KB
- VERSION3 B