Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/conventions/config-cascade/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ by a pointer line). Per-concern keys and schema are versioned by their own owner
change independently. A change to the precedence order or the meaning of a layer is a major bump;
adding an optional layer or relaxing a rule additively is a minor bump.

## Deviations and Implementers table, 2026-09-28

- **`source-control` `branch_issue_pattern` fail-closed stop declared (#4673).** The Declared list
gains the surface's divergence from rule 4 (degrade soft on a malformed layer): a layer whose
`## branch_issue_pattern` section exists but yields no usable pattern stops resolution with no
issue number, because the value feeds a `Closes #N` line and a lower source's number could close
the wrong issue. The row's conformance cell names the exception. The near-miss-heading stop that
shipped in #4581 was that divergence already, undeclared. No contract rule change, so no version
bump.

## Implementers table, 2026-09-08

- **`architecture` and `authoring-formats` C4 dialect surfaces (#3910).** The two rows no longer
Expand Down
14 changes: 13 additions & 1 deletion docs/conventions/config-cascade/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,18 @@ surface, or amend this contract) is a separate human-gated decision.
gitignoring one. Claude Code documents local behavior only for surfaces it actually resolves
(`CLAUDE.local.md`, `settings.local.json`); a generic `*.local.*` filename has no
platform-defined meaning.
- **`source-control` stops `branch_issue_pattern` resolution on an unusable layer (#4673).**
Rule 4 of the resolution algorithm would resolve as if a malformed layer were absent. For this
one key, `parse-branch-issue.sh` instead fails closed: a layer whose `## branch_issue_pattern`
section exists but yields no usable pattern (a near-miss heading, no value, a heading or HTML
comment as the first value line, an empty or unterminated fence, or a pattern that fails
validation) prints a note naming the layer and the reason, emits no issue number, and exits 1.
The consumer is a `Closes #N` line that closes an issue on merge, so a lower layer or the
default supplying a different number is worse than no number; the no-number path already exists
and `/source-control:pull-request create` relays the note. A higher layer that already supplied
a valid pattern still wins, since the lower layer is never read, and a malformed deprecated
`userConfig` value is still ignored with a note. Every other `source-control` key keeps the
soft degrade.

**Undeclared**, meaning divergence with no recorded rationale:

Expand All @@ -281,7 +293,7 @@ convention home, layers → `team, via pointer line`, conformance → the retire

| Surface | Consumer config path | Layers | Conformance |
|---|---|---|---|
| `source-control` | `.claude/source-control.md` | all three | conforms (per-key override, #660); enforcement reads team-tracked only per [`commit-convention`](../commit-convention/README.md); loop-lane keys (`babysit_loop_*`, read by the source-control babysit lane; the work-items lanes tie in via the loop-lane convention only) ride the same surface, with the merge-rung key in the policy-floor class: standing raises bind from the team-tracked layer only, and the one named single-invocation exception is an explicitly typed argument rather than a config value in any layer, per [`loop-lane`](../loop-lane/README.md) |
| `source-control` | `.claude/source-control.md` | all three | conforms (per-key override, #660), except the declared fail-closed stop on an unusable `branch_issue_pattern` layer (#4673, see Declared); enforcement reads team-tracked only per [`commit-convention`](../commit-convention/README.md); loop-lane keys (`babysit_loop_*`, read by the source-control babysit lane; the work-items lanes tie in via the loop-lane convention only) ride the same surface, with the merge-rung key in the policy-floor class: standing raises bind from the team-tracked layer only, and the one named single-invocation exception is an explicitly typed argument rather than a config value in any layer, per [`loop-lane`](../loop-lane/README.md) |
| `toolchain` / `ecosystem-commands` | `.claude/ecosystems/<ecosystem>.yaml` | all three | conforms |
| `codebase-health` | `.claude/codebase-health.md` | all three | conforms (concatenating, with a declared empty-list opt-out) |
| `bugs` | `.claude/bugs.md` | all three | conforms; `lanes` concatenate and deduplicate by lane `name`, with a declared empty-list opt-out that also drops the bundled defaults, and `filing_posture` is a nearest-wins scalar. Keys owned by the plugin's `reference/config.md`, which also partitions them from the plugin's `output_dir` `userConfig` option. That option is never a key in this surface, and a layer declaring it is reported as an inert unknown key. Written (team layer only) by `/bugs:setup apply`, read by `/bugs:scan` |
Expand Down
2 changes: 1 addition & 1 deletion plugins/source-control/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "source-control",
"version": "0.61.5",
"version": "0.62.0",
"description": "Git and GitHub delivery workflow: /commit (Conventional Commits + Co-authored-by trailer via safe heredoc mechanics), /pull-request (prep, create, CI monitoring, review-comment triage, merge, CI-log fetch), /babysit-prs (self-pacing fleet loop, safe by default; opt-in worker/autopilot tiers add gate-checked merge and thread resolution behind a deterministic Python engine), /babysit-loop (the loop-lane merge lane: a standing or drain loop that invokes babysit-prs per cycle, configured through repo-scoped babysit_loop_* keys on the layered source-control.md seam, with merge authority human-only until the target repo's tracked config adopts the lane, a gate-proven C2-mechanical baseline once adopted, and standing merge-rung raises binding from the team-tracked layer only, with one named exception, where an invocation line explicitly typing both the autopilot tier keyword and the dedicated raise argument --merge c3-this-run widens that single invocation's merge authority up to C3 behind a fresh independent frontier-tier resolver, while C4-structural and C5-untrusted-provenance stay unconditionally human-merge), /worktree (create, status, cleanup, audit for parallel-session isolation), /setup (check the effective commit-subject / PR-title convention merged across its config layers and the babysit-prs config, or apply, which interviews the repo and writes the convention config to a chosen layer), and /resolve-conflicts (intent-first merge/rebase conflict resolution with a semantic-conflict sweep, never --abort). The commit-subject / PR-title convention is configurable via a source-control.md config written by a re-runnable setup skill, layered across a ~/.claude user-global file, the tracked team file, and a gitignored .claude/source-control.local.md personal overlay merged per key; Conventional Commits is the default when no convention is declared.",
"author": {
"name": "Melodic Software",
Expand Down
6 changes: 6 additions & 0 deletions plugins/source-control/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
All notable changes to the `source-control` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.62.0] - 2026-09-28

### Changed

- **`parse-branch-issue.sh` stops on an unusable `branch_issue_pattern` layer instead of skipping it** ([#4673](https://github.com/melodic-software/claude-code-plugins/issues/4673)). A layer whose `## branch_issue_pattern` section exists but yields no usable pattern now prints a note containing `resolution stopped`, emits no issue number, and exits 1. Before, it was reported and skipped, and a lower layer, the userConfig, or the built-in default could then supply a different number to the `Closes #N` line: with a broken trailing-number team pattern, `feat/12-widget-34` closed #12. The stop covers a heading or HTML comment as the first value line, an empty or unterminated fence, a pattern that breaks a limit, holds a backreference, or does not compile, and a section with no value (previously skipped without a note). The near-miss-heading stop is unchanged. A higher layer that already supplied a valid pattern still wins, and a userConfig value that fails validation is still ignored, with a note, so the default applies. `create.md`, `config-resolution.md`, setup's `SKILL.md`, and `apply-convention.md` describe the stop, and setup's check reports a stopped layer as a FAIL. The repo's config-cascade convention records the stop as a declared deviation from its degrade-soft rule.

## [0.61.5] - 2026-09-28

### Fixed
Expand Down
26 changes: 17 additions & 9 deletions plugins/source-control/reference/config-resolution.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,23 @@ Markdown, one `## <key>` H2 per key, the value as the section body:
- A leading UTF-8 BOM and CRLF line endings are accepted, and so are trailing whitespace and a
closing `#` sequence on the heading (`## branch_issue_pattern ##`). Headings inside fenced
blocks are ignored.
- A **near-miss heading** stops resolution: an H2 outside a fence whose text contains
`branch_issue_pattern` in any case but is not the exact heading (`## branch_issue_pattern:`,
`## Branch_Issue_Pattern`, `## branch_issue_pattern (ERE)`). The script prints a note and no
issue number, and exits 1. It does not fall back to a lower layer, the userConfig, or the
default, because any of those could close the wrong issue. A higher-precedence layer that
already supplied a valid pattern wins, since the lower layer is never read.
- A layer is reported and skipped, and resolution continues with the next source, when the
section's first value line is a heading or an HTML comment (`<!--`), its fence is empty or
unterminated, or its pattern fails validation.
- A layer whose section exists but yields no usable pattern **stops resolution**. The script
prints a note containing `resolution stopped` and no issue number, and exits 1. It does not
fall back to a lower layer, the userConfig, or the default, because any of those could close
the wrong issue; the no-number path already exists and `create` relays the note. A
higher-precedence layer that already supplied a valid pattern wins, since the lower layer is
never read. The stop reasons:
- a **near-miss heading**: an H2 outside a fence whose text contains `branch_issue_pattern` in
any case but is not the exact heading (`## branch_issue_pattern:`, `## Branch_Issue_Pattern`,
`## branch_issue_pattern (ERE)`);
- a section with no value before the next H2 or the end of the file;
- a first value line that is a heading or an HTML comment (`<!--`);
- an empty or unterminated fence;
- a pattern that fails validation.

This diverges from the config-cascade rule to degrade soft on a malformed layer, and this repo's
`config-cascade` convention records it as a declared deviation. A userConfig value that fails
validation is still reported and ignored, so the default applies.

A pattern passes validation when it compiles as an ERE and keeps within these limits, which are
checked before it is compiled: at most 200 characters, every `{m}`, `{m,}`, or `{m,n}` bound at
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ If SKILL.md's "Branch-to-issue grammar" surface shows a configured `branch_issue
ISSUE_NUM=$(bash "${CLAUDE_PLUGIN_ROOT}/skills/pull-request/scripts/parse-branch-issue.sh" "" '<branch-issue-pattern>' || true)
```

The script's stderr is left visible on purpose: it carries the deprecation note when the userConfig value is used, and a note naming the source and the reason (never the pattern text) when a layer or the userConfig value is skipped (a heading or HTML comment as the first value line, an empty or unterminated code fence, or a pattern that is invalid, holds a backreference, or breaks the length, bound, or nested-quantifier limit), when a near-miss heading such as `## branch_issue_pattern:` stops resolution (no issue number, whatever the lower sources say), or when a match yields no numeric id. Relay any note to the user; stdout carries only the issue number. Fill `<branch-issue-pattern>` with the resolved ERE. Its last capture group must resolve to the numeric GitHub issue number (a pattern with no capture group, or a non-numeric capture such as a bare Jira key, prints nothing and takes the no-closure path); configure a scheme that captures the number wherever it sits, e.g. `^[^/]+/([0-9]+)-` for `alice/1234-slug` or `-([0-9]+)$` for `feat/add-widget-1234`.
The script's stderr is left visible on purpose: it carries the deprecation note when the userConfig value is used, and a note naming the source and the reason (never the pattern text) when a layer's section exists but yields no usable pattern (a near-miss heading such as `## branch_issue_pattern:`, no value, a heading or HTML comment as the first value line, an empty or unterminated code fence, or a pattern that is invalid, holds a backreference, or breaks the length, bound, or nested-quantifier limit), which stops resolution with no issue number whatever the lower sources say; when the userConfig value breaks one of those pattern checks and is ignored; or when a match yields no numeric id. Relay any note to the user; stdout carries only the issue number. Fill `<branch-issue-pattern>` with the resolved ERE. Its last capture group must resolve to the numeric GitHub issue number (a pattern with no capture group, or a non-numeric capture such as a bare Jira key, prints nothing and takes the no-closure path); configure a scheme that captures the number wherever it sits, e.g. `^[^/]+/([0-9]+)-` for `alice/1234-slug` or `-([0-9]+)$` for `feat/add-widget-1234`.

```bash
CLOSES_LINE=""
Expand Down
Loading
Loading