Skip to content

feat(conventions): one setup lifecycle for where plugins store team conventions and settings #6594

Description

@kyle-sexton

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 separate docs/conventions/<plugin>.yaml,
    so every agent and tool can use them.
  • Only the prose .md can move; its path is set in the pointer. The .yaml stays at its fixed
    default path, so scripts and hooks never parse a pointer.
  • The root AGENTS.md has 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: check reports drift and writes nothing; apply converges 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).

  1. 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 config block in docs/conventions/<concern>.md with a .claude fallback (testing:
    plugins/testing/skills/setup/SKILL.md:20-26; multi-agent:
    plugins/multi-agent/skills/setup/SKILL.md:50-55), a .claude/<name>.md plus a separate
    neutral YAML file reached by a convention_source pointer (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).
  2. 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 standards components/github-actions-conventions/README.md:131-136 (merged 2026-10-04) puts
    structured configuration in docs/conventions/<concern>.yaml with a JSON Schema and prose in
    docs/conventions/<concern>.md, superseding ADR 0044. This repository's
    docs/conventions/config-cascade/README.md:50-92 and 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).
  3. 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 in AGENTS.md produced most of the
    gain in round 1 (+16 of 44). The test's conclusion: setup should write an AGENTS.md line
    routing the work to the skills, "since AGENTS.md is always loaded and the listing is not".
  4. 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.
  5. 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 today apply can only fill absent keys and report what it cannot reconcile
    (docs/plugin-philosophy.md:617-625).
  6. Cross-agent reach differs by level. AGENTS.md covers 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 .md is relocatable. The .yaml stays 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.md pointing 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. setup carries disable-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>:check sibling
    (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 default value differs from the recorded one,
    apply updates it and says so; check lists it.
  • When it differs for a chosen value, the value is kept and check flags it with the old and new
    defaults, so the team can decide.
  • Prose files carry the same written_against line (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 a retirements.yaml record
    (docs/plugin-philosophy.md:635-641), so check detects the leftover in every consumer
    repository and apply offers 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

  1. 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 .yaml carries and the prose front-matter shape.

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

  1. 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_version bump with its CHANGELOG) and
    plugin-philosophy (apply converges; setup section points at the owner doc) in the same change.
  2. 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.
  3. 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.
  4. A static conformance check (pointer line present, file at its path, written_against present)
    joins the registry row's audit only after two or three plugins have adopted.
  5. 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

Activity

  1. added
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 7, 2026
  2. github-actions commented on Oct 7, 2026

    @github-actions
    Contributor

    Thanks for the report. Automated triage handed this issue to a maintainer.

    Automated triage. A maintainer reviews it.

  3. added
    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.
    on Oct 7, 2026
  4. kyle-sexton commented on Oct 9, 2026

    @kyle-sexton
    ContributorAuthor

    The developer-experience plugin (#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:

    <!-- BEGIN GENERATED: plugin-conventions -->
    - Before creating, changing or cleaning up CLIs, scripts or other developer tools, read `docs/conventions/developer-experience.md`. Use /developer-experience:build-cli to create a tool or change its interface, and /developer-experience:audit-tools to take stock of or clean up tools; a bug fix inside one needs neither.
    <!-- END GENERATED: plugin-conventions -->
    • Each line is a - 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.
    • The line names only model-invokable skills; setup carries disable-model-invocation: true.
    • check reports a block that opens with the draft <!-- BEGIN plugin-conventions prefix as draft-marker for migration, and apply refuses to edit it (exit 3), so no consumer is rewritten silently.

    Why GENERATED (OD-1 B), with the counter-evidence. lib/index-regen.sh:58 also 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 existing convention-home region 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 at AGENTS.md:77 and :100. So the repository already reads GENERATED as "machine-owned", and the draft's BEGIN plugin-conventions (managed by ...) form would add a second marker grammar.

    Provenance marker (OD-2) and conventions-file contract (a).

    • Path: docs/conventions/developer-experience.md by 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).
    • Front matter written_against: developer-experience@<semver>, rewritten only by apply and only when it writes something.
    • Each section except ## Not yet has <!-- developer-experience: source=chosen --> or source=default on the line after its heading. default is updated by apply when the plugin default changes and the team has not edited the body (an edited default body is reported as a marker to flip, never overwritten); a changed default on a chosen section is flagged by check and never applied.
    • Sections are Tools, Shared helpers, CLI contract, Secrets, Platforms, then an optional ## Not yet that is the only place a target may appear. No sentence elsewhere states a target as current fact.
    • Unknown sections and unrecognized prose are kept verbatim by apply.
    • This is per-section for prose, as Open question 1 here recommends.

    Convention-home reuse (OD-11). The repository already has a setup-written pointer to the consumer's convention home, <!-- BEGIN GENERATED: convention-home -->, resolved by lib/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.md when a home is bound, else docs/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 of AGENTS.md and CLAUDE.md carries the marked region canonical, so a repository that keeps CLAUDE.md canonical is served identically. DX's setup writes AGENTS.md only and never edits CLAUDE.md. When CLAUDE.md exists without an @AGENTS.md import, check reports that AGENTS.md may 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 canonical CLAUDE.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.

  5. kyle-sexton commented on Oct 10, 2026

    @kyle-sexton
    ContributorAuthor

    Input from the owner, recorded during the planning interview for a new CSS skill in user-interface (/user-interface:write-css plus /user-interface:setup).

    Settled

    • Config resolves in four layers, later wins per key, lists combine:
      1. plugin-shipped defaults
      2. user-global
      3. project team layer, checked in
      4. project-local personal overlay (.local.md / .local.yaml, gitignored, the same pattern as Claude Code's settings.local.json)
    • docs/conventions is 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.
    • Everything that makes sense to configure is configurable. A setting shipped fixed is a stated assumption that downstream consumers will not need to change it, so each fixed default is listed with its reason.
    • Skills read the layers through one shared resolver and never hard-code a path, so a later format or folder decision changes the resolver, not every skill.

    Still open, for this issue to decide

    • Format: prose Markdown vs YAML vs JSON for structured settings and for team conventions.
    • Folder layout: one conventions folder vs several.
    • How this reconciles with routing-as-data's current rule against a structured personal layer (the CSS skill will record that as a deviation until this lands).

    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

  6. kyle-sexton commented on Oct 11, 2026

    @kyle-sexton
    ContributorAuthor

    Further owner direction from the user-interface CSS skill design session. This is the target default the repo should converge on; the CSS skill ships as its first adopter.

    Defaults

    • Flat by default: one <plugin>.yaml for pure config and one <plugin>.md for prose per plugin, no per-plugin subfolder unless a plugin genuinely needs one.
    • Markdown for prose, YAML for pure config.
    • Progressive disclosure through pointers: the instruction file in scope (AGENTS.md or CLAUDE.md, whichever is used) points to the convention files with context clues.
    • One key vocabulary per plugin, defined by one schema, accepted at every layer. userConfig exposes the user-global keys under flattened names (css.browser_target becomes css_browser_target).

    Cascade (later wins per key, lists combine)

    1. plugin-shipped defaults
    2. userConfig (user scope, not committed)
    3. user-global files at ~/docs/conventions/<plugin>.{yaml,md}, mirroring the repo layout, pointed to from the user-global instruction file (not committed)
    4. project team files at <home>/<plugin>.{yaml,md}, <home> from the convention-home pointer, default docs/conventions (committed)
    5. project personal overrides at <home>/<plugin>.local.{yaml,md} (gitignored)

    Shared mechanism

    • One canonical resolver (lib/config-cascade.mjs, plus a canonical lib/yaml-subset.mjs), vendored per ADR 0019, so a later change to locations or format lands in one place.
    • Anything a plugin ships that assumes a dependency, glue or implementation detail the consumer cannot change becomes a configuration point with a good default; whatever stays fixed is listed with its reason.

    Migration requirement

    • Existing plugins that diverge (for example work-items writing tracker.json at the repo root) move to this pattern.
    • The resolver reports files found in an old location or format, and each plugin's setup (or first skill use) offers: "You're using an old format. Migrate?", then moves the values with the user's yes.

    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

  7. removed
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions