Skip to content

bestdan/agent-guidance

unversioned · 3849a940b232MIT

Injects portable engineering and communication preferences at session start.

agent-guidance

Portable agent preferences, injected at the start of every coding-agent session: every repo, every machine, and cloud sessions where the agent's home directory is not yours.

Status: carrying real content. The two payload files hold the real rules — portable.md everything that holds under any harness, portable-claude.md the rules that name Claude Code machinery. The split between them was projected once, during the migration out of bestdan/dotfiles, from a one-off inventory there that recorded per rule whether it is portable and which harnesses can act on it. That inventory is migration scaffolding with an end date; this repo is the standing source of truth for the rules themselves.

The plugin landed first carrying a sentinel rule per file, so that delivery to every audience could be verified before anything worth losing moved. Both files still declare a GUIDANCE-SENTINEL-* marker, now as a permanent delivery canary rather than as a placeholder.

What it ships

FileReachesCarries
portable.mdevery harnesspreferences any coding agent can act on
portable-claude.mdClaude Code onlypreferences that name Claude Code machinery
inject.shClaude Code and Codex SessionStartprints the hook's JSON contract for the files it is given; both by default
codex/hooks.jsonCodex CLIregisters inject.sh with portable.md only
skills/plugin-deliveryClaude Code, on demandwhy a release may not have reached the session reading it, and what to do
skills/authoringClaude Code, at PR timereads writing_about_code.md and authoring_pull_requests.md
skills/reviewingClaude Code, when reviewing a changereads writing_about_code.md and reviewing.md
dev_docs_layout.mdevery harness, by namehow a repo's dev_docs/ is laid out: directories, naming, front matter
scripts/dev-docs-layout.pyany repo's check suitechecks a repo's dev_docs/ against that layout; exits 1 on a violation

The plugin is the repository root. hooks/hooks.json registers inject.sh on Claude Code's SessionStart; the root plugin.json follows the Agent Plugins layout for harnesses that read it.

The payload and the skill load by different routes on purpose. Preferences have to be in context from the first turn, so they ride the always-on hook. Troubleshooting nobody needs until a symptom appears would only be noise there, so it is a skill the harness loads when the symptom shows up. The one cost is in Versioning: the two can skew.

Install

Claude Code:

claude plugin marketplace add bestdan/agent-guidance
claude plugin install agent-guidance@agent-guidance

Cloud sessions install enabled plugins from account settings, so enable agent-guidance@agent-guidance there as well. No local file can do that for you.

Codex CLI reads the root plugin.json, whose extensions.com.openai.hooks points at codex/hooks.json. That registers the same inject.sh on Codex's SessionStart, whose output contract matches Claude Code's, and hands it portable.md alone, so Codex never sees portable-claude.md. The hook is declared but not yet verified on a live Codex session; until it is, the consuming dotfiles repo also builds ~/.codex/AGENTS.md from portable.md at shell startup, so Codex loses nothing either way.

codex plugin marketplace add bestdan/agent-guidance

Why a hook and not a CLAUDE.md

inject.sh carries the reasoning in its comment block. In short: a harness reads user preferences from its own home directory, a cloud container's home is not yours, and a plugin's SessionStart hook is the one channel that reaches those sessions on the first turn.

Versioning

Neither manifest declares a version, on purpose. The resolved version is the commit, so every push is an update and nothing depends on remembering to bump a string. inject-selftest.test.sh fails if one comes back.

Every push is an update, but no session picks up a new payload on its own. A local machine sits on the old commit until claude plugin marketplace update and claude plugin update run; a cloud environment is frozen at its last build, because a cached setup script is skipped entirely and nothing inside a session can move it. Both failures are silent. The payload is the part that cannot advance by itself — the skill can, which is the skew the end of this section describes.

So the hook appends a provenance block naming the copy that was loaded — the commit for an installed copy, or "a working checkout" under --plugin-dir — with the date its payload was written, and how to refresh it. That does not make a session current; it makes a session able to say what it is running, which is the part that was missing. It is computed at inject time rather than written into the markdown, because a literal in the file reads identically on a fresh copy and a year-old one and so can never signal staleness.

The block also names the plugin root it delivered from, which is a second job: portable.md's three Rules bullets send a harness with no skills to that directory for writing_about_code.md, authoring_pull_requests.md, reviewing.md and dev_docs_layout.md, and the block is what resolves it. dev_docs/conventions.md carries which carriers resolve the root and which do not.

It is not the only route on every audience, and the README should not pretend otherwise: a local Claude session can run claude plugin list, which reports Version: <commit> (verified 2026-09-10). What the block adds there is that the answer is already in context, needing no command and no idea that one should be run. Where it is the only route is Codex, whose generated ~/.codex/AGENTS.md reads identically however old it is.

The payload and the skill can skew, and neither one dates the other. Auto-update hot-loads newer skills into a running process while leaving the registry — and so the bundled hooks — pinned to the old install (#52218). A session can therefore be reading a current skills/plugin-delivery while running a stale portable.md, or the reverse. The provenance block dates the payload and only the payload; do not infer the payload's age from anything the skill says.

Why a release stalls in the first place, the upstream reports, the dead ends not worth re-walking, and the cloud procedure are all in skills/plugin-delivery.

Tests

scripts/run-tests.sh

inject.test.sh proves the wiring; inject-selftest.test.sh proves each of those assertions fails when its wiring is broken. skills.test.sh does the same job for skills/, where the failure is quieter still: a skill whose front matter does not parse is skipped rather than reported, so it is simply never offered. skills-selftest.test.sh is its tripwire, and it is not ceremony — two of that suite's three assertions shipped vacuous, passing on the exact regressions they named, and review caught them rather than the suite. The mutations now live in CI so a future edit cannot quietly restore that.

prose-check.test.sh is the odd one out: it checks content, not wiring. It runs scripts/prose-check.py over every tracked markdown file and fails on a paragraph carrying more than one em-dash interruption, which is a rule from writing_about_code.md that prose alone did not hold. The same script reports how many sentences run over the 25-word cap, and that half never fails. Why the two rules are treated differently is in dev_docs/conventions.md.

dev-docs-layout.test.sh checks content too. It pins each rule of scripts/dev-docs-layout.py at its boundary on fixtures, then runs the checker over this repo's own dev_docs/, so a file that breaks dev_docs_layout.md fails CI here the way it will in any repo that wires the checker in.