ai: Split commit discipline into focused skills
Moves commit discipline from optional Version Control section to
mandatory System Prompt, signaling universal application across all
work. Splits committing-with-discipline into two focused concerns:
describing-changes skill (message content—explaining reasoning) and
process verification (checking what's being committed). This separation
enables describing-changes to apply beyond commits to PRs,
documentation, and code reviews.
Adds /describe command to update jj revision descriptions after
committing, enabling refinement before push without history rewrites.
Updates all command documentation with conventional type prefixes (fix:,
feat:, docs:, ai:, etc.) for consistency.
Together these establish a unified, mandatory approach to change
documentation across commits, reviews, and documentation—not just ad-hoc
reasoning.
Assisted-by: Claude Haiku 4.5 via Claude Code
diff --git a/.gitignore b/.gitignore
index c175ea5f..2fa3b0f7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,6 +46,7 @@ private
/ai/claude/*
!/ai/claude/CLAUDE.md
+!/ai/claude/commands
!/ai/claude/skills
diff --git a/ai/AI.md b/ai/AI.md
index 55294402..a08d005a 100644
--- a/ai/AI.md
+++ b/ai/AI.md
@@ -16,6 +16,8 @@ Rules:
Be honest, not agreeable.
+**MANDATORY:** Use the describing-changes skill when crafting any commit messages or change documentation.
+
Present only verified facts.
State directly when you cannot verify something:
@@ -105,9 +107,8 @@ footer with model and tool:
Assisted-by: Claude Sonnet 4 via Claude Code
```
-Use the committing-with-discipline skill whenever crafting commit messages,
-especially when working with superpowers plugins. This ensures commits are
-well-structured, intentional, and follow your project's conventions.
+Use the describing-changes skill to explain reasoning and decisions in commit
+messages. Focus on why the change was made rather than what the diff shows.
## Ruby
diff --git a/ai/claude/commands/commit.md b/ai/claude/commands/commit.md
index cbda1d83..cc5ee90e 100644
--- a/ai/claude/commands/commit.md
+++ b/ai/claude/commands/commit.md
@@ -1,18 +1,18 @@
# Commit Changes
-Invoke the `committing-with-discipline` skill before committing. This
-command helps you create commits with proper context, verification, and
-discipline.
+Use the `describing-changes` skill to draft clear, reasoning-focused commit messages.
+The `elements-of-style` writing skill is required as a sub-skill for clarity.
Usage:
- `/commit` - Commit all current changes
- `/commit <fileset>` - Commit only files matching the fileset expression
Before committing:
-1. Use the `committing-with-discipline` skill to ensure proper discipline
+1. Use the `describing-changes` skill to draft the commit message explaining reasoning
+ - The skill requires `elements-of-style:writing-clearly-and-concisely` for clarity
2. Check if a fileset was provided as arguments
-3. If a fileset is provided, use `jj commit <fileset>` to commit only
-matching files
+3. If a fileset is provided, use `jj commit <fileset>` to commit only matching files
4. If no fileset is provided, use `jj commit` to commit all changes
5. Include the "Assisted-by" footer with the current model and tool being used
6. Use a HEREDOC for the commit message to ensure proper formatting
+7. Verify the commit with `jj show` to confirm only intended changes were committed
diff --git a/ai/claude/commands/describe.md b/ai/claude/commands/describe.md
new file mode 100644
index 00000000..a0ee6365
--- /dev/null
+++ b/ai/claude/commands/describe.md
@@ -0,0 +1,21 @@
+# Describe a Change
+
+Use the `describing-changes` skill to draft a reasoning-focused description for a jj revision.
+The `elements-of-style` writing skill is required as a sub-skill for clarity.
+
+Usage:
+- `/describe <revision>` - Describe a specific revision (e.g., `-1`, `@-`, `r`, or commit ID)
+
+Revision argument is required.
+
+**Important:** Completely discard the existing revision description. This command rewrites it entirely.
+
+Process:
+1. View the target revision with `jj show -r <revision>` to understand the changes
+2. Use the `describing-changes` skill to draft a new description explaining reasoning
+ - Do NOT reference or build on the existing message
+ - Start from scratch explaining why this change exists
+ - The skill requires `elements-of-style:writing-clearly-and-concisely` for clarity
+3. Use `jj describe -r <revision>` with the drafted message, including the "Assisted-by" footer
+4. Use a HEREDOC for the description to ensure proper formatting
+5. Verify with `jj show -r <revision>` to confirm the message was updated
diff --git a/ai/claude/skills/committing-with-discipline/SKILL.md b/ai/claude/skills/committing-with-discipline/SKILL.md
deleted file mode 100644
index fd4e6161..00000000
--- a/ai/claude/skills/committing-with-discipline/SKILL.md
+++ /dev/null
@@ -1,253 +0,0 @@
----
-name: committing-with-discipline
-description: |
- Use when creating or reviewing commits, before using git/jj to commit -
- enforces contextual commit messages that document reasoning (not just what
- changed) and verifies commits contain exactly intended changes
----
-
-# Committing with Discipline
-
-## Overview
-
-A commit message should document *why* a change was made, not *what* changed.
-The diff already shows what. Your message adds the context, reasoning, and
-decisions the code alone cannot convey. This skill prevents the
-rationalizations that lead to lazy commits: vague messages, batched unrelated
-changes, skipped verification, and stripped-down messages for "obvious"
-changes.
-
-## When to Use
-
-- Before running `jj commit` or `git commit`
-- When reviewing a commit before pushing
-- When tempted to batch unrelated changes
-- When the change feels "obvious" and doesn't need explanation
-- Before closing the work - verification is part of discipline
-
-## Core Principle
-
-**Commit messages are for future readers, not current writers.**
-
-A change that's obvious to you now won't be obvious:
-- To future you (in 6 months, what was the tradeoff?)
-- To someone reviewing the codebase
-- During debugging when you need to understand *why* a line exists
-
-## Red Flags - STOP and Rethink
-
-If you catch yourself thinking any of these, stop and apply the discipline:
-
-- "This is obvious/self-explanatory" → If obvious, explain why it's the right approach
-- "It's just cleanup" → Cleanup of unrelated code should be separate commit(s)
-- "I'll remember this later" → You won't. The commit message is for later
-- "It's blocking other work" → Blocking is a reason to commit, not to batch unrelated changes
-- "It's a quick fix" → Quick doesn't exempt you from explaining it
-- "No one else works on this" → Lower standards = unmaintainable code later
-- "I can amend later" → Amending forces history rewrites. Do it right the first time
-- "The diff is small/obvious" → Document the *decision*, not the size
-- "I need to explain this thoroughly" → Verbosity obscures reasoning. Cut ruthlessly
-
-All of these mean: Stop. Follow the discipline. Your future self will thank you.
-
-## The Discipline
-
-### 1. Check What You're Committing (MANDATORY)
-
-```bash
-# Before ANY commit, see exactly what will be committed
-jj diff # See all changes
-jj status # See file list and states
-```
-
-**The question:** Do all these files belong together in one logical change?
-
-**If no:** Create separate commits. Batching different logical changes is never justified.
-
-**Reality check:** If you can't explain why these files changed together in 1-2
-sentences, they don't belong in the same commit.
-
-**No exceptions:**
-- Not even if "they're both quick changes"
-- Not even if "one builds on the other"
-- Not even if "I'm under time pressure"
-- Not even if "the files are related"
-
-Different logical changes = different commits. Period.
-
-### 2. Understand the Context
-
-Before writing your message, answer these questions:
-
-- What problem does this change solve?
-- Why is this the right approach (vs alternatives)?
-- Are there relevant decisions, tradeoffs, or constraints?
-- Will there be questions when reviewing this commit in 6 months?
-
-**Write these answers down as your message draft.** Don't trust your memory.
-
-### 3. Apply Writing Discipline (MANDATORY)
-
-**Before drafting any commit message, use the `elements-of-style:writing-clearly-and-concisely`
-skill.** This is not optional. Commit messages are prose for humans; the skill
-applies unconditionally.
-
-Key principles from Strunk for commit messages:
-
-- Use active voice (Rule 10): "Fixed X" not "X was fixed"
-- Put statements in positive form (Rule 11): State what you did, not what was wrong
-- Use definite, specific language (Rule 12): Avoid vague descriptions
-- Omit needless words (Rule 13): Strip anything visible in the diff
-- Place emphatic words at the end (Rule 18): Key reasoning should be prominent
-
-### 4. Draft the Message
-
-The diff shows what changed. Your message explains why:
-
-**Structure:**
-- **Opening line:** Succinct imperative + why (5-10 words). Must stand alone.
-- **Reasoning (if needed):** Default 2-3 sentences. Expand only if context genuinely requires it.
-- **Gotchas (if any):** Only if critical and non-obvious.
-
-**CRITICAL:** Do NOT include anything visible in the diff. Strip ruthlessly. Every sentence must earn its place. Default to brevity; expand only with justification.
-
-**Test for bad messages:** If you could write your commit message after reading
-only the diff (without context), you are describing changes. Rewrite it to
-explain reasoning instead.
-
-**Bad example - describing the diff:**
-```
-Updated config file to use new API endpoint in lib/config.js
-Changed ENDPOINT_URL constant from v1 to v2
-Modified 3 lines: added new constant, updated old constant, added comment
-```
-
-(All visible in the diff. Reader sees what changed without needing your message.)
-
-**Bad example - listing what the code does:**
-```
-Added caching layer to user service
-
-Adds cache initialization, TTL configuration, and invalidation logic.
-Cache stores user objects by ID with 5-minute expiry.
-```
-
-(The diff shows the code. What's missing: why add caching now?)
-
-**Better - explaining the reasoning:**
-```
-Use API v2 endpoint to support OAuth scope requirements
-
-v1 lacks scope boundaries; v2 includes automatic retry, eliminating custom handler.
-```
-
-Message explains the business/technical decision in two sentences, not the code itself.
-
-### 5. Add Metadata Footer
-
-Include attribution for assisted commits:
-
-```
-Assisted-by: Claude <model> via Claude Code
-```
-
-Use a HEREDOC for proper formatting:
-
-```bash
-jj commit -m "$(cat <<'EOF'
-Your message here.
-
-Assisted-by: Claude Sonnet 4.5 via Claude Code
-EOF
-)"
-```
-
-### 6. Verify the Commit (MANDATORY)
-
-After committing, verify it contains exactly what you intended:
-
-```bash
-jj log --limit 1 # See the commit message
-jj show # See the full diff of what you just committed
-```
-
-**The question:** Does this commit contain exactly (and only) what you intended?
-
-**If no:** You made a mistake. The commit is saved, but you can:
-- Amend it: `jj describe -r @` (if not yet pushed)
-- Or create a follow-up commit fixing the issue
-
-**Verify every single commit. No exceptions:**
-- Not even if you're confident it's correct
-- Not even if the change is small
-- Not even if you're in a hurry
-- Verification finds mistakes. If you skip verification, you will push mistakes.
-
-**This is not optional.** Every. Single. Time.
-
-## Common Mistakes
-
-| Mistake | Why It Matters | Fix |
-|---------|----------------|-----|
-| Message describes files, not changes | Reader is lost about intent | Explain the *why* not the *what* |
-| Batching unrelated changes | Future debugging is confused | One logical change per commit |
-| "Quick fix" messages ("typo", "cleanup") | No context for future readers | Explain why, even if quick |
-| Skipping verification | Wrong files committed (or secrets leaked) | Always run `jj show` after committing |
-| Including obvious details | Wastes reader time | Trust the diff; add only non-obvious context |
-| Amending commits after push | Breaks history for others | Do it right the first time |
-
-## Example: Discipline in Action
-
-### Scenario
-You've fixed a caching bug in src/services/cache.ts. It was a simple one-line change.
-
-### The Temptation
-"One line change, obvious fix, let's commit: 'fix cache bug'"
-
-### The Discipline
-1. **Verify:** `jj status` → only cache.ts modified ✓
-2. **Understand:** Why did this bug exist? What was the wrong behavior? How does the fix work?
-3. **Write context:** "TTL wasn't being compared correctly. The bug was
- comparing timestamp (number) to TTL duration (also number), confusing
- milliseconds with seconds. Fixed by normalizing to milliseconds before
- comparison."
-4. **Draft message:**
- ```
- Fix cache TTL comparison by normalizing to milliseconds
-
- TTL (seconds) and timestamp (milliseconds) were compared without unit
- conversion, causing premature expiry. Now normalize both to milliseconds.
- ```
-5. **Verify:** `jj show` → confirms only the TTL comparison line changed ✓
-
-**Result:** Future debugger sees not just the fix, but *why* it was needed. No
-"why did they do this?" comments in code reviews.
-
-## Implementation
-
-When using this skill:
-1. Before ANY commit, run `jj diff` and `jj status`
-2. Verify all files in the commit belong together logically
-3. Answer the "why" questions before writing the message
-4. Draft your message explaining the *decision*, not the *diff*
-5. Use HEREDOC for formatting + attribution
-6. After committing, run `jj show` to verify exactly what was committed
-
-## Rationalizations to Close
-
-| Rationalization | Counter |
-|-----------------|---------|
-| "The diff is small, doesn't need explanation" | Small changes still need context. "Why is this line here?" matters more when code is simple |
-| "This is obvious to anyone reading the code" | You're wrong. Commit message is for future-you who forgot, and reviewers missing context |
-| "I don't have time for a real message" | You don't have time to debug this later when the context is gone |
-| "I'll fix the message later with amend" | Amend after push breaks history. Do it right now. Amend only before pushing |
-| "No one else works on this file" | Lower standards = abandoned codebase. Apply discipline to everything |
-| "This file changed, let me just include it" | Include it only if it belongs with this logical change. When in doubt, separate commits |
-| "I already verified it manually" | Manual verification is error-prone. Run `jj show`. No shortcuts |
-| "The message can be figured out from the diff" | The *what* can be figured out. The *why* cannot. Message documents decisions |
-| "These are both quick changes, I'll batch them" | Quick ≠ related. Different logical changes need different commits |
-| "I'll remember why I did this" | You won't. Write it down in the commit message now |
-| "The message format doesn't matter much" | Format matters less than context, but use HEREDOC for clean formatting |
-| "I've already reviewed the code, skip jj show" | Reviewing code ≠ verifying commit contents. Run `jj show` every time |
-| "I need to add more detail to be clear" | More words ≠ more clarity. Verbosity hides the real reason. Cut first; expand only if necessary |
-| "This deserves a long explanation" | Default to 2-3 sentences. Expand only if the commit genuinely needs more context to justify its changes |
diff --git a/ai/claude/skills/describing-changes/SKILL.md b/ai/claude/skills/describing-changes/SKILL.md
new file mode 100644
index 00000000..888fdb92
--- /dev/null
+++ b/ai/claude/skills/describing-changes/SKILL.md
@@ -0,0 +1,201 @@
+---
+name: describing-changes
+description: |
+ Use when drafting any explanation of changes (commit messages, PRs, code
+ reviews, docs) - focuses on documenting reasoning and decisions rather than
+ describing what changed
+---
+
+# Describing Changes
+
+## Overview
+
+A change description should explain *why* a decision was made, not *what* the code does. The diff already shows what. Your explanation adds the reasoning, tradeoffs, and context the code alone cannot convey.
+
+**Core principles:**
+- The reader can see the code changed. They cannot see why you chose this approach instead of alternatives, or what constraint forced the change.
+- **Be concise.** Readers skip lengthy explanations. 2-3 sentences covering the problem, solution, and tradeoff is sufficient. Strip everything visible in the diff.
+
+## When to Use
+
+About to explain why something changed? Use this skill.
+
+Applies to:
+- Commit messages
+- Pull request descriptions
+- Code review comments
+- Technical documentation
+- Change logs
+
+**When NOT to use:** When describing how code works (separate from why changes were made)
+
+## The Core Pattern
+
+| ❌ WHAT Focus | ✅ WHY Focus |
+|---|---|
+| "Added .toMilliseconds() call" | "Timestamp and TTL were compared without unit normalization, causing..." |
+| "Fixed cache expiration check" | "Cache entries expired prematurely because the stored timestamp used a different unit scale than..." |
+| "Updated endpoint from v1 to v2" | "v1 endpoint lacks OAuth scope support needed for permission boundaries. v2 provides..." |
+| "Changed loop logic" | "Original loop terminated early when encountering null values, breaking batching for partial datasets. New logic..." |
+
+**Ask yourself:** Can I write this explanation after reading only the diff? If yes, you're describing the change. Rewrite to explain reasoning instead.
+
+## Implementation
+
+**REQUIRED SUB-SKILL:** Use the elements-of-style:writing-clearly-and-concisely skill when drafting any change description. This is mandatory. Commit messages are prose for humans; apply the writing skill unconditionally to ensure clarity and conciseness.
+
+Before writing any change description:
+
+### 1. Identify What Changed
+
+Read the diff. List the concrete technical changes:
+- Modified this function
+- Added this parameter
+- Removed this check
+- Updated this constant
+
+### 2. Answer: Why Now?
+
+Why is this change happening RIGHT NOW, at this moment?
+- What problem does it solve?
+- What broke that forced this?
+- What new requirement triggered it?
+- What opportunity did it enable?
+
+**If you can't answer "why now," the change shouldn't exist.**
+
+### 3. Answer: Why This Approach?
+
+Why this solution instead of alternatives?
+- What approaches were considered?
+- Why were they rejected?
+- What constraint made this the only viable path?
+- What tradeoff did you accept?
+
+**If you don't know why you chose this over something else, you haven't thought it through.**
+
+### 4. Answer: Why Does It Matter?
+
+What's the impact?
+- How does this affect behavior?
+- What breaks without it?
+- What improves with it?
+- What downstream systems depend on this change?
+
+**If the impact is invisible, the message should explain it.**
+
+### 5. Write the Description
+
+Begin with a type prefix (fix:, feat:, docs:, ai:, refactor:, etc.) followed by concise reasoning:
+
+```
+fix: Cache TTL comparison unit mismatch
+
+Cache entries expired prematurely because TTL (seconds) and timestamp
+(milliseconds) were compared directly. Normalizing both to milliseconds
+on comparison avoids data migration and works immediately.
+```
+
+**Format:** Problem → Solution → Why this approach, in 2-3 sentences maximum.
+
+**First line:** Keep it short and specific (under 50 characters ideally, rarely exceeding 60). The first line should communicate the core problem or fix in one concise phrase. Longer first lines force readers to scan instead of grasp the change at a glance.
+
+**Don't include:**
+- Details visible in the diff (like the specific code change)
+- Multiple alternatives and their tradeoffs (just mention the constraint that eliminated them)
+- Implementation details (show in code, not message)
+- Justifications for why the approach is "good" (reason why it was chosen vs alternatives, not praise)
+
+**Multi-part changes:** If one commit includes multiple changes (error handling + retry logic + config), explain how they work together in one sentence. If changes are independent, use separate commits instead.
+
+## Common Mistakes
+
+| Mistake | Why It Fails | Fix |
+|---------|------------|-----|
+| "Added authentication" | Describes code, not reasoning | "Added OAuth to support permission boundaries for multi-tenant deployments" |
+| "Fixed bug in parser" | Says what, not why it broke | "Parser treated empty strings and null identically, causing..." |
+| "Refactored loop" | Describes the change | "Original loop couldn't handle batches with null entries. New approach..." |
+| "Updated config" | Vague about intent | "Increased timeout from 5s to 30s because slow networks were exceeding..." |
+| All detail visible in diff | Assumes reader sees what you see | Strip diff details, add context not visible in code |
+
+## Red Flags - When You're Being Too Wordy
+
+Stop and trim if you catch yourself:
+
+- Writing more than 3-4 sentences (you're over-explaining)
+- Including implementation details visible in the diff
+- Explaining what the code does rather than why the change exists
+- Justifying the approach as "good" instead of explaining the constraint
+- Enumerating multiple alternatives considered (mention only the blocker)
+- Adding foundational context readers can find in issue tickets
+
+**Max length:** 50-75 words for most changes. Under 30 words for simple fixes.
+
+## Red Flags - When You're Describing the Change Instead of Explaining It
+
+Stop and rewrite if you catch yourself saying:
+
+- "Added/removed/modified X" → Rephrase: WHY did you add/remove/modify it?
+- "This line does Y" → Rewrite: This change was needed because...
+- "Changed from A to B" → Rewrite: We switched from A to B because A had the problem of...
+- "The diff shows Z" → Rewrite: The reason for this change is...
+- "Obvious from the code" → That's exactly backwards - if it's obvious what changed, explain why it changed
+- "Cleanup/refactor" → Refactor from what? Into what? Why?
+- "I'll remember why I did this" → You won't. Write it down now.
+- "The diff is clear" → Clear about WHAT changed. Not clear about WHY.
+- "It's just cleanup" → Cleanup of what? Why now instead of before?
+
+All of these signal you're still describing the change instead of explaining the reasoning.
+
+## Example: Before and After
+
+### ❌ Describing the Change
+```
+Fix cache TTL comparison
+
+Modified cache.ts to normalize timestamp units before comparison.
+Added .toMilliseconds() call on both values before checking expiration.
+Prevents premature cache invalidation.
+```
+
+(Reader sees: "Added .toMilliseconds()". So what? Why was this needed?)
+
+### ✅ Explaining the Reasoning (Concise)
+```
+fix: Normalize TTL unit mismatch
+
+Cache entries expired because TTL (seconds) and timestamp (milliseconds)
+were compared directly. Normalize both to milliseconds on comparison
+to avoid data migration.
+```
+
+(Reader sees: Problem → Solution → Why this approach. Done in 2 sentences.)
+
+## Rationalizations to Resist
+
+When you catch yourself thinking one of these, STOP and add more context:
+
+| Rationalization | What It Blocks | Counter |
+|-----------------|----------------|---------|
+| "The diff is clear" | Doesn't explain why this approach over alternatives | The diff shows WHAT changed. Your message explains WHY. |
+| "I'll remember why I did this" | Lost context in 2 weeks when debugging | You won't remember. Write it down now. Future-you needs this. |
+| "It's just cleanup/refactor" | Doesn't explain the business motivation | Why now? Why this code? What problem does cleaning up solve? |
+| "This is obvious from the code" | Assumes readers have your context | Obvious to you ≠ obvious to reviewers missing background. |
+| "No one else works on this file" | Lowers standards temporarily | Lower standards compound. Document anyway. |
+| "The change is small/simple" | Size doesn't determine if reasoning matters | Small changes need context more, not less. |
+| "I need to explain everything" (over-explaining) | Makes readers skip the message | 2-3 sentences is enough. If more needed, it belongs in issue ticket. |
+| "The skill says explain alternatives" | Leads to verbose comparisons | Explain the constraint, not all options. Readers don't need a matrix. |
+
+All of these thoughts mean: Either add context explaining reasoning (first 6) or trim to essentials (last 2).
+
+## Key Questions
+
+Use these to validate your change description:
+
+1. **What problem does this solve?** - Must be answerable without looking at code
+2. **Why this approach?** - What alternatives were rejected and why?
+3. **What breaks without it?** - Business/product impact, not just technical
+4. **Why not done before?** - What changed to make this relevant now?
+5. **What does the reader need to understand?** - What context am I providing that isn't in the diff?
+
+If you skip any of these, add more context to your description.