Skip to content

feat(source-control): setup writes a self-describing team convention file - #1055

Merged
kyle-sexton merged 1 commit into
mainfrom
feat/1046-setup-self-describing-header
Jul 22, 2026
Merged

feat(source-control): setup writes a self-describing team convention file#1055
kyle-sexton merged 1 commit into
mainfrom
feat/1046-setup-self-describing-header

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Summary

The team-tracked .claude/source-control.md that /source-control:setup apply writes lands in shared history with no signal for a teammate who does not run these plugins — audit finding f6 on #912, declared folded into #913 but never delivered by PR #925 (its file list contains no setup-skill change).

  • The apply template now opens with a self-describing preamble: read by the source-control plugin (and the guardrails commit-convention gate where installed), inert without them, and a drafting aid, not team-wide enforcement — tool-agnostic enforcement for every committer is a commit-msg hook or CI check.
  • The apply report for a team write states the same draft-aid vs enforcement distinction.
  • The header is template-owned: a reconfiguration run rewrites it in place, never appends a second copy.
  • Preamble safety proven, not assumed: a new regression case in lib/resolve-convention-pattern.test.sh shows a preambled file resolves identically to a bare one (the parse contract reads only the first non-empty body line under a ## <key> H2).

source-control 0.17.00.17.1 with CHANGELOG entry.

Test plan

  • bash lib/resolve-convention-pattern.test.sh — 28 cases, 0 failed (new preamble case included)
  • bash scripts/check-changelog-parity.sh --check-bump main — pass
  • bash scripts/check-changed-skills.sh main — setup: PASS (0 errors)

Related

🤖 Generated with Claude Code

…file

Closes #1046 (audit umbrella #912, finding f6 — declared folded into #913
but never delivered by PR #925).

The team-tracked .claude/source-control.md lands in shared history with no
signal for a teammate who does not run these plugins. The setup apply
template now opens with a self-describing preamble: read by the
source-control plugin (and the guardrails commit-convention gate where
installed), inert without them, a drafting aid — not team-wide
enforcement, which is a commit-msg hook or CI check. The apply report for
a team write states the same distinction.

The header is template-owned (rewritten in place on reconfiguration,
never appended twice), and prose above the first H2 is inert by
construction — a new regression case in resolve-convention-pattern.test.sh
proves a preambled file resolves identically to a bare one.

source-control 0.17.0 -> 0.17.1 with CHANGELOG entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton enabled auto-merge (squash) July 22, 2026 18:36
@claude

claude Bot commented Jul 22, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 7s —— View job


PR Review: feat(source-control): setup writes a self-describing team convention file

  • Gather context and understand the request
  • Read changed files
  • Review SKILL.md changes (preamble template)
  • Review test changes (regression case)
  • Review plugin.json version bump
  • Review CHANGELOG entry

Summary

This PR delivers audit finding f6 from #912 that was claimed in #913 but never shipped in #925. The change is small, focused, and mechanically sound. Overall: approve.


Parser correctness — preamble is genuinely inert

The h2_value() awk function in lib/resolve-convention-pattern.sh uses the pattern /^##[[:space:]]/ to detect H2 headings. Critically:

  • # source-control configuration (H1, one #) does not match — in_section stays 0
  • All prose in the preamble also does not match
  • in_section only becomes 1 when awk encounters ## subject_pattern
  • Lines before that are skipped unconditionally

The claim is verified, not assumed.


Test case quality

lib/resolve-convention-pattern.test.sh L55-60 — the new regression case:

r="$(newrepo $'# source-control configuration\n\nRead by the source-control Claude Code plugin ...\n\n## subject_pattern\nConventional Commits')"
assert_eq "preamble above first H2 is inert" "$CC_ERE" "$(run "$r")"

This calls the actual resolver script against a real temp file and asserts the output equals $CC_ERE — no mocking. The case count reaches 28, matching the PR description.

Minor observation: The test preamble is a slightly condensed version of the SKILL.md template text (shorter wording, fewer words). Since the parse contract is purely structural (H2 heading detection, not content matching), this doesn't affect correctness — any prose before the first ## H2 is inert. But if the template ever gains content that could accidentally match an awk pattern (e.g., a line starting with ## in an example), a test using the exact template text would catch it immediately. Not a blocker, just something to consider in a follow-up.


SKILL.md additions

Two new documentation blocks were added:

  1. L268–275 — in the "Write the config" step, explains why the preamble is safe and that a reconfiguration run rewrites the whole header block in place.

  2. L374–379 — in the "Report" step, instructs the model to state plainly the draft-aid vs enforcement distinction for team writes.

Both are well-placed and technically accurate. The template at L227–262 shows the exact preamble text the model should write.


Version and CHANGELOG

0.17.0 → 0.17.1 is the correct patch bump — no interface change, no breaking change, purely additive template content. The CHANGELOG entry at CHANGELOG.md L3–21 is accurate and thorough.


No issues found

The PR is correct, tested, well-documented, and closes a real audit gap. No security concerns, no regressions, no structural problems.

@claude

claude Bot commented Jul 22, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 44s —— View job


Security Review — PR #1055

  • Load tools
  • Fetch PR diff
  • Read changed files
  • Analyze for security issues
  • Post findings

Findings

No security issues found.

Analysis

All four changed files were reviewed:

lib/resolve-convention-pattern.test.sh — The new test case at line 59 passes a hardcoded prose string through newrepo, which writes it via printf '%s\n' into a mktemp -d-isolated temp directory (cleaned via EXIT trap). The run helper calls bash "$SCRIPT" "$1" where $1 is the temp directory path resolved from mktemp, not user-controlled input. No injection surface; no persistent side effects.

plugins/source-control/.claude-plugin/plugin.json — Version bump 0.17.0 → 0.17.1 only. No dependency changes, no permission changes, no supply-chain risk.

plugins/source-control/CHANGELOG.md — Documentation only.

plugins/source-control/skills/setup/SKILL.md — The preamble block added to the file template (lines 230–233) is fixed prose — no user-interpolated values, no shell metacharacters. It is written to .claude/source-control.md verbatim by the model, which already had write access to that path. The SKILL.md instruction at lines 268–274 correctly documents that prose above the first ## heading is ignored by the resolver — the regression test confirms this invariant holds. The report addition at lines 374–378 is output-only and contains no evaluation or execution path.

One observation worth noting (not a vulnerability): the preamble section in the written file is prose that Claude reads when the file is loaded. A future actor with write access to .claude/source-control.md could craft a malicious preamble targeting prompt injection. However: (a) this attack surface pre-exists this PR and applies equally to CLAUDE.md and any other file Claude reads, (b) write access to .claude/source-control.md implies equivalent access to CLAUDE.md, and (c) the template preamble itself is benign. This is not a finding introduced by this PR.

@kyle-sexton
kyle-sexton merged commit 91e01c1 into main Jul 22, 2026
25 checks passed
@kyle-sexton
kyle-sexton deleted the feat/1046-setup-self-describing-header branch July 22, 2026 18:39
kyle-sexton added a commit that referenced this pull request Jul 22, 2026
Resolve source-control CHANGELOG conflict by composing both sides: keep #1055's
self-describing team-convention-file entry as [0.17.1] (shipped to main), and move
the worktree-convention SSOT entry to a new [0.17.2] heading. Re-bump plugin.json
0.17.1 -> 0.17.2 (one increment past main's current value).
kyle-sexton added a commit that referenced this pull request Jul 22, 2026
…1032) (#1059)

Closes #1032

## Summary

Completes #975's adoption path: `/source-control:setup` was the last
mechanism-owning surface that
didn't know about `pr_body_required_sections` (added in #1029), so a
team using the sanctioned
guided setup flow couldn't configure the required-section scaffold or
see which layer supplies it —
the only path was hand-editing `.claude/source-control.md` directly.

- **`check` reports a `pr_body_required_sections` row** on the
effective-configuration table,
resolving to the plugin's portable default (`Summary`, `Test plan`) with
`won by: plugin default`
when no layer sets it — a named, reportable value rather than a blank
row, since "unset" is itself
  a meaningful state for this key.
- **`apply`'s interview offers setting it**, deliberately recommending
only the plugin's own portable
default and never proposing a `Related`/linked-issue section (or any
other org-specific list) as a
universal default — it asks what the repo's actual convention requires
(a PR template, a CI gate
like `pr-issue-linkage`, team practice) rather than inventing one, per
the plugin's Two-lane
  convention posture.
- **The written-config template** gains the matching `##
pr_body_required_sections` section, at
parity with every other per-key surface (`subject_pattern`,
`pr_title_pattern`, `trailer_policy`,
  `pr_body_attribution`).
- New evals 13-14 cover the check-report default and the
agnostic-interview behavior.
- Plugin version bumped `0.18.0` → `0.19.0` with a matching
`CHANGELOG.md` entry.

This branch was rebased onto `feat/975-required-body-sections` (then
onto `main` after #1029 merged)
so it includes #1029's `pr_body_required_sections` key and #1055's
setup-template preamble together —
verified both coexist correctly in the write template (preamble header,
then the per-key section
list ending with the new `## pr_body_required_sections` section).

## Test plan

- `npx markdownlint-cli2` over the changed skill doc and CHANGELOG: 0
errors.
- `node -e "JSON.parse(...)"` over the updated `evals/evals.json`: valid
JSON.
- `bash scripts/check-changelog-parity.sh --check-bump origin/main`:
PASS.
- `bash scripts/validate-plugins.sh`: every plugin manifest and the
marketplace catalog validate.
- Manually verified the merged write template renders correctly end to
end (preamble +
`pr_body_required_sections` section both present, no duplication) after
rebasing onto `main`.

## Related

- #975 / #1029 — the mechanism and portable-default key this PR
completes the adoption path for.
- #1055 — the setup-template self-describing-preamble change this branch
was rebased across; both
changes touch the same "Write the config" template step and coexist
without conflict.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 25, 2026
…OT (#401) (#1021)

## Summary

Plugin-side deliverable for #401. The retired external prose doc
`~/.claude/docs/ghq-layout-sibling-pr-worktrees.md` (dotfiles/chezmoi
repo) prescribed a stale
sibling-at-`ghq`-level worktree layout and carried the babysit
worktree-exemption policy. This PR
closes the SSOT gap named in the issue: it states, in the plugin itself,
that the plugin — not any
external prose doc — is the canonical, sole source for both worktree
conventions.

**Nothing was actually missing or migrated.** Verification (below)
confirms the babysit exemption
policy is already fully captured *and machine-enforced* in the plugin,
and that the "Codex-home
root" the issue flags is a per-user `babysit_worktree_root` config
**value**, not plugin content —
migrating that literal path into a user/machine/org-agnostic plugin
would be wrong. This PR
clarifies canonical ownership; it does not restate the retired prose
(point-don't-copy).

## Fix

- `plugins/source-control/skills/babysit-prs/reference/worktrees.md` —
added a statement that this
file is the canonical, sole source for the ephemeral babysit-worktree
exemption, with the
load-bearing rationale a future maintainer could otherwise undo: rooting
these worktrees outside
every repository's discoverable tree (plugin data dir by default) keeps
ephemeral scratch out of
repository enumeration such as `ghq list`; repointing
`babysit_worktree_root` back under a
  discoverable tree reintroduces that pollution.
- `plugins/source-control/skills/worktree/SKILL.md` — added one sentence
stating this skill owns
the parallel-session **external-root** convention going forward (the
discoverable replacement),
explicitly scoped to the external-root replacement, not the abandoned
sibling layout.

## Verification

- **Babysit exemption fully captured + machine-enforced.**
`reference/worktrees.md` Policy already
documents lease-scoped cleanup and "never request global open-PR
cleanup" (lines 12-31), and the
operational runbook step 8 in `SKILL.md` (line 433) says "Never globally
prune open-PR
worktrees." `scripts/prune_babysit_worktrees.py` lines 189-194
machine-enforce it: `--lease-token`
requires `--pr`, and `--prune-open-clean` requires **both** `--pr` and
`--lease-token` — so no
  global open-PR prune path exists.
- **Codex-home root is a config value, not plugin content.**
`babysit_worktree_root` (userConfig in
`.claude-plugin/plugin.json`) defaults to the `worktrees/` subdir of the
plugin data dir. The
babysit skill already migrated its root off `CODEX_HOME` to
`${CLAUDE_PLUGIN_DATA}` (CHANGELOG
line 735). Live grep: `CODEX_HOME|\.codex|\.agents` across the whole
babysit skill → **zero
  matches**.
- **No dangling pointer to the retired doc.** Live grep for
`ghq-layout|sibling-pr-worktree` and
`.claude/docs` worktree pointers across the plugin → **no plugin
reference** (the only `ghq list`
  hits are unrelated `repo-hygiene` tree-batch features).
- **markdownlint-cli2** on all three edited markdown files (root
`.markdownlint-cli2.jsonc`) →
  `0 error(s)`.
- **Commit signed** (SSH, ED25519), pushed via explicit refspec.
- Out of scope, intentionally deferred (epic #398, not this issue): the
setup skill emitting a
per-tool worktree-root map + exemptions as *output*. Not missed —
deferred.

Closes #401

## Related

- Part of epic #398.
- **Cross-repo follow-up (out of scope here):** physical deletion of
`ghq-layout-sibling-pr-worktrees.md` and removal of its pointer from the
user's `CLAUDE.md`
"Reference docs" list happen in the **dotfiles/chezmoi** repo — not
reachable from this worktree.
- **Draft hold released:** the PR was held as draft while unrelated
source-control PRs landed first
(#1055 → `0.17.1`, #1029 → `0.18.0`, #1032 → `0.19.0`, and later main
advanced to `0.26.2`),
each requiring a merge-only freshness re-merge and re-bump. PR #853 (the
last open
source-control PR behind the hold) has merged, so the hold is lifted.
Current state bumps
`0.26.2 → 0.26.3` with the CHANGELOG entry moved to `[0.26.3]`; every
`plugin.json` +
`CHANGELOG.md` conflict was resolved by composing both sides (kept
main's entries and newer
  description, re-slotted the worktree-SSOT entry on top).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

source-control: setup apply writes self-describing header + draft-aid framing (deliver the #913 f6 fold)

1 participant