Skip to content

source-control: tracked config is single-layer — no ~/.claude user-global or *.local.* overlay, diverging from seam 2 #647

Description

@kyle-sexton

Plugin: source-control · Source: design review of the tracked convention config

Problem

.claude/source-control.md is resolved as a single project-level file — skills/commit/SKILL.md:25, skills/pull-request/SKILL.md:31, skills/setup/SKILL.md:38-42,146.

docs/MIGRATION-PLAYBOOK.md:196-240 (seam 2, "Tracked rich config under ${CLAUDE_PROJECT_DIR}") mandates three layers, resolved in a fixed order:

The proven shape is a single tracked file .claude/<plugin>.md (Markdown, for model-facing guidance) or .claude/<plugin>.yaml (structured rules), each with a gitignored *.local.* personal overlay and an optional ~/.claude/<plugin>.md user-global.

Resolution + override semantics. Resolve user-global → team (project) → local overlay, additive-preferred: a later layer adds to or refines earlier layers rather than silently replacing them.

Recommended consumer .gitignore. Ship the overlay convention with the one line the consumer adds: .claude/*.local.*

None of the three are implemented. source-control is one of eight fleet surfaces that are single-layer.

Consequence: a commit convention cannot follow an operator across repos or machines, and a personal deviation from team policy has nowhere to live. Per-machine reconfiguration currently requires editing the team-tracked file.

Reference implementation to mirror: plugins/toolchain/reference/resolution-ladder.md:31-35, which conforms exactly.

Scope

  1. Add the ~/.claude/source-control.md user-global layer.
  2. Add the .claude/source-control.local.md gitignored personal overlay.
  3. Implement the mandated ladder — user-global → team → local — as rung 1 of the existing three-rung convention ladder. Rungs 2 (repo CLAUDE.md/rules/commit-msg hook) and 3 (Conventional Commits default) are unchanged.
  4. Merge semantics: per-key override, documented as a deliberate deviation. The seam's default is concatenation (right for the security-guidance precedent's prose blocks, meaningless for a regex — two subject_pattern values cannot concatenate). All four keys are scalars or closed lists, so this is the seam's sanctioned "genuinely must override … per key, never by dropping the base layer wholesale" case. Record it explicitly rather than deviating silently.
  5. Ship the recommended .gitignore line to the consumer. Do not write to the consumer's root .gitignore — no setup skill in the fleet does, and the prohibition is stated at plugins/toolchain/skills/setup/SKILL.md:141 and docs/conventions/topic-docs/README.md:263.
  6. Teach setup check / apply all three layers: check renders the effective merge and which layer won each key; apply gains user-global and local-overlay targets. The existing gitignore guard must split — a gitignored team file stays a hard STOP (skills/setup/SKILL.md:192-195), a gitignored local overlay is required.
  7. Fix the bundled-template defect (below).
  8. plugin.json semver bump + plugin CHANGELOG.md entry.

Defect included in scope

Two bundled copies of the template exist and disagree:

  • plugins/source-control/skills/commit/.claude/source-control.md:22trailer_policy: none
  • plugins/source-control/skills/setup/.claude/source-control.md:22 — the full Co-Authored-By: Claude <model> (<context>) <noreply@anthropic.com> trailer

Same key, opposite default, in one plugin. Collapse to a single template.

Out of scope

  • File-format change (.md.yaml or YAML frontmatter). A moved config path is a breaking change per docs/MIGRATION-PLAYBOOK.md:368-372 (semver bump plus a CHANGELOG entry in every adopting plugin). This layering work is additive and non-breaking; format is a separate decision and lands separately if at all.
  • Per-subdirectory / monorepo scoping. Deferred, not excluded. Trigger: the first monorepo needing per-subproject convention. Exactly one fleet surface supports it today (work-items, via a CWD→root climb at tools/work-item-tracker/lib/binding.sh:17-27); commit/SKILL.md:25 currently enforces the opposite for this plugin.
  • Fleet-wide conformance and the governing-doc gap — see the linked issues.

Verification notes

  • Built-in-first gate (docs/PLUGIN-PHILOSOPHY.md:80-85) is cleared: userConfig provably cannot host this. Per https://code.claude.com/docs/en/plugins-reference#user-configuration, Claude Code "reads it back from user settings, the --settings flag, and managed settings only; entries in a project's .claude/settings.json or .claude/settings.local.json are ignored" — so a userConfig path or value cannot be team-shared. docs/PLUGIN-PHILOSOPHY.md:159-160 states the same rule locally.
  • Security criterion 4 (docs/MIGRATION-PLAYBOOK.md:657-659, "no reading consumer files outside ${CLAUDE_PROJECT_DIR}") is in apparent tension with seam 2's blessed ~/.claude/ user-global. The first-party security-guidance plugin reads ~/.claude/claude-security-guidance.md, so the reconciliation is that user-home config is the operator's own declared surface, not consumer repository data. Flagging for review rather than assuming it.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority: mediumReal value, no hard deadline; normal backlog flow.status: readyTriaged, unblocked, and fully specified; eligible to pick up.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions