Routes agent-instruction content to the surface that loads it at the right moment.
A convention that only matters when someone edits a .cs file should not be paid for in every
session. Claude Code already provides the machinery to fix that: path-scoped rules and nested
instruction files. Using it correctly is harder than it looks, and every way of getting it
wrong fails silently. This plugin finds the content worth moving, validates the move mechanically
before proposing it, and executes it behind a human gate.
| Skill | Verb contract | What it does |
|---|---|---|
/instruction-placement:audit |
Read-only findings report | Sweeps the instruction layer and ordinary markdown, classifies candidates, emits a diffable findings artifact |
/instruction-placement:realign |
Per-item human-gated apply | Executes accepted findings; the only mutating surface, with no blanket-approve path |
/instruction-placement:check |
Deterministic pass/fail gate | Verifies every rule glob resolves and the always-loaded index is current |
/instruction-placement:setup |
Verify prerequisites, report config | Confirms the index target is one Claude Code will actually read, and resolves every setting with its source |
/instruction-placement:delta |
Read-only movement report | Re-runs the audit and reports only what changed since last time, above a noise budget, suppressing every finding the operator already declined |
Run setup first on a new repository. It catches the one failure the other gates cannot see, an
index Claude Code never loads. Then audit. Nothing changes until you accept a specific finding in
realign. Use delta for repeat runs, so a re-audit costs attention proportional to what actually
moved. Wire check
into CI beside the linters.
This plugin is a participant in the marketplace's lifecycle artifact protocol
(reference/artifact-protocol.md, byte-identical to the canonical
copy) and resolves every path through its topic-docs binding
(reference/topic-docs.md). Three homes, and the difference between them
is what a decline costs to make twice:
| What | Default location | Tier | Crosses checkouts? |
|---|---|---|---|
Findings (audit writes, realign updates statuses) |
.work/instruction-placement/<branch-slug>/findings.md |
Memory, branch-keyed | No |
Spine baseline (delta reads and captures) |
.work/instruction-placement/<branch-slug>/baselines/spine-baseline.md |
Memory, branch-keyed | No |
Declined findings (realign offers, audit and delta read) |
.claude/instruction-placement.md |
Tracked, three cascade layers | Yes, once committed |
The first two are evidence and a diff spine: recomputed by the next run, and worthless outside the
checkout that produced them. The third is the operator's judgment, which is expensive to reproduce
and worthless inside only one checkout, so it rides a tracked file, where git carries it to every
worktree whose branch holds the commit. Its entries follow the marketplace's finding-suppression
contract; the keys and layer rules are owned by
reference/consumer-config.md, and what the memory-tier files
contain by context/findings-artifact.md.
Four facts make the naive version of this migration actively harmful, and each one shapes the design.
The full evidence, including a first-party repro, is in
context/verified-mechanics.md.
A rule without paths: costs exactly what CLAUDE.md costs. Unscoped rules load at launch with
the same priority as .claude/CLAUDE.md. Moving a section into .claude/rules/ without a glob is
bookkeeping, not a saving. The glob is the product.
Everything that defers is invisible inside subagents. Measured on Claude Code 2.1.238: a subagent
dispatched after its parent had loaded a nested CLAUDE.md, a nested AGENTS.md, and a
path-scoped rule saw none of them. In a repository where the file editing is delegated, a naive
demotion puts the C# conventions out of reach of the agent editing C#. This is why every accepted
move regenerates an always-loaded index of deferred surfaces, the one thing that does reach a
subagent, and that turns an invisible rule into one an ordinary Read can fetch.
Path scoping triggers on read, not write. Creating a new file is not a read, so a rule governing how new files are made would not fire in the case it exists for. Creation-governing content is denied the path-scoped destination structurally, not by judgment.
A nested AGENTS.md with no CLAUDE.md shim is never loaded. Claude Code reads CLAUDE.md, not
AGENTS.md, at every level of the tree. The shim is a correctness requirement; writing the
AGENTS.md alone produces a file that reviews as correct and reaches nothing.
An earlier version of this README claimed that path-scoping improves adherence, that a convention arriving when a matching file is read is followed more reliably than the same text buried in a large always-loaded file. That claim was measured and not supported, so it has been removed rather than softened.
Across 32 trials at two bloat levels, using a realistic 251-line always-loaded file and an extreme
1,927-line one nearly ten times the official 200-line guidance, a clear convention was followed
100% of the time in both arms. Full method, caveats, and the ceiling effect the run hit:
evals/adherence-results.md.
Weigh a migration on context cost and on the promote lane. Do not expect your instructions to be obeyed better afterwards.
Some content is never proposed for demotion, however path-local it looks: irreversible actions, secret handling, data integrity, external publication, legal and compliance obligations, and bounds on the agent's own authority.
The reasoning is asymmetric consequence. Demotion trades guaranteed presence for conditional presence. When a style convention goes missing the cost is a nit in review; when a safety rail goes missing the cost is unbounded. The index mitigates absence but cannot guarantee attention. Injection is automatic, a pointer is discretionary, so safety rails stay where injection reaches them.
audit reports what it held back and why, so the exclusion is visible. realign has no code path
that can apply one. This is the single place where an operator instruction does not carry.
.claude/rules/ is Claude-only. AGENTS.md is read by other coding agents. The plugin's default
posture keeps shared content portable: subtree conventions go in a nested AGENTS.md with a
CLAUDE.md shim beside it, and the generated index lives in the root AGENTS.md when one exists.
One semantic difference is deliberately not papered over: other agents resolve AGENTS.md
nearest-wins, while Claude concatenates the whole ancestor chain. Subtree content is therefore
written as additive and self-contained, and a candidate that only makes sense as an override is
reported rather than moved.
Placement is one question about an instruction, and it is not the only one. Where a sibling plugin owns a neighbouring question, route to it rather than bending this rubric. Each is optional: when it is not installed, keep the observation in the report rather than judging it here.
| Question | Owner |
|---|---|
| Is this instruction still needed by the current model? | claude-config's instruction audit |
| Is the memory layer healthy: size, index integrity, conflicts? | claude-memory's audit |
| Does this whole document earn its existence? | docs-hygiene's derivability audit |
| Is this file structured well for progressive disclosure generally? | docs-hygiene's progressive-disclosure audit |
| Is the same content repeated across several files? | docs-hygiene's single-source-of-truth extraction |
| Is the prose too long or too noisy? | docs-hygiene's compression and noise audits |
The dividing line: those audits ask whether a piece of content is good, needed, or duplicated. This plugin asks only where it should live, and owns the one capability none of them has, the validated move, including glob derivation and the index that keeps the result reachable.
These routes are operative, not decorative. When a candidate raises one of these questions, the audit invokes the named skill via the Skill tool if its plugin is installed, and otherwise keeps the observation in the report as an unjudged note. A routed candidate is reported as routed, never silently dropped, and never re-classified as a placement finding just because the sibling was missing. A section can be both misplaced and duplicated; routing one question does not cancel the other finding.
Two rungs of this plugin's own ladder are deliberately report-only. Content that a linter should enforce, and content that should become a skill, are routed rather than executed: authoring the replacement mechanism is separate work, and deleting an instruction before its replacement exists removes the only thing enforcing it.
Conditions that should change this plugin, recorded so they are acted on rather than forgotten.
| Trigger | Action |
|---|---|
| Claude Code makes deferred surfaces visible to subagents | Re-run the measurements; the index's justification weakens and the hard-deny classes may narrow |
| Path scoping gains a write trigger | Drop the structural deny on creation-governing content |
Rules gain an official description: frontmatter field |
Make the index's description source explicit rather than a preferred-if-present convention |
| A second consumer needs the findings artifact | Promote its contract to a documented cross-plugin seam before that consumer ships, per the convention registry. The contract's stability guarantees and the three promotion prerequisites are already written down in context/findings-artifact.md; the owner doc is deliberately not written yet, because an interface with one implementation is a guess |
| The glob engine needs semantics bash cannot express cleanly | Reconsider the hand-rolled expander; it exists to avoid eval on repository content |
The options below are personal, enable-time dials. The one setting that is policy rather than taste,
the record of findings the operator has declined, lives on the tracked cascade surface
.claude/instruction-placement.md instead, whose keys, layers, and policy-floor merge are owned by
reference/consumer-config.md.
Generated from this plugin's .claude-plugin/plugin.json. Every option Claude Code
will prompt for when the plugin is enabled, with the environment variable each hook
reads it from.
| Option | Type | Default | Environment variable | Description |
|---|---|---|---|---|
index_drift_hook_enabled |
boolean | true |
CLAUDE_PLUGIN_OPTION_INDEX_DRIFT_HOOK_ENABLED |
PostToolUse notice when a write inside a .claude/rules tree leaves the generated index stale. Advisory and non-blocking; the authoritative gate is /instruction-placement:check in CI. Costs a string comparison on writes outside a rules tree. |
breadth_max |
number | 75 |
CLAUDE_PLUGIN_OPTION_BREADTH_MAX |
Percent of tracked files above which a rule's paths: glob is reported over-broad. Advisory only, never fails the check gate. Raise it in a repository where one extension legitimately covers most files. |
index_max_rows |
number | 40 |
CLAUDE_PLUGIN_OPTION_INDEX_MAX_ROWS |
Surfaces listed individually in the generated index before the remainder is grouped by directory with a count. The index is always-loaded, so this bounds its own cost. |
Three supported routes, in the order most people want them:
-
Interactively. Claude Code prompts for declared options when you enable the plugin. To change them later:
/plugin configure instruction-placement@<marketplace>. -
Headless. Repeat
--configfor each option. Replace<marketplace>with the marketplace you installed this plugin from:claude plugin install instruction-placement@<marketplace> -s <scope> --config index_drift_hook_enabled=<value>
The same command reconfigures a plugin that is already installed: it prints
already installedand still writes the value. The short-circuit message is about the install, not the config write. Do notclaude plugin uninstallto reconfigure: uninstalling drops this plugin's whole storedpluginConfigsentry, resetting every option in the table above to its default.-sdefaults touser, so pass the scopeclaude plugin listreports for this plugin. The verified-version record lives in the plugin-reconfiguration convention.The value is stored immediately; the session you are in does not change. Hooks are handed their
CLAUDE_PLUGIN_OPTION_*when the session starts, so start a fresh Claude Code session before expecting new behavior. A check run in the old session still reports the old value, and that is not a failed write. -
By hand, in settings. Add the value under
pluginConfigsin your user settings (~/.claude/settings.json):{ "pluginConfigs": { "instruction-placement@<marketplace>": { "options": { "index_drift_hook_enabled": <value> } } } }Plugin option values are read from user,
--settings, and managed settings only, not from a project's.claude/settings.json. To vary behavior per repository, enable or disable the plugin in that project'senabledPluginsinstead of setting an option there.
Do not set the CLAUDE_PLUGIN_OPTION_* variables yourself. They are how Claude Code
hands a configured value to a hook process; the value comes from the routes above.
- User configuration: the
userConfigschema and theCLAUDE_PLUGIN_OPTION_<KEY>export - Plugin install options: the
--configflag's reference entry - Plugins and skills settings:
enabledPlugins,extraKnownMarketplaces,pluginConfigs - Settings files and who they affect: user vs project vs local precedence
- Manage installed plugins: enabling, disabling,
/plugin list