aboutsummaryrefslogtreecommitdiff
path: root/claude-rules
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-22 16:59:56 -0500
committerCraig Jennings <c@cjennings.net>2026-05-22 16:59:56 -0500
commit3cb467e6aa4356f9912d661ef12d581d61b65cb6 (patch)
tree6cc1e0634dedcdbbf68fcfc6d6696492fa96ad82 /claude-rules
parentb37e4f8cb494ca14b9a00c967c7df8bd8d0a9ee1 (diff)
downloadrulesets-3cb467e6aa4356f9912d661ef12d581d61b65cb6.tar.gz
rulesets-3cb467e6aa4356f9912d661ef12d581d61b65cb6.zip
feat: split team publishing rules into an installable overlay
The global commits.md carried DeepSat-specific publishing steps — Linear ticket-state moves, the Slack notification protocol with its channel ID and engineer names, the deepsat.ghe.com host, the team merge norm. Those are symlinked into every project on the machine, so they sat as dead weight in personal repos and risked misfiring where there's no Linear ticket to move or Slack mpdm to ping. I split them out. commits.md keeps the universal skeleton (identity, attribution, commit format, the review-and-publish gate, verification) and replaces the team steps with seams: "run the project's publishing overlay here if it defines one," the same pattern startup.org uses for startup-extras. A project with no overlay runs the complete flow, just without ticket and chat integration. The DeepSat specifics move to teams/deepsat/claude/rules/publishing.md. That file is not a global rule — install-team.sh copies it into one project's .claude/rules/ (make install-team TEAM=deepsat PROJECT=...), keyed on the PROJECT argument, so only the named project gets it. Location decides distribution: claude-rules/ is the global-symlink set, teams/ is targeted-copy, so the overlay reaches DeepSat and nowhere else. The startup freshness check (sync-language-bundle.sh) now covers team overlays alongside language bundles: a process_bundle function handles both, with a team syncing only its own rule (no generic rules, hooks, or settings — those belong to a language bundle). A drifted overlay rule auto-fixes from canonical at the project's next startup, the same mechanism language bundles already ride. Tested: 3 new bats cases (team overlay clean / drifted-and-fixed / does-not-pull-generic-rules) on top of the 11 existing; install-team + sync verified end-to-end against a temp project. make test green, shellcheck clean.
Diffstat (limited to 'claude-rules')
-rw-r--r--claude-rules/commits.md61
1 files changed, 16 insertions, 45 deletions
diff --git a/claude-rules/commits.md b/claude-rules/commits.md
index aeb25cb..a467d93 100644
--- a/claude-rules/commits.md
+++ b/claude-rules/commits.md
@@ -187,26 +187,21 @@ Edge case: when one of these files *is* the change (a commit in the rulesets rep
Different artifact types carry different content. Don't duplicate.
-**Linear ticket bodies:** two sections, in order.
+**PR descriptions:** four sections, in order.
1. **Problem** — what's wrong, with enough detail that a teammate can
recognize the same failure mode in their own work.
-2. **Fix** — what changed (or what's proposed).
-
-The causal "why" and the test verification belong in the PR, not the
-ticket. Linear's GitHub integration auto-cross-links once the PR body
-includes the `Linear:` line, so the ticket reader reaches the PR
-without needing a body-level link.
-
-**PR descriptions:** four sections, in order. Same first two as the
-ticket, plus:
-
+2. **Fix** — what changed.
3. **Why this fixes it** — causal link, one or two sentences.
4. **How it was tested** — skip for proposals, specs, or discussions;
required for shipped fixes.
The PR is the technical artifact. It carries the detail.
+If the project's publishing overlay defines a ticket system, see it for
+ticket-body conventions (a ticket body is typically just the Problem and
+Fix, with the causal why and test verification left to the PR).
+
**PR review comments** are conversational and don't follow this
structure — they follow the Voice and Focus rules above.
@@ -318,7 +313,7 @@ Either way the draft runs through `/voice personal` first. The subflows below de
**For PR descriptions:**
-1. Write the title as line 1 and the body below it to `/tmp/pr-<ticket-or-slug>.md`. **Title format:** `<conventional-commit subject> (<TICKET-ID>)` — the ticket ID goes at the end in parentheses (e.g. `refactor: remove dead if-count-is-not-None check in admin (SE-289)`). If there is no ticket, omit the parenthetical. The body must also include a `Linear: [<TICKET-ID>](<linear-url>)` line so Linear's GitHub integration auto-cross-links the PR to the ticket. If there is no ticket, state that explicitly ("Linear: n/a") so reviewers know it was considered.
+1. Write the title as line 1 and the body below it to `/tmp/pr-<slug>.md`. **Title format:** the conventional-commit subject (`refactor: remove dead if-count-is-not-None check in admin`). If the project defines a publishing overlay with a ticket system, follow it for the ticket suffix in the title and the cross-link line in the body (see the overlay).
2. Run `/voice personal` on the file. The PR title stays imperative per Conventional Commits — `/voice personal` rewrites the body, not the title.
3. Print the final draft inline in the terminal. Title on line 1, blank line, then body — exactly as it'll be posted. State that the skill ran. Surface any pattern #39 (public-artifact scope) warnings.
4. Ask: approve, request changes, or open in editor. Wait for an explicit answer. Do not open the file in `emacsclient` (or any editor) by default.
@@ -327,16 +322,15 @@ Either way the draft runs through `/voice personal` first. The subflows below de
- **Open in editor** → only if the user asks. `emacsclient -n /tmp/pr-<ticket-or-slug>.md`. After the editor closes, re-read the file, re-print inline, ask again.
5. Split the file on the first blank line and pass the title and body to `gh pr create --title "..." --body "$(tail -n +3 <file>)"` (or a heredoc) so formatting is preserved. Add `--reviewer <user[,user...]>` in the same call when you already know who should review.
6. Request reviewers on the new PR if you didn't pass `--reviewer` at create time. Use `gh pr edit <N> --add-reviewer <user>`. If the repo has a `CODEOWNERS` file, GitHub auto-suggests based on touched paths. Still issue the explicit request so the reviewer gets notified. Pick reviewers per the team's convention for the area touched (often documented in the per-repo `CLAUDE.md`). For follow-up PRs, consider tagging the parent PR's author if their context would help. PRs without a human reviewer request stall — "checks passed" is not a substitute for review.
-7. After `gh pr create` returns a URL, post a comment on the linked Linear ticket with the PR URL (use the Linear MCP `save_comment` tool, or open the ticket manually if MCP is unavailable). This closes the ticket→PR direction of the cross-link.
-8. Move the Linear ticket to the "Dev Review" status (use `save_issue` with the Dev Review state ID, or the Linear UI). The ticket should not remain "In Progress" once a PR is open against it.
+7. **Project publishing overlay (if present).** If the project defines a publishing overlay — a `publishing-<team>.md` rule loaded from its `.claude/rules/` — run its post-create steps now: ticket cross-linking, ticket-state moves, and any other tracker integration it specifies. A project with no overlay skips this; the PR is already open and reviewers are requested, which is the complete universal flow.
**For PR review comments and replies (review verdicts, threaded discussion, follow-up notes on someone else's PR or your own):**
Pick the shape first. Most reviews are Shape 1.
-- **Shape 1 — Single review** (verdict + summary body + 0+ inline pins). The default for any post that carries a verdict (`APPROVE`, `REQUEST_CHANGES`, `COMMENT`), even when the verdict has no line-specific findings. One `gh api` call posts the summary, every inline pin, and the verdict together. Slack notification fires once for `APPROVE` or `REQUEST_CHANGES`.
-- **Shape 2 — Issue-thread comment** (no verdict). General PR discussion, not a review. No inline pins. No Slack notification.
-- **Shape 3 — Reply on an existing inline thread**. Responding to a specific prior reviewer comment. Threads under that comment. No Slack notification.
+- **Shape 1 — Single review** (verdict + summary body + 0+ inline pins). The default for any post that carries a verdict (`APPROVE`, `REQUEST_CHANGES`, `COMMENT`), even when the verdict has no line-specific findings. One `gh api` call posts the summary, every inline pin, and the verdict together. review notification fires once for `APPROVE` or `REQUEST_CHANGES`.
+- **Shape 2 — Issue-thread comment** (no verdict). General PR discussion, not a review. No inline pins. No review notification.
+- **Shape 3 — Reply on an existing inline thread**. Responding to a specific prior reviewer comment. Threads under that comment. No review notification.
**Inline threshold for Shape 1.** Any finding that names a `path:line` belongs as an inline comment pinned to that line. Cross-cutting observations (verdict rationale, "third PR with the same pattern", overall test-coverage gaps that don't pin to one place) stay in the summary body. There's no "fold one inline into the summary" exception — a single line-specific finding still goes inline.
@@ -383,34 +377,11 @@ Pick the shape first. Most reviews are Shape 1.
-F "comments[][body]=<inline 2>"
```
- `event` is one of `APPROVE`, `REQUEST_CHANGES`, `COMMENT`. The `comments[]` array can be empty for verdicts with zero line-specific findings — the call still uses the same endpoint. Pass `--hostname` for non-`github.com` hosts (e.g. `deepsat.ghe.com`).
+ `event` is one of `APPROVE`, `REQUEST_CHANGES`, `COMMENT`. The `comments[]` array can be empty for verdicts with zero line-specific findings — the call still uses the same endpoint. Pass `--hostname` for non-`github.com` hosts (a project's publishing overlay names its host when it's a GitHub Enterprise instance).
7. Verify the review landed. `gh api repos/<owner>/<repo>/pulls/<N>/reviews --hostname ...` returns the latest review with bundled inlines. Confirm `state` matches the verdict and the inline count matches what was posted.
-8. **Notify the PR author on Slack** (`APPROVE` or `REQUEST_CHANGES` verdicts only). After the review posts, send a one-line Slack message to the PR-review group DM `C0B1B0NH2N5` (the mpdm with Craig, Eric, Vrezh, Kostya — *not* `C0AM2MWHCJU`, which is a separate 3-person Craig/Vrezh/Kostya mpdm and is *not* an "#ai" channel despite older notes calling it that) via the `slack-deepsat` MCP. No `/voice personal` pass — the message is short and templated. Two cases:
- - Approved: `<@author-slack-id> Approved PR #N.\n<pr-url>`
- - Changes requested: `<@author-slack-id> Changes Requested PR #N\n<pr-url>`
- Replace `#N` with the PR number and `<pr-url>` with the GHE URL of the PR. Always lead with an `<@author-slack-id>` mention so the engineer who owns the merge decision gets pinged — the message is useless without it. Resolve the Slack user ID via the slack-deepsat `users_search` tool using the PR author's name or email. The URL goes on its own line, immediately after the message. If the MCP doesn't have a post-message tool registered (for example, in a project where the Slack MCP is read-only), surface that to the user and skip the post — don't fall back to a different channel or asking the user to paste it.
-
- **MCP payload format.** The `mcp__slack-deepsat__conversations_add_message` tool's `payload` parameter is the **raw Slack mrkdwn message body**, not a Slack API JSON envelope. Pass the message body string directly:
-
- ```
- payload: <@U09AUV4087N> Approved PR #171.
- https://deepsat.ghe.com/deepsat/orchestration_dashboard_mvp/pull/171
- ```
-
- Wrapping the body in `{"text": "..."}` posts the literal JSON object as the message — the `<@>` mention strips to plain text, escaped `\n` renders as the letter `n`, and the `text:` key prefix appears in the post. The engineer doesn't get pinged. Use real newlines in the string (a literal newline character), not the escape sequence. The MCP has no delete method, so a garbled post can only be removed manually from Slack — surface garble to the user immediately so they can clean it up before reposting.
-
- Slack mrkdwn that the `payload` string supports: `<@USER_ID>` for user mentions, `<#CHANNEL_ID>` for channel refs, real newlines for line breaks, `*bold*`, `_italic_`, ``\`code\``` and ``` ``` ``` blocks, and `<url|label>` for labeled links.
-
- **Thread under the engineer's review-request post.** Before posting, scan the channel's recent history (`mcp__slack-deepsat__conversations_history`) for the engineer's original post containing the PR URL — typically a "New PR" message or just the PR link. Post the verdict notification with `thread_ts=<original-message-ts>` so it lands in the reply thread off that original. This keeps each PR's review state contained in one thread instead of fragmenting across top-level posts in a mixed feed. The same threading rule applies to both `APPROVE` and `REQUEST_CHANGES` verdicts.
-
- **No review-request post found?** Don't auto-pick a fallback. Ask the user which they prefer:
- - Post the verdict at the top level of the channel.
- - DM the engineer directly.
- - Don't post at all.
-
- Skip Slack for `event=COMMENT` reviews (informal feedback, not a verdict) and for Shapes 2 and 3 below. Approve and request-changes are the only verdicts that warrant the notification.
+8. **Project review-notification overlay (if present).** If the project defines a publishing overlay with a review-notification step (e.g. a Slack ping to the PR author), run it now — but only for `APPROVE` and `REQUEST_CHANGES` verdicts. The overlay owns the channel, the message format, the author-mention lookup, and the threading. A project with no overlay skips notification entirely. `COMMENT` verdicts and Shapes 2-3 below never notify, overlay or not.
**Shape 2: Issue-thread comment (no verdict)**
@@ -421,7 +392,7 @@ Use when the post is informal discussion that shouldn't appear as a review verdi
3. Print inline, ask approve/changes/edit, gate as in Shape 1 step 5.
4. Post: `gh pr comment <N> --body-file /tmp/pr-<N>-comment.md`.
5. Verify: `gh api repos/<owner>/<repo>/issues/<N>/comments`.
-6. No Slack notification.
+6. No review notification.
**Shape 3: Reply on an existing inline thread**
@@ -433,9 +404,9 @@ Use when responding to a specific prior reviewer comment.
4. Print inline, ask approve/changes/edit, gate as in Shape 1 step 5.
5. Post: `gh api repos/<owner>/<repo>/pulls/<N>/comments -F in_reply_to=<comment-id> -F body="$(cat /tmp/pr-<N>-reply-<comment-id>.md)"`.
6. Verify in the same `comments` list.
-7. No Slack notification.
+7. No review notification.
-**Approve does not authorize a merge.** The team's practice is approve-then-author-merges, not approve-and-merge. The Slack notification in Step 8 hands the merge decision to the PR author. Anything in `## Merge Strategy` below applies only to merges *you* are about to perform on your own branches — and even then, the merge needs its own explicit user confirmation per the rules there.
+**Approve does not authorize a merge.** Reviewing a PR never authorizes merging it. Anything in `## Merge Strategy` below applies only to merges *you* are about to perform on your own branches — and even then, the merge needs its own explicit user confirmation per the rules there. A project's publishing overlay may add a team merge practice (e.g. approve-then-author-merges, where the review notification hands the merge decision to the PR author); that's an overlay concern, not a global one.
**Exception:** trivial one-liners the user dictated verbatim in the
conversation (e.g. "commit this as `chore: bump version`", "reply just