Repository navigation
feat(conventions): one setup lifecycle for where plugins store team conventions and settings #6594
Description
No related issue: this issue proposes the convention; #5906 migrates surfaces to it.
Summary
Define one repository-wide convention, owned by a new docs/conventions/setup-lifecycle/README.md
and recorded in one ADR that replaces ADR 0044, for how every plugin's setup skill stores and
maintains a team's conventions and settings:
- Conventions and settings live outside
.claude/: prose the model reads in
docs/conventions/<plugin>.md, values scripts read in a separatedocs/conventions/<plugin>.yaml,
so every agent and tool can use them. - Only the prose
.mdcan move; its path is set in the pointer. The.yamlstays at its fixed
default path, so scripts and hooks never parse a pointer. - The root
AGENTS.mdhas one managed block holding one short trigger line per plugin that needs
one, naming model-invokable skills. A plugin gets a line only with stumble evidence. Each setup
writes only its own line, after confirmation. - Setup keeps the closed verb set:
checkreports drift and writes nothing;applyconverges the
repository by detected state (init, migrate, or configure). Model-invoked skills tell the user to
run setup when conventions are missing or stale. - Each value records whether the team chose it or inherited a default, and each file records the
plugin version it was written against, so a changed plugin default updates inherited values and
only flags deliberate ones. - Files record current facts, never targets.
- At user level, an agent-neutral file under
~/.config/<plugin>/, with a pointer in each installed
agent's personal instructions file.
The user wants this as the single source of truth for every plugin, not only for the new
developer-experience plugin (#6302), which adopts it first.
Problem
Evidence, pointer-form test (2026-10-07, blind-scored, one throwaway .NET fixture, 3 runs per cell): with only one trigger-and-path line in AGENTS.md and the team conventions in docs/conventions/developer-experience.md, the agent read the conventions file and called the right plugin skill in 6 of 6 runs, without the prompt naming a skill. Scores: S1 10, 10, 9 of 12 and S3 10, 9, 10 of 10, against 11, 11, 10 and 10, 10, 10 when the prompt named the skills, and 9, 9, 9 and 4-6 when the plugin was installed with no pointer (its skills never fired: the model saw about 320 skills, about 30 with descriptions, and these as bare names).
- Plugin config is spread over three location rules and several shapes. Today a setup skill
may write.claude/<name>(ai-slop:plugins/ai-slop/skills/setup/SKILL.md:20-24), a fenced
yaml configblock indocs/conventions/<concern>.mdwith a.claudefallback (testing:
plugins/testing/skills/setup/SKILL.md:20-26; multi-agent:
plugins/multi-agent/skills/setup/SKILL.md:50-55), a.claude/<name>.mdplus a separate
neutral YAML file reached by aconvention_sourcepointer (source-control:
plugins/source-control/skills/setup/SKILL.md:54-58,130-136), or a.claude/standards.yaml
pointer file (review:plugins/review/skills/setup/SKILL.md:34-40, ratified in ADR 0042). Other
agents and tools do not read.claude/(ADR 0044 Context,docs/adr/0044-*.md:12-17). - The documented target has already moved past the docs. The owner comment on Migrate per-skill .claude config files to userConfig, one conventions doc, or the cascade (ADR 0018) #5906 records
that standardscomponents/github-actions-conventions/README.md:131-136(merged 2026-10-04) puts
structured configuration indocs/conventions/<concern>.yamlwith a JSON Schema and prose in
docs/conventions/<concern>.md, superseding ADR 0044. This repository's
docs/conventions/config-cascade/README.md:50-92and ADR 0044 still describe the fenced block,
and the promised ADR ("home plugin customization in docs/conventions/.yaml") is not in
docs/adr/(latest is 0057). - A skill hidden in a large listing does not fire unless an always-loaded line points to it.
In the developer-experience value test (2026-10-07, round 2), the plugin's skills fired in 0 of 12
runs when merely present: the model saw about 320 skills, about 30 with descriptions, and the
plugin's skills appeared as bare names. Told to use them, the same skills scored 10/10 on every
script-cleanup run against 4-6 without. Team conventions inAGENTS.mdproduced most of the
gain in round 1 (+16 of 44). The test's conclusion: setup should write anAGENTS.mdline
routing the work to the skills, "since AGENTS.md is always loaded and the listing is not". - Setup wrote targets as current facts. In the same test, two of three setup runs described
the target contract as current ("every command supports non-interactive flags"), which would
make an agent hang on commands that do not. - No setup skill can tell a team-chosen value from an inherited default. When a plugin
changes a recommended default, a re-run cannot know whether to update the stored value or leave
it, so todayapplycan only fill absent keys and report what it cannot reconcile
(docs/plugin-philosophy.md:617-625). - Cross-agent reach differs by level.
AGENTS.mdcovers repository and folder levels across
most agents, with nested files and nearest-wins precedence. No cross-agent user-level or local
file exists: each agent has its own (~/.claude/CLAUDE.md,~/.codex/AGENTS.md,
~/.copilot/copilot-instructions.md,~/.gemini/GEMINI.md, Cursor user rules). Merge order
across layers is unconfirmed for several agents, so a design should not depend on one layer
overriding another. (Basis: the instruction-layers research run of 2026-10-07; its claims are
single-source content claims about each publisher's own docs, runtime not probed.)
Proposal
Locations and formats
| What | Path | Format | Reader |
|---|---|---|---|
| Team conventions for a plugin's concern | docs/conventions/<plugin>.md by default; relocatable through the pointer line |
Markdown prose | the model, on demand through the pointer |
| Team settings scripts and hooks read | docs/conventions/<plugin>.yaml, fixed |
YAML, validated by a JSON Schema the plugin ships | scripts, hooks, and the model |
| Pointer | one short trigger line per plugin that needs one, inside one managed block in the root AGENTS.md |
Markdown | every agent that reads AGENTS.md |
| User-level conventions and settings | ~/.config/<plugin>/, same .md/.yaml split |
Markdown and YAML | the model through a pointer in each installed agent's personal instructions file; scripts at the fixed path |
- Script-read values go in the
.yaml, prose in the.md, never mixed. A plugin with no
script-read values has no.yaml; a plugin with no prose has no.md. - Only the prose
.mdis relocatable. The.yamlstays at its fixed default path, so no script or
hook resolves a pointer: the hook cost ADR 0044 decision 4 guards (testing's hook has a 150 ms
p95 budget) and the lookup ADR 0042 declined (docs/adr/0042-*.md:43-45,59) both stay at zero. - The same pointer pattern serves a folder (a nested
AGENTS.mdpointing to a folder-scoped
file) and the user level. No cross-agent user file exists (Problem 6), so the user-level file is
agent-neutral under~/.config/<plugin>/, and setup writes a pointer into the personal
instructions file of each agent installed on the machine, after confirmation. - Surfaces that config-cascade keeps as dedicated files stay where they are: per-operator-keyed
surfaces, policy-floor surfaces, and mutable state
(docs/conventions/config-cascade/README.md:209-212), and machine state in
${CLAUDE_PLUGIN_DATA}(docs/plugin-philosophy.md:383-391). Personal scalars stay in
userConfig. The gitignored overlay.claude/<stem>.local.<ext>is unchanged.
Pointer format
One managed block in the root AGENTS.md; one short trigger line per plugin that needs one:
<!-- BEGIN plugin-conventions (managed by plugin setup skills; each line edited through its plugin's setup) -->
- Developer tooling (scripts, CLIs, dev commands): read `docs/conventions/developer-experience.md`; use `/developer-experience:audit-tools` before changing scripts and `/developer-experience:build-cli` when adding a command.
<!-- END plugin-conventions -->- The line leads with the trigger and follows the pointer-writing rules in
/docs-hygiene:write-for-agents(front-load the matching word, say when to follow and what the
reader gets). Its final wording waits on the pointer-form test (Evidence above). - It names only skills the model can invoke.
setupcarriesdisable-model-invocation: true
(docs/conventions/invocation-mode/README.md:99-105), so a pointer cannot route the model to it;
drift checks the model should run go through the<plugin>:checksibling
(docs/plugin-philosophy.md:604-610). - A plugin gets a line only with stumble evidence: each line is a standing instruction under
Instruction economy (docs/plugin-philosophy.md:948-953). Problem 3 is the evidence for the
pattern; each plugin's line names its own. - Each setup writes only its own line inside the block, after showing it and getting a yes.
instruction-placement's rules-index block is the precedent for a managed region
(docs/conventions/config-cascade/README.md:217-224). - This repository's own
AGENTS.md"Conventions that load on demand" table is a working example
of trigger-and-path routing.
Setup actions
Setup keeps the closed verb set check and apply (docs/plugin-philosophy.md:617-633).
| Action | Writes | Does |
|---|---|---|
check |
never | Drift report: config at an old location (such as .claude/, detected through the plugin's retirements.yaml records), a missing or stale pointer line, a pointer to a missing file, options a plugin update added, recommended defaults that changed since the recorded version, values that state a target as a fact. |
apply |
yes, after showing each change | Converges by detected state. No conventions: init (infer current facts from the repository, interview for the rest, write the .md, .yaml and pointer line). Config at an old location: migrate (move it, rewrite the pointer, carry every value and its provenance; a value it cannot carry is reported, never dropped). Existing config: configure (confirm, change or add options, update inherited defaults). |
No argument runs check. apply runs check before and after. A second apply with no changed
inputs writes nothing. apply's conservative-reconcile rules (docs/plugin-philosophy.md:619-625)
still hold.
Model-invoked skills of an adopting plugin tell the user to run /<plugin>:setup when the
conventions they need are missing or stale, since the model cannot invoke setup itself.
Provenance and version recording
The .yaml records the plugin version it was written against and, per value, whether the team
chose it:
written_against: developer-experience@0.3.0
settings:
default_runner:
value: just
source: chosen # set or confirmed by the team
require_dry_run:
value: true
source: default # inherited from the plugin at written_against- When the installed plugin's default for a
defaultvalue differs from the recorded one,
applyupdates it and says so;checklists it. - When it differs for a
chosenvalue, the value is kept andcheckflags it with the old and new
defaults, so the team can decide. - Prose files carry the same
written_againstline (front matter) and mark team-chosen rules
versus plugin-recommended ones (granularity: Open question 1).
Current facts, not targets
Every stored statement describes the repository as it is when written. A goal the team has not
reached goes in a separate, labeled "Not yet" list or a work item, never in the convention text.
check flags universal claims ("every command supports X") it can test and finds false.
Migration rules
- A surface migrates in its owning plugin's own change, as ADR 0044 decision 7 already requires
per surface. - Leaving
.claude/<name>or~/.claude/<name>is declared with aretirements.yamlrecord
(docs/plugin-philosophy.md:635-641), socheckdetects the leftover in every consumer
repository andapplyoffers the gated move. No bespoke detection prose. - During the bounded window, the old file is read while present and every run says so, per the
one sanctioned dual-read (docs/plugin-philosophy.md:629-633; ADR 0018 decision 5). - When both old and new locations exist, the new one wins and the run warns, naming both paths
(the rule ADR 0044 decision 3 already uses).
What it supersedes or amends
| Existing rule | Change |
|---|---|
ADR 0044 decisions 1-2 (docs/adr/0044-*.md:25-41): team layer is a fenced yaml config block in docs/conventions/<concern>.md |
Superseded by one new ADR that replaces ADR 0044: settings live in docs/conventions/<plugin>.yaml, prose in docs/conventions/<plugin>.md. This is the ADR the owner comment on #5906 promised ("home plugin customization in docs/conventions/.yaml"); this issue carries it. |
ADR 0044 decision 4 (:45-48) and config-cascade/README.md:85-89: the pointer is a load hint, "no script reads it", scripts read the fixed path |
Kept: the .yaml stays at its fixed path; only the prose .md moves through the pointer, and only the model reads the pointer. |
ADR 0042 (docs/adr/0042-*.md:43-45,59): the pointer file is not a fleet rule; generalizing it "would add a lookup to every resolution ladder for no measured benefit" |
Kept: no script-facing lookup is added. The model-facing pointer rests on Problem 3's evidence. |
config-cascade/README.md:217-224: a single index/pointer line in the root file binds the convention home |
Amended: one managed block with one short trigger line per plugin that needs one. |
config-cascade/README.md:28-36,50-92: user-global layer at ~/.claude/<name>, team layer location axis |
Location axis rewritten to the .yaml/.md defaults; user-global layer moves to ~/.config/<plugin>/ with a pointer in each installed agent's personal file, ~/.claude/<name> retired through retirements.yaml. The gitignored overlay is unchanged. |
docs/plugin-philosophy.md:617-633: the setup verb set is "deliberately closed at check and apply" |
Kept. apply gains converge-by-detected-state (init, migrate, configure) and provenance-aware default updates. |
docs/plugin-philosophy.md:540-543: setup offers check and apply |
Amended to point at the new owner doc. |
plugins/testing/skills/setup/SKILL.md:15 ("never edits CLAUDE.md or AGENTS.md") and plugins/multi-agent/skills/setup/SKILL.md:107 |
Stand. They describe those plugins' setups. An adopting setup writes only its own line inside the managed block, with confirmation. |
docs/plugin-philosophy.md:948-953 (Instruction economy, evidence-gated additions) |
Applied: a plugin gets a pointer line only with its own stumble evidence. |
config-cascade/README.md:316-319 (Versioning) |
Changing what the team layer is triggers a contract_version bump: minor if .claude/<name> stays a supported fallback (as ADR 0044 judged), major if it is retired. |
docs/conventions/retired-conventions/ |
Reused unchanged for leaving .claude/ and ~/.claude/. |
docs/conventions/plugin-reconfiguration/ |
Unchanged; it owns native userConfig routes, which this convention does not touch. |
Open questions
- Is provenance recorded per value or per section?
- A. Per value in the
.yaml, per section in the.md. - B. Per value everywhere, with inline markers in prose.
- C. Per file only.
- Recommendation: A. Script-read values need exact default tracking; prose rules change as
sections, and per-sentence markers would make the file hard for people to edit. Basis:
judgment. Settles: the schema every plugin's.yamlcarries and the prose front-matter shape.
- A. Per value in the
Settled by the user on 2026-10-07 and stated above as the proposal: user-level location, script
path resolution, pointer count, the setup verb set, and the owner doc.
Rollout
- Owner doc first: write
docs/conventions/setup-lifecycle/README.md, add its convention
registry entry (docs/plugin-philosophy.md:808-813), and record one ADR that replaces ADR 0044
(the ADR Migrate per-skill .claude config files to userConfig, one conventions doc, or the cascade (ADR 0018) #5906 promised) and amends ADR 0018 decision 1. Amend config-cascade (location axis,
user-global layer, pointer count,contract_versionbump with its CHANGELOG) and
plugin-philosophy (applyconverges; setup section points at the owner doc) in the same change. developer-experience(feat(developer-experience): new plugin for a team's developer tooling (Brief confirmed) #6302) adopts first, since it has no existing surface to migrate.- Other plugins adopt in their own changes, each with its retirement record and, if it wants a
pointer line, its own stumble evidence. Migrate per-skill .claude config files to userConfig, one conventions doc, or the cascade (ADR 0018) #5906 carries the migration of the existing.claude/*
surfaces and fenced blocks once the owner doc lands. - A static conformance check (pointer line present, file at its path,
written_againstpresent)
joins the registry row's audit only after two or three plugins have adopted. - Notify the UX and UI lanes when the owner doc lands; docs: add a routing-as-data convention for plugins that route to installed tools #6483's routing data follows the same
location, format and provenance rules.
Related
- Migrate per-skill .claude config files to userConfig, one conventions doc, or the cascade (ADR 0018) #5906: migrate per-skill
.claudeconfig files; its owner comment moves the target to
docs/conventions/<concern>.yaml. This issue defines the convention that migration lands on. - feat(developer-experience): new plugin for a team's developer tooling (Brief confirmed) #6302: new
developer-experienceplugin, first adopter; source of the value-test evidence. - docs: add a routing-as-data convention for plugins that route to installed tools #6483: routing-as-data convention for plugins that route to installed tools; its data files
should use the same location, format and provenance rules. - ADR 0018, ADR 0042, ADR 0044;
docs/conventions/config-cascade/,
docs/conventions/retired-conventions/,docs/conventions/plugin-reconfiguration/.
Activity
github-actions commentedon Oct 7, 2026
Thanks for the report. Automated triage handed this issue to a maintainer.
Automated triage. A maintainer reviews it.
kyle-sexton commentedon Oct 9, 2026
The
developer-experienceplugin (#6302) is the first adopter of the managed block and merges first, so the owner doc can adopt what it ships instead of the other way round. This records the shipped contract and where it differs from the draft here.Shipped marker text (amend this issue's draft to the GENERATED form). From
plugins/developer-experience/skills/setup/scripts/pointer-line.sh:-bullet. A plugin's setup owns exactly one line, the one that starts with its own prefix, and never edits another byte. The path in the line is the only variable part.setupcarriesdisable-model-invocation: true.checkreports a block that opens with the draft<!-- BEGIN plugin-conventionsprefix asdraft-markerfor migration, andapplyrefuses to edit it (exit 3), so no consumer is rewritten silently.Why GENERATED (OD-1 B), with the counter-evidence.
lib/index-regen.sh:58also uses<!-- BEGIN GENERATED: ... -->, for a region regenerated whole, while this block is edited line by line, so "GENERATED" there means "rerendered". B stays because the existingconvention-homeregion is also a line-owned GENERATED region that setup skills write a single line into (docs/conventions/authoring-formats/README.md:144,plugins/architecture/skills/setup/SKILL.md:59-66), as is the rules index atAGENTS.md:77and:100. So the repository already reads GENERATED as "machine-owned", and the draft'sBEGIN plugin-conventions (managed by ...)form would add a second marker grammar.Provenance marker (OD-2) and conventions-file contract (a).
docs/conventions/developer-experience.mdby default, any repo-relative path the user picks; the pointer line carries it. User level:~/.config/developer-experience/developer-experience.md(OD-6, same name at both levels).written_against: developer-experience@<semver>, rewritten only byapplyand only when it writes something.## Not yethas<!-- developer-experience: source=chosen -->orsource=defaulton the line after its heading.defaultis updated byapplywhen the plugin default changes and the team has not edited the body (an editeddefaultbody is reported as a marker to flip, never overwritten); a changed default on achosensection is flagged bycheckand never applied.Tools,Shared helpers,CLI contract,Secrets,Platforms, then an optional## Not yetthat is the only place a target may appear. No sentence elsewhere states a target as current fact.apply.Convention-home reuse (OD-11). The repository already has a setup-written pointer to the consumer's convention home,
<!-- BEGIN GENERATED: convention-home -->, resolved bylib/resolve-convention-home.sh(scripts/cross-plugin-source-registry.txt:128). It holds exactly one line, so the trigger line cannot go there. DX vendors the resolver byte-identical and defaults the conventions file to<home>/developer-experience.mdwhen a home is bound, elsedocs/conventions/developer-experience.md. This issue does not mention that mechanism; the owner doc should unify the two pointer mechanisms.Departure from
docs/conventions/config-cascade/README.md:229-243(OD-3 A). Those rules make whichever ofAGENTS.mdandCLAUDE.mdcarries the marked region canonical, so a repository that keepsCLAUDE.mdcanonical is served identically. DX's setup writesAGENTS.mdonly and never editsCLAUDE.md. WhenCLAUDE.mdexists without an@AGENTS.mdimport,checkreports thatAGENTS.mdmay not load and prints the exact line to add, and the shim is routed to/instruction-placement:migrate. The owner doc decides whether the block may also live in a canonicalCLAUDE.md.The shipped behavior is in #6668; amend the draft marker and these rules in the owner doc to match, or record the retirement and migration if you choose another form.
kyle-sexton commentedon Oct 10, 2026
Input from the owner, recorded during the planning interview for a new CSS skill in
user-interface(/user-interface:write-cssplus/user-interface:setup).Settled
.local.md/.local.yaml, gitignored, the same pattern as Claude Code'ssettings.local.json)docs/conventionsis only the shipped default location. Every repo and project may store its config elsewhere, and plugins and skills must honor the resolved locations every time.Still open, for this issue to decide
Until this lands, the CSS skill follows the shape on main and in
user-experience: YAML for settings, Markdown for prose, one folder resolved through the convention-home pointer.🤖 Generated with Claude Code
kyle-sexton commentedon Oct 11, 2026
Further owner direction from the
user-interfaceCSS skill design session. This is the target default the repo should converge on; the CSS skill ships as its first adopter.Defaults
<plugin>.yamlfor pure config and one<plugin>.mdfor prose per plugin, no per-plugin subfolder unless a plugin genuinely needs one.AGENTS.mdorCLAUDE.md, whichever is used) points to the convention files with context clues.userConfigexposes the user-global keys under flattened names (css.browser_targetbecomescss_browser_target).Cascade (later wins per key, lists combine)
userConfig(user scope, not committed)~/docs/conventions/<plugin>.{yaml,md}, mirroring the repo layout, pointed to from the user-global instruction file (not committed)<home>/<plugin>.{yaml,md},<home>from the convention-home pointer, defaultdocs/conventions(committed)<home>/<plugin>.local.{yaml,md}(gitignored)Shared mechanism
lib/config-cascade.mjs, plus a canonicallib/yaml-subset.mjs), vendored per ADR 0019, so a later change to locations or format lands in one place.Migration requirement
work-itemswritingtracker.jsonat the repo root) move to this pattern.Repo-wide codification (the written standard, an ADR superseding ADR 0044, and migrating each existing plugin) is proposed as this issue's scope rather than the CSS work's.
🤖 Generated with Claude Code