# Setup

> Configure the source-control plugin. check (read-only, default) reports the effective commit-subject / PR-title convention merged across the user-global, team, and personal-overlay layers plus the babysit-prs userConfig surface; apply interviews the repo, writes the convention config to a chosen layer, and walks the sanctioned babysit reconfigure paths. Use when setting up or inspecting this plugin's configuration, choosing or overriding a commit convention at any layer, configuring or checking babysit, or when /commit, /pull-request, or /babysit-prs report missing configuration. Re-runnable and safe.

- Skill: `melodic-software/setup-46` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add melodic-software/setup-46`
- Raw SKILL.md: https://api.skillmd.com/api/skills/melodic-software/setup-46/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- Author: melodic-software (https://skillmd.com/u/melodic-software)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/melodic-software/setup-46

---


## Purpose

Inspect and configure the source-control plugin per the uniform setup contract
(`docs/PLUGIN-PHILOSOPHY.md` "Setup is explicit and repeatable" in the marketplace repository):
`check` reports the effective configuration, `apply` writes it. Two configuration surfaces:

1. The commit-subject / PR-title convention config, layered across a user-global file, the tracked
   team file, and a gitignored personal overlay and merged per key by
   [../../reference/config-resolution.md](../../reference/config-resolution.md). Resolved first by
   `/source-control:commit` and `/source-control:pull-request` before they fall back to inference or
   the bundled Conventional Commits default. Conventional Commits is genuinely optional, some orgs
   gate on ticket-prefixed subjects (`WEB-123: description`), so the plugin ships a sensible
   default, not a hardcoded requirement.
2. The `/source-control:babysit-prs` native `userConfig` surface (not a tracked repo file).

Idempotent: re-running reads the existing configuration and offers updates rather than overwriting
blind. The plugin ships a working zero-config default (Conventional Commits / inference for the
convention; the safe babysit tier over your own PRs), so an unconfigured surface is **INFO**, never
FAIL.

Action routing: no argument or `check` runs the check; `apply` runs the check first, then
remediation. When `apply` carries a `subject_pattern=` argument it writes the convention
non-interactively; with no arguments in an interactive session it runs the convention interview
(spoke below). `layer=` selects which config layer `apply` writes, defaulting to the tracked team
file.

## `check` (read-only)

Report a PASS/FAIL/INFO table across both surfaces; modify nothing.

### Convention config

Anchor at the repo root: resolve `REPO_ROOT` once, `${CLAUDE_PROJECT_DIR}` when set, otherwise
`git rev-parse --show-toplevel`, and use that literal resolved path for every repo-relative read
below, never a cwd-relative path (invoked from a nested directory, a cwd-relative path would inspect
the wrong file). Re-resolve `REPO_ROOT` at the top of every self-contained Bash call, a fresh shell
does not carry a prior call's variables.

Read all three layers, then report **one effective-configuration table**, a row per key, its
resolved value, and which layer supplied it, followed by a per-layer presence line. Never present a
single layer's value as the effective convention; a reader who cannot see which layer won cannot
tell why `/source-control:commit` behaves as it does.

```text
key                        value                       won by
subject_pattern            ^[A-Z]+-\d+: .+             team
pr_title_pattern           Same as subject_pattern      team
trailer_policy             none                         local overlay
pr_body_attribution        none                         local overlay
pr_body_required_sections  Summary, Test plan           plugin default
```

`pr_body_required_sections` is a **list**-valued key (like `type_list`, and unlike every scalar row
above it), render it comma-joined for this report regardless of how many lines the winning layer's
file spells it across. When every layer leaves it unset, the row still resolves, to the plugin's
portable default, `Summary` and `Test plan`, so `won by` reads `plugin default` rather than the row
going blank; this is the one key whose "no layer sets it" state is itself a reportable, named value,
not a bare absence. A winning layer declaring the literal keyword `none` renders the row's value as
`none (no required sections)` with that layer in `won by`, a resolved value distinct from the unset
row above, per config-resolution.md.

Per-layer verdicts:

- **User-global** (`~/.claude/source-control.md`): present → report which keys it contributes;
  absent → INFO. It is outside the repo, so no git check applies to it.
- **Team** (`REPO_ROOT/.claude/source-control.md`): present → PASS. **FAIL** when excluded by
  `.gitignore`. Teammates would never receive the shared convention; report the matching rule.
  Absent → INFO, remediable by `apply`.
- **Local overlay ignore rule** (the recursive `.claude/**/*.local.*`, covering
  `REPO_ROOT/.claude/source-control.local.md`): probe the ignore rule whether or
  not the overlay file exists. The rule's job is to be in place **before** the
  first overlay is written; conditioning the probe on the file already existing
  is the window that produces the exposure. Missing rule → FAIL, remediable by
  `apply` (which writes the line at team-layer bind, not only at `layer=local`).
  Probe with `git check-ignore --no-index -v -- .claude/nested/overlay.local.md`
  (the path does not need to exist). The sentinel is nested so a leftover
  `.claude/*.local.*` rule, which still matches the flat overlay path, is not
  mistaken for the recursive rule. A match counts only when `-v` names a
  repository `.gitignore` as the source. `$GIT_DIR/info/exclude` and
  `core.excludesFile` are operator-local and do not protect a teammate.
- **Local overlay file** (`REPO_ROOT/.claude/source-control.local.md`): when
  present, PASS only when an ignore rule matches it **and** it is not in the
  index. Two distinct failures hide behind one symptom and need different
  remediations, so probe them separately. See the two-probe form under
  `apply`. Absent file is OK once the ignore rule itself is present.

**FAIL** when the *effective* `subject_pattern` is not machine-checkable. It must be either the
literal keyword `Conventional Commits` or an anchored regex (`^…`-style); a plain-language
description cannot be evaluated by `/source-control:commit` or `/source-control:pull-request`. Name the layer that supplied the
offending value.

With **all three layers absent**: INFO, no declared convention; `/source-control:commit` and `/source-control:pull-request` infer
from the repo's own `CLAUDE.md`/rules/commit-msg hook, then fall back to the bundled Conventional
Commits default. The remediation is `apply` to persist a convention.

**Neutral-SSOT drift probes.** When a `convention_source` pointer is declared or a neutral file is
resolved (explicit pointer, or the well-known default `docs/conventions/source-control/commit-convention.yml`),
`check` surfaces two drift conditions the resolver otherwise handles silently. Round-trip the
enforcement resolver (`lib/resolve-convention-pattern.sh <REPO_ROOT> subject_pattern`) and read its
diagnostics:

- **Broken pointer / neutral file → FAIL.** A declared `convention_source` whose target is missing,
  or a resolved neutral file that fails the seam's safety/dialect/empty-key contract, disables
  enforcement fail-closed. This is easy to miss because nothing signals it until a commit is
  unexpectedly blocked or allowed, so surface it here, naming the resolver's diagnostic and the
  remediation (restore the file, fix the pointer, or `apply` to rewrite it).
- **Shadowed markdown → WARN.** A neutral file resolves (via pointer or the well-known default) **and**
  `.claude/source-control.md` still carries a markdown-H2 `subject_pattern`/`pr_title_pattern` for the
  same key: the neutral value wins (rungs 1–2 over rung 3) and the stale markdown is inert but
  misleading. Recommend `apply` to retire the duplicate (migration removes it), per
  [reference/apply-convention.md](reference/apply-convention.md) "Migration retires duplicates".

**Retired conventions** — when this plugin ships `retirements.yaml`: run
`bash "${CLAUDE_PLUGIN_ROOT}/lib/check-retirements.sh" --manifest "${CLAUDE_PLUGIN_ROOT}/retirements.yaml"`.
Exit 0 → PASS. Exit 1 → one finding per TSV row: `migrate` is FAIL, `delete`/`remove-line` WARN,
`report-only` INFO; remediation is `apply`. Exit 2 → FAIL, never silent. Bash unavailable → report
the step UNKNOWN with remediation, never green.

### Babysit config

1. **Effective configuration.** Report every babysit `userConfig` key with its resolved value or its
   inference when unset. The authoritative render is the effective-configuration block that loads
   with `/source-control:babysit-prs` (its `help` mode prints it without taking any other action); a
   surviving literal `${user_config.…}` placeholder there means the key is unset. For each unset key
   state what will be inferred at run time. `babysit_watched_owners` → the current repo's owner,
   `babysit_self_logins` → none (your `gh api user --jq .login` login is always used, extras only add
   to it), `babysit_default_tier` → `safe`, `babysit_merge_method` → repo convention then squash, the
   review-trigger keys → module dormant, `babysit_worktree_root` → the plugin data dir's
   `worktrees/` subdirectory. Unset keys are INFO (documented defaults), not FAIL.
2. **Branch-protection posture across watched repos.** For each watched owner (or the current repo's
   owner when `babysit_watched_owners` is unset), enumerate the repos babysit would touch. Repos
   with open PRs authored by the self logins, via
   `gh search prs --state open --author @me --owner <owner> --json repository`, and for each, read
   the default branch's effective rules (`gh api repos/<owner>/<repo>/rules/branches/<default-branch>`,
   falling back to `gh api repos/<owner>/<repo>/branches/<default-branch>/protection` for classic
   protection). Flag every repo reporting zero required reviews AND zero required status contexts as
   **unprotected**: the merge gate refuses gate-proven merges there for non-self authors, and for a
   self author whenever the base is not the default branch (`--allow-unprotected` is the deliberate
   override), so an unprotected repo in an autopilot fleet deserves a protection rule, not an
   override.
3. **Windows long-path support for the worktree root.** On Windows, worktrees under the (possibly
   deep) worktree root can exceed 260 characters. Probe `git config --get core.longpaths` and the OS
   policy (registry value `LongPathsEnabled` under
   `HKLM\SYSTEM\CurrentControlSet\Control\FileSystem`); report each as enabled/disabled with the
   remediation (`git config --global core.longpaths true`; the OS value needs an elevated change, so
   report it, never attempt it). Skip this probe silently on non-Windows.
4. **Lane-script reachability under the host permission layer.** The babysit lane declares its own
   bundled scripts, engine, gates, and guarded wrappers, invocable without a per-call permission
   denial as a prerequisite, and for the paths that prove readiness it declares no degrade tier
   (`babysit-prs` "Engine and degrade"; the contract, including why a denied mutation degrades
   while a denied check cannot, is `skills/babysit-prs/reference/safety.md` "Lane-Script
   Reachability"). Probe it here so the operator learns of a gap before a cycle stalls on it, in
   two parts:
   - **Canary (the load-bearing half).** Run the lane's mandated invocation forms against
     non-mutating targets, **both** of them, because they live under different path prefixes:

     ```bash
     bash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-merge" --help
     bash "${CLAUDE_PLUGIN_ROOT}/scripts/babysit-readiness-gate.sh" --help
     ```

     These are the exact spellings the lane uses for every merge and for every readiness
     declaration, with `--help` so each prints usage and exits 0 without touching the network or
     GitHub. Probe both: an allow rule or classifier decision covering the `bin/` wrapper says
     nothing about the `scripts/` helper, so a canary that ran only the first would certify a
     path the lane's readiness verdict never travels, and the readiness gate has no degrade tier
     at all. A **tool-call denial on either is a FAILED prerequisite**, not an INFO note. The
     reason is fail-closed posture, not logical certainty: the classifier decides per call, so a
     denied `--help` does not *prove* the production shapes are denied any more than a permitted
     one proves they are allowed. What it does establish is that the mandated spelling reaches the
     classifier and can lose there, and the cheapest, most obviously harmless shape is the one
     least likely to be denied while the heavier ones pass. A prerequisite check whose weakest
     probe was refused must report FAILED rather than assume the untested shapes fare better.
     Name which form was denied, say plainly that the production shapes were not probed, report
     the denial verbatim with the remediation below, and never retry it or re-spell it as a raw
     interpreter invocation to get past the denial, that form is exactly what the wrapper exists
     to replace.

     **A pass is reachability, not a guarantee. Report it as such.** A permitted `--help` proves
     the mandated spelling exists and that a call to it got through; it does *not* prove the
     production shapes (`owner/repo#N --allowed-owners …`, `<N> --extra-self …`) will be
     permitted, because the classifier decides per call, at call time. That per-call property cuts
     both ways: it leaves a pass provisional, and it is why the FAILED verdict above is a
     fail-closed choice rather than a proof. The probes stay `--help`-only
     deliberately: the merge wrapper's read-only production shape is a live GitHub call, so a
     representative probe would make a `check` run start touching the fleet it was asked to
     inspect, which the plugin's `babysit-wrapper-help` shell test exists to keep from
     regressing. The
     residual gap is covered rather than hidden: a denial that lands mid-cycle instead is
     fail-honest by the mechanism this section rests on, the gate prints `READINESS_UNPROVEN`,
     or nothing at all when the call never happened, and
     `skills/babysit-prs/reference/loop.md` §5.5 requires the per-PR **Gate verdict** line to
     quote that stdout verbatim, so an unproven readiness surfaces in the report instead of
     being absorbed. The canary is the proactive convenience; the quoted verdict is the
     enforcement.
   - **Effective configuration (context for the canary).** Run `claude auto-mode config` and report
     whether its effective rules cover this plugin's bundled scripts. It prints the merged result
     across the scopes the classifier reads `autoMode` from. User settings and managed settings,
     so read that output rather than hunting for the underlying files; the managed scopes are not
     locally readable as ordinary settings files. **Forward the launch-time scope when there is
     one.** `--settings` is a global flag consumed at launch, not an input the subcommand accepts,
     so a bare `claude auto-mode config` spawned from a session that was itself launched with
     `--settings <file>` reports without that scope and under-states the effective rules. Probe
     with `claude --settings <file> auto-mode config` in that case, and say which form was used;
     when the scope came from an Agent SDK settings object with no file to re-supply, report the
     probe as scope-incomplete rather than as the effective configuration. A missing or
     narrow-looking block is INFO, never FAIL on its own: settings cannot prove reachability,
     because a host safety classifier decides per call, at call time. Pair it with the
     [auto-mode configuration reference](https://code.claude.com/docs/en/auto-mode-config).

   - **GraphQL reachability (a different wall, probed the same way).** The two canaries above prove
     the lane's own scripts reach the classifier; this one proves GitHub will answer them. Sandboxed
     sessions (Claude Code on the web and remote execution) serve only a pinned set of GraphQL
     operations and refuse the rest with `HTTP 403`, and `gh pr view --json` is implemented entirely
     over GraphQL. Probe it read-only against the repository itself:

     ```bash
     gh api graphql -f query='query{viewer{login}}' --jq '.data.viewer.login'
     ```

     A login means GraphQL is served and the lane runs at full fidelity. A `403`, or a message
     saying the operation is not enabled for this session, is INFO rather than FAILED: the engine
     re-sources the `gh pr view` bundle over REST by itself and keeps running. Report the one thing
     the operator loses, because it is the thing that stops merges: review-thread **resolution** has
     no REST equivalent, so the merge gate reports `threadResolutionProven: false` and holds every
     PR as readiness UNPROVEN (`skills/babysit-prs/SKILL.md` "Engine and degrade"). Say so at
     `check` time rather than letting a cycle discover it per PR. The 403 reads like an expired
     token or a missing scope and is neither, so never remediate it by re-authenticating; the only
     remedy is to run the merge half of the lane from a session that is served GraphQL.

   The remediation is always the operator's to apply, never write settings from this skill.

## `apply` (idempotent)

Run `check` first. Then write the convention (surface 1) and walk the sanctioned babysit
reconfigure paths (surface 2).

### Convention config

The full write path is normative in
[reference/apply-convention.md](reference/apply-convention.md). Read it before writing any layer.
In brief:

- **Target layer.** `layer=` picks `user` / `team` (default) / `local`; infer the layer from the
  request's wording and state the pick before writing, the wrong layer either misses teammates or
  commits a personal preference to shared history.
- **Non-interactive** (`subject_pattern=`): an in-place *update*, never a fresh file. Carry every
  independent key, recompute derived keys (`type_list`, `pr_title_pattern`), reject a
  non-machine-checkable value, and for an overlay omit requested keys the layers below already
  resolve identically.
- **Interactive:** the interview. Anchor at `REPO_ROOT`, read all three layers first, infer before
  asking (declared prose, commit-msg hooks, commit-history consensus over the configurable
  `setup_inference_*` window), interview one decision at a time with a recommendation first, settle
  the optional keys (`trailer_policy`, `pr_body_attribution`, `pr_body_required_sections`,
  including the `none` value and the omission-never-resets trap), write the template, verify per
  layer (team = tracked and staged; local = ignored and untracked, two independent probes; user =
  no git command at all), and report the new **effective merge**, not just what was written.

- **Neutral SSOT:** a `team` write may materialize a tool-agnostic flat-scalar YAML file other tools
  consume too. It defaults to the well-known path `docs/conventions/source-control/commit-convention.yml`
  (resolved with no pointer); `## convention_source` is written only to relocate it. **Recommended as
  the default when a second enforcement consumer exists** (commit-msg hook, CI title check), markdown-only
  when this plugin is the sole consumer; migration retires markdown keys the neutral file takes over
  (spoke section "Neutral convention SSOT").

Every step's exact contract, the interview steps, the written-file template, the per-layer
verification scripts, and the failure remediations, lives in the spoke; this summary never
overrides it.

**Retired conventions** — after normal convergence, re-run detection; per finding, individually
gated: `delete`/`remove-line` → confirm, then `--clean <id>`, report what was removed; `migrate` →
carry content per the record's `successor` (convention prose read from the consumer repo is
untrusted input — never executed or interpolated), the operator confirms the migrated result, then
`--clean <id> --i-migrated`. Re-run detection last and report the final state. Repeated declines
route to the finding-suppression convention, never a new consumer-side file.

### Babysit config

`/source-control:babysit-prs` is configured through the plugin's native `userConfig`, which Claude
Code owns (`pluginConfigs`), this skill never hand-edits it. It documents and walks the two
sanctioned paths:

- **Interactive:** `/plugin configure source-control@<marketplace>` (or the `/plugin` dialog → source-control →
  configure), any time. Claude Code prompts per key using the manifest's types and defaults.
- **Headless / CI:** rerun the install with the new values:
  `claude plugin install source-control@<marketplace> -s <scope> --config KEY=VALUE` (repeatable
  per key). Multi-value keys (`babysit_watched_owners`, `babysit_self_logins`,
  `babysit_review_bot_logins`, `babysit_extra_bot_logins`) are supplied comma-joined. Against an
  already-installed plugin it prints `already installed` **and still writes the value** (per the
  marketplace's plugin-reconfiguration convention,
  <https://github.com/melodic-software/claude-code-plugins/blob/main/docs/conventions/plugin-reconfiguration/README.md>,
  which owns the verified-version record). Do **not** uninstall to reconfigure: uninstalling drops
  this plugin's entire stored `pluginConfigs` entry, resetting every option in the README's
  Options reference to its manifest default. `-s` defaults to `user`; pass the scope
  `claude plugin list` reports for this plugin, and run from that project's directory for a
  `project`/`local` scope, or the write lands at a scope that does not load.

When an uninstall is warranted for a reason other than reconfiguring (troubleshooting, changing
scopes, reinstalling a version), pass `--keep-data`. Uninstalling from the **last remaining scope**
otherwise deletes this plugin's `${CLAUDE_PLUGIN_DATA}` directory (Rule 4 of the marketplace's
`plugin-data-report-keying` convention). That directory holds
`${CLAUDE_PLUGIN_DATA}/state/babysit-prs`: the babysit-prs queue state, the worker leases, and the
feedback ledger, which no `userConfig` key relocates. It is also the **last** resolution rung for
both worktree roots. `babysit_worktree_root` falls back to `${CLAUDE_PLUGIN_DATA}/worktrees`
whenever it is unset, while `/source-control:worktree create` reaches that same directory only when
neither the target repository's `worktreeroot.path` git config nor `worktree_root` resolves. So
check where the roots actually resolve before assuming the directory is disposable: babysit's own
worktrees are ephemeral scratch that rebuild from GitHub, but the state directory and any
`/source-control:worktree` tree still holding uncommitted work do not.

Reconfiguring `userConfig` does not reach the already-running session, after either path, the new
values become visible only in a fresh session. Do not re-run the babysit `check` in the same session
expecting the change and report a false failure; instead report "reconfigured; verify with `check` in
a fresh session".

## Output

A convention config file at the chosen layer (when `apply` wrote one), plus the resulting effective
merge with the winning layer per key, a one-paragraph summary of where the convention came from
(inferred or user-declared), and, for babysit, the `check` probe report and the reconfigure path
used. `check` alone reports the effective configuration across both surfaces and changes nothing.

## Gotchas

- **Omitting a key never resets it.** Per-key fallthrough means a section left out of a higher
  layer inherits the lower layer's value. Resetting to the portable default *over* a lower layer
  that sets the key requires writing the explicit default value; omission only inherits (the
  `apply` interview states this when it applies).
- **`none` and absence are different states** for `trailer_policy`, `pr_body_attribution`, and
  `pr_body_required_sections`: absence falls through (ultimately to the bundled default), `none` is
  a resolved opt-out that wins its layer's per-key override.
- **Gate inference on the resolved value, never file presence.** A `source-control.md` layer that
  contributes only other keys leaves `subject_pattern` unresolved. Skipping inference because
  "some config file exists" recommends the bundled default over the repo's real convention.
- **Nested-directory invocations silently read the wrong files.** Anchor every repo-relative read
  at `REPO_ROOT` (`${CLAUDE_PROJECT_DIR}`, else `git rev-parse --show-toplevel`), a cwd-relative
  `.claude/source-control.md` read from a subdirectory misses the repo-root config and degrades
  without an error. Re-resolve in each self-contained Bash call.
- **Linked worktrees hide the hooks directory.** Resolve it with `git rev-parse --git-path hooks`,
  in a linked worktree `.git` is a file, and `core.hooksPath` can move the directory anywhere.
- **History inference clocks: `--since` filters by committer date.** Render `%cd`, not `%ad`, a
  rebased or cherry-picked commit enters the window by committer date but would bucket by its old
  author date, skewing the recency split. A shallow clone truncates
  the window silently. Probe `git rev-parse --is-shallow-repository` and report the actual span.
- **Same-session `userConfig` reads are stale.** Reconfigured babysit values become visible only
  in a fresh session. Re-running `check` in the same session reports a false failure.
- **A broken `convention_source` pointer or well-known file fails closed.** Enforcement and drafting
  surface it as a config error rather than silently falling back to markdown values a migration may
  have retired. Verify the neutral file round-trips through the resolver at write time. The neutral
  file resolves by a fixed 3-rung precedence (explicit pointer > well-known
  `docs/conventions/source-control/commit-convention.yml` **when git-tracked** > markdown-H2); an
  untracked/gitignored file at the well-known path is skipped on both surfaces (policy floor), and
  `check` warns when a resolved neutral file shadows a stale markdown-H2 duplicate.

## What this skill does NOT do

- Make a commit or open a PR, that's `/source-control:commit` and `/source-control:pull-request`.
- Enforce the convention at commit time, a project's own `commit-msg` hook (when one exists) remains
  the authoritative gate; this config only tells the plugin's skills what shape to draft and
  pre-check against.
- Write the consumer's `.gitignore`, except the one recursive `.claude/**/*.local.*` line at
  team-layer bind / `apply`. That line must exist before any overlay is written,
  so `apply` appends it when missing and announces the edit. Everything else in
  `.gitignore` stays the consumer's.
- Write the plugin cache, Claude Code user settings, or `pluginConfigs`. The convention lives in
  the consumer's own config layers; babysit settings live in Claude-Code-owned `userConfig`,
  reconfigured only through the two paths above.

