aboutsummaryrefslogtreecommitdiff
path: root/claude-rules
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-07 16:22:40 -0500
committerCraig Jennings <c@cjennings.net>2026-05-07 16:22:40 -0500
commit46cf5b4c3e10aec4293297d05e4abfe3caac71eb (patch)
treee6c99d8621860a3e8bb0a3e5fa11f5301bff5747 /claude-rules
parentdcb8c1dcf52c86645c730b4f0076800069926660 (diff)
downloadrulesets-46cf5b4c3e10aec4293297d05e4abfe3caac71eb.tar.gz
rulesets-46cf5b4c3e10aec4293297d05e4abfe3caac71eb.zip
docs(commits): add bundled-review shape, voice mode gating, drop humanizer
I rewrote the PR review subflow into three explicit shapes. Shape 1 is a single review that bundles the verdict, the summary body, and zero or more inline pins into one `gh api .../reviews` call. Shape 2 is an issue-thread comment with no verdict. Shape 3 is a reply on an existing inline thread. The single-review path replaces the prior pattern where a Request-Changes verdict with line-specific findings needed separate `gh pr review` + `gh pr comment` calls. That fragmented the Slack notification and the review history. I migrated all `humanizer` references to `/voice personal`. The voice skill replaced humanizer, so the old name was dead. I dropped the two lineage mentions of "humanizer's signs of AI writing" since they pointed at a skill that no longer exists. I added a Voice mode and approval gate preamble at the top of Step 2. The mode is decided by whether `.ai/` is tracked in the repo. Gitignored or absent means personal-voice with the full approval gate. Tracked means general-voice and the gate is skipped, since the personal-only patterns (first-person rewrite, contractions, semicolon swap) don't fit a shared rules file. I also updated the Single-skill gate wrap-up paragraph at the end of Step 2 to reference both modes.
Diffstat (limited to 'claude-rules')
-rw-r--r--claude-rules/commits.md117
1 files changed, 101 insertions, 16 deletions
diff --git a/claude-rules/commits.md b/claude-rules/commits.md
index b485506..a8ddb88 100644
--- a/claude-rules/commits.md
+++ b/claude-rules/commits.md
@@ -246,10 +246,21 @@ enough to skip review" exemption on top of it.
### Step 2: draft, review, publish
+**Voice mode and approval gate.** Before drafting, run a single command from the repo root to decide which mode applies:
+
+```
+git ls-files .ai/ 2>/dev/null | head -1
+```
+
+- **No output** — `.ai/` is gitignored, missing, or empty. **Personal-voice mode**: drafts run through `/voice personal` (39 patterns including the 8 personal-only ones), and the **approval gate applies**. Write to `/tmp`, run the voice pass, print inline, ask approve / request changes / open in editor, then publish only on explicit approval.
+- **Any output** — one or more files under `.ai/` are tracked. The `.ai/` layer is shared with the team, so the personal voice patterns (first-person rewrite, contraction enforcement, semicolon swap, etc.) don't fit. **General-voice mode**: drafts run through `/voice` (general mode, 31 patterns; the 8 personal-only patterns are skipped), and the **approval gate is skipped**. Write to `/tmp`, run the voice pass, print inline, publish immediately.
+
+The subflows below describe the personal-voice path with the full gate. For the general-voice path: substitute `/voice` for `/voice personal` everywhere, and collapse the "Ask: approve, request changes, or open in editor" step — the draft prints inline and the publish step runs immediately afterward.
+
**For commit messages:**
1. Write the proposed message to `/tmp/commit-<short-slug>.md`.
-2. Run `/voice personal` on the file. Always. The skill walks 39 patterns covering humanizer's signs of AI writing, universal good-writing rules (Strunk & White, Orwell, Plain English, Garner), and the personal voice patterns (first-person rewrite, semicolons → periods/commas, contractions, sentence-split on conjunctions, felt-experience cut, sentence-fragment rewrite, terse cut for rhetorical padding, public-artifact scope flag). The commit subject line stays imperative per Conventional Commits — `/voice personal` rewrites the body, not the subject. Skip the pass for purely mechanical commits (a chore version bump, a typo fix) where the subject alone carries the message.
+2. Run `/voice personal` on the file. Always. The skill walks 39 patterns covering signs of AI writing, universal good-writing rules (Strunk & White, Orwell, Plain English, Garner), and the personal voice patterns (first-person rewrite, semicolons → periods/commas, contractions, sentence-split on conjunctions, felt-experience cut, sentence-fragment rewrite, terse cut for rhetorical padding, public-artifact scope flag). The commit subject line stays imperative per Conventional Commits — `/voice personal` rewrites the body, not the subject. Skip the pass for purely mechanical commits (a chore version bump, a typo fix) where the subject alone carries the message.
3. Print the final draft inline in the terminal. Every line, exactly as it'll be committed. No truncation, no summary. State that the skill ran (e.g. "/voice personal — 39 patterns walked"). If pattern #39 (public-artifact scope) flagged anything, surface those warnings; the user resolves them manually.
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 — print first, edit only if asked.
- **Approve** → commit with `git commit -F /tmp/commit-<short-slug>.md`.
@@ -272,23 +283,97 @@ enough to skip review" exemption on top of it.
**For PR review comments and replies (review verdicts, threaded discussion, follow-up notes on someone else's PR or your own):**
-1. Write the proposed comment to `/tmp/pr-<N>-comment.md` (or `/tmp/pr-<ticket>-comment.md` if the ticket ID is clearer).
-2. Run `/voice personal` on the file. Always. The *Voice and Focus* rules — especially the colleague-tone framing — matter the most here. The comment is directed at a specific person who will reply, and the thread is public.
-3. Print the final draft inline in the terminal. State that the skill ran. Surface any pattern #39 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.
- - **Approve** → continue to step 5.
- - **Request changes** → make them, re-run `/voice personal`, re-print inline, ask again.
- - **Open in editor** → only if the user asks. `emacsclient -n /tmp/pr-<N>-comment.md`. After the editor closes, re-read the file, re-print inline, ask again.
-5. Post with the `gh` command that matches the comment type:
- - Review-shaped comment (the reviewer's overall verdict on the PR): `gh pr review <N> --comment --body-file /tmp/pr-<N>-comment.md` (use `--approve` or `--request-changes` in place of `--comment` when formally approving or blocking).
- - Issue-thread comment (general PR discussion, not a formal review): `gh pr comment <N> --body-file /tmp/pr-<N>-comment.md`.
- - Inline code comment pinned to a specific line/hunk: no direct `gh` flag — use `gh api /repos/<owner>/<repo>/pulls/<N>/comments -F body=@/tmp/pr-<N>-comment.md -F commit_id=... -F path=... -F line=...`.
-6. Verify the comment landed. `gh api /repos/<owner>/<repo>/pulls/<N>/reviews` for review-shaped comments, `gh api /repos/<owner>/<repo>/issues/<N>/comments` for issue-thread comments.
-7. **Notify the PR author on Slack** (review-shaped comments only — `--approve` or `--request-changes`). After the review posts, send a one-line Slack message to channel `C0AM2MWHCJU` via the `slack-deepsat` MCP. No `/voice personal` pass — the message is short and templated. Two cases:
+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.
+
+**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.
+
+**Shape 1: Single review (bundled summary + inline)**
+
+1. Identify findings, split into **inline-eligible** (each names a specific `path:line`) and **summary-only** (cross-cutting). Decide the verdict.
+
+2. Write one concatenated draft to `/tmp/pr-<N>-review.md` with explicit separators:
+
+ ```
+ === SUMMARY ===
+ <verdict summary body>
+
+ === INLINE path=frontend/src/foo.tsx line=440 ===
+ <inline body 1>
+
+ === INLINE path=frontend/src/bar.tsx line=137 ===
+ <inline body 2>
+ ```
+
+ The separator format is exactly `=== SUMMARY ===` and `=== INLINE path=<path> line=<n> ===`. The summary block is mandatory even for verdict-only reviews. Inline blocks are zero-or-more.
+
+3. Run `/voice personal` on the file once. The skill walks all 39 patterns across every block at the same time. The separators stay intact because they aren't prose.
+
+4. Print the final draft inline in the terminal. Every block, exactly as it'll be posted, with its separator header. State that the skill ran (e.g. "/voice personal — 39 patterns walked across summary + 3 inline"). Surface any pattern #39 warnings.
+
+5. 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.
+ - **Approve** → continue to step 6.
+ - **Request changes** → make them, re-run `/voice personal` on the whole file, re-print inline, ask again.
+ - **Open in editor** → only if the user asks. `emacsclient -n /tmp/pr-<N>-review.md`. After the editor closes, re-read, re-print inline, ask again.
+
+6. Split the file on the separator lines and post in **a single** `gh api` call:
+
+ ```
+ gh api repos/<owner>/<repo>/pulls/<N>/reviews \
+ --hostname <ghe-host-or-omit> \
+ -F event=REQUEST_CHANGES \
+ -F body="<summary block>" \
+ -F "comments[][path]=<path1>" \
+ -F "comments[][line]=<line1>" \
+ -F "comments[][body]=<inline 1>" \
+ -F "comments[][path]=<path2>" \
+ -F "comments[][line]=<line2>" \
+ -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`).
+
+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 channel `C0AM2MWHCJU` 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.
- Skip this step for plain `--comment` reviews (informal feedback, not a verdict) and for issue-thread or inline code comments. Approve and request-changes are the only verdicts that warrant the notification.
+
+ **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.
+
+**Shape 2: Issue-thread comment (no verdict)**
+
+Use when the post is informal discussion that shouldn't appear as a review verdict (e.g. "I'd like to discuss the X approach before you continue").
+
+1. Write the proposed comment to `/tmp/pr-<N>-comment.md`.
+2. Run `/voice personal`.
+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.
+
+**Shape 3: Reply on an existing inline thread**
+
+Use when responding to a specific prior reviewer comment.
+
+1. Find the parent comment ID: `gh api repos/<owner>/<repo>/pulls/<N>/comments`.
+2. Write the reply to `/tmp/pr-<N>-reply-<comment-id>.md`.
+3. Run `/voice personal`.
+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.
**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.
@@ -298,7 +383,7 @@ conversation (e.g. "commit this as `chore: bump version`", "reply just
`/review-code` in Step 1 still runs when it applies; Phase 0 of that skill
handles trivial diffs, and acknowledgment-only replies don't need it at all.
-**Single-skill gate.** Each of the three subflows above runs `/voice personal` before printing the draft. The skill walks 39 patterns in one editorial review — humanizer's 25 signs of AI writing plus 6 universal good-writing patterns plus 8 personal-only patterns. Running it is mandatory; the printed draft must have been through it. When the user asks mid-flow for "the voice pass" on an in-progress draft, that means re-run the full 39-pattern walk in personal mode — not a subset. Always state that the skill ran when announcing the printed draft (e.g. "/voice personal — 39 patterns walked"). Skipping the pass without flagging it is a defect.
+**Single-skill gate.** Each of the three subflows above runs the voice skill before printing the draft. In personal-voice mode it's `/voice personal` — 39 patterns covering AI-writing signs, universal good-writing rules, and the 8 personal-only patterns. In general-voice mode it's `/voice` — the same 31 patterns minus the 8 personal-only ones. The mode is determined by the **Voice mode and approval gate** preamble at the top of Step 2. Running the skill is mandatory; the printed draft must have been through it. When the user asks mid-flow for "the voice pass" on an in-progress draft, that means re-run the full pattern walk in the current mode — not a subset. Always state that the skill ran when announcing the printed draft (e.g. "/voice personal — 39 patterns walked" or "/voice — 31 patterns walked"). Skipping the pass without flagging it is a defect.
### Hook-level authorization