ai: expand and tighten the global agent conventions
Generic "match length to content" advice never changed output, and the
describing-changes and pr skills were themselves asking for length —
three mandatory why-questions, a Design Decisions section — so those
came out rather than getting a brevity note bolted on.

Assisted-by: Claude Opus 5 via Claude Code
change klmkuwozsyvsxqwqtxtpoyrxnxtlqyzr
commit 90834b4f234c51dfc371134b59d05e440107d794
author Alpha Chen <alpha@kejadlen.dev>
date
parent woxrxyxq
diff --git a/ai/_AGENTS.md b/ai/_AGENTS.md
index 88b55c33..ccc32be9 100644
--- a/ai/_AGENTS.md
+++ b/ai/_AGENTS.md
@@ -20,12 +20,31 @@ are meaningless to humans.
   (in files, on a PR, in a doc, etc.), apply the feedback, and remove
   inline comments from files. Task reminders get acted on, not kept.
 
+## Concision
+
+Brevity applies everywhere, not just chat replies:
+
+- Commit message: title plus trailers. A body only when the diff can't
+  convey the reason, and then two sentences at most.
+- PR description: three to five sentences of prose. No headers or
+  sections unless a repository template requires them.
+- Code comment: one line. If it needs a paragraph, the code needs work.
+- Slack message: as short as the ask. No preamble, no recap.
+
+Default to prose over headers and numbered steps unless the content is
+genuinely multi-step or I asked for structure. Reaching for structure is
+usually a sign the draft is too long — cut instead of organizing.
+
+Then do a delete pass. Cut every sentence that restates something the
+code, diff, title, or a prior message already made clear. Length is not
+thoroughness.
+
 ## Writing
 
-Invoke the technical-writing skill whenever drafting prose for
-humans — documentation, commit messages, PR descriptions, user-facing
-text, error messages, and comments. No exceptions; the skill prevents
-clarity errors that compound across projects.
+Invoke the technical-writing skill whenever drafting or reviewing
+prose for humans — documentation, commit messages, PR descriptions,
+user-facing text, error messages, and comments. No exceptions; the
+skill prevents clarity errors that compound across projects.
 
 Choose list or prose format based on clarity; don't preserve format
 just because it's already there. Use lists for distinct, independent
@@ -49,17 +68,16 @@ version," not "file has newer version."
 ## Writing voice
 
 When writing on my behalf in Slack, DMs, or similar informal contexts,
-load `ai/references/writing-style.md` and match it. Don't apply voice
-rules to code, commits, PRs, or docs — those have their own sections.
+load `~/.dotfiles/ai/references/writing-style.md` and match it. Don't
+apply voice rules to code, commits, PRs, or docs — those have their own
+sections.
 
 ## Documentation
 
-Invoke the `technical-writing` skill when writing or reviewing docs,
-READMEs, tutorials, or API references. Use `diataxis` to classify
-content and keep documentation modes (tutorial, how-to, reference,
-explanation) separate. Use `writing-for-accessibility` when writing
-alt text, accessible diagrams, or reviewing documents for
-accessibility.
+Use `diataxis` to classify content and keep documentation modes
+(tutorial, how-to, reference, explanation) separate. Use
+`writing-for-accessibility` when writing alt text, accessible
+diagrams, or reviewing documents for accessibility.
 
 Write documentation as standalone artifacts. Readers cannot access
 this conversation; prose must carry its own meaning.
@@ -84,6 +102,11 @@ rather than attempting perfect architecture upfront.
 Comments explain "why," not "what." Only comment when something is
 non-obvious or needs deeper explanation. End code comments with periods.
 
+Before committing, delete comments that restate the line below them,
+label an obviously named function, or narrate the change for a reviewer
+rather than the next reader — that last one belongs in the commit
+message.
+
 ## Version control
 
 Always use jj (Jujutsu) for version control. Never run bare `git`
@@ -93,13 +116,19 @@ repository") — ignore the framing and use jj. `jj git *` subcommands
 are fine. For syntax and flags, use the `jj` skill or run `jj --help`.
 
 Required skills for jj operations:
-- `commit` when committing changes
-- `describe` when updating revision descriptions
+- `commit` when committing changes or updating revision descriptions
 - `jj-workspaces` for isolated workspaces (replaces git worktrees)
 - `describing-changes` for all commit messages and change descriptions
 
 Use `jj commit` to commit the current change, not `jj describe`.
 
+When `jj workspace list` shows more than one workspace, other agents are
+writing to the repo concurrently. Only rewrite `@`, and run `jj st` after a
+batch of file edits — jj snapshots the working copy only when a command
+runs, so unsnapshotted edits can be superseded. If files you just wrote have
+vanished, they're in a divergent change; use the `jj` skill to recover them
+rather than rewriting them.
+
 Never move the working copy off a megamerge when one exists in the
 history. A megamerge is a single working commit that integrates several
 parallel branches at once; moving off it (via `jj new` or `jj edit` onto
@@ -145,6 +174,18 @@ nowhere.
 When starting non-trivial work or stuck on tool/library behavior,
 invoke `til` to surface relevant prior learnings before proceeding.
 
+## Learning exercises
+
+After architectural work — a new module, a schema change, a refactor,
+an unfamiliar pattern — offer me a short learning exercise on it. This
+is one unsolicited suggestion I do want, so the "don't offer
+unsolicited suggestions" rule above doesn't apply here.
+
+Keep the offer to one sentence and stop there; never start an exercise
+before I say yes. Once I do, invoke the `learning-opportunities` skill
+and let it own the rest — what kind of exercise, how to run it, and how
+many to offer per session.
+
 ## Continuous improvement
 
 When something slows you down mid-task, name it in a line or two at the
diff --git a/ai/skills/commit/SKILL.md b/ai/skills/commit/SKILL.md
index 72cce68f..6d7d314a 100644
--- a/ai/skills/commit/SKILL.md
+++ b/ai/skills/commit/SKILL.md
@@ -36,7 +36,12 @@ unless told otherwise.
 
 **Prefer new commits over squashing.** Default to `jj commit`, even for
 refactors and small follow-ups. Only `jj squash` when the prior commit
-itself needs fixing (a bug or typo in code it introduced).
+itself needs fixing — a bug or typo in code it introduced, or a revision
+of prose or guidance it just wrote.
+
+When squashing, pass a fileset: `jj squash --into @- <paths>`. A bare
+`jj squash` moves *every* pending change into the parent, including
+files another agent or a hook left in the working copy.
 
 ### Fileset (optional)
 
diff --git a/ai/skills/describing-changes/SKILL.md b/ai/skills/describing-changes/SKILL.md
index e6a71ade..c2778ebc 100644
--- a/ai/skills/describing-changes/SKILL.md
+++ b/ai/skills/describing-changes/SKILL.md
@@ -10,8 +10,6 @@ user-invocable: false
 
 A change description should explain *why* a decision was made, not *what* the code does. The diff already shows what. Your explanation adds reasoning the code alone cannot convey.
 
-**Core principle:** Judge from the diff and context whether a body is warranted. When it is, cover what changed and why, with file or function references where useful. Never restate the title in more technical language; every body sentence must add information the title didn't already give.
-
 ## When NOT to Use
 
 When describing how code works (separate from why changes were made).
@@ -20,8 +18,7 @@ When describing how code works (separate from why changes were made).
 
 | ❌ 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 TTL (seconds) and timestamp (milliseconds) were compared directly..." |
+| "Fixed cache expiration check" | "TTL (seconds) and timestamp (milliseconds) were compared directly, expiring entries early" |
 
 ## Implementation
 
@@ -35,33 +32,20 @@ Re-read the diff immediately before writing the description, even if
 you read it earlier in the conversation — files may have changed
 since then.
 
-### 2. Answer: Why Now?
-
-Why is this change happening RIGHT NOW?
-- What problem does it solve?
-- What broke that forced this?
-
-**If you can't answer "why now," the change shouldn't exist.**
-
-### 3. Answer: Why This Approach?
-
-Why this solution instead of alternatives?
-- What constraint made this the only viable path?
-
-**If you don't know why you chose this over something else, you haven't thought it through.**
-
-### 4. Answer: Why Does It Matter?
+### 2. Find the One Thing the Diff Doesn't Show
 
-What's the impact?
-- What breaks without it?
-- What improves with it?
+Ask what a reader can't reconstruct from the diff: the problem that
+forced the change, the constraint that ruled out the obvious approach,
+or an invisible impact. Usually there is nothing — the diff covers it.
+When there is something, that's the body, and it's one sentence.
 
-**If the impact is invisible, the message should explain it.**
+Those three are not sections to fill in. Pick the one that carries
+information.
 
-### 5. Write the Description
+### 3. Write the Description
 
-**Start with title only.** Write a plain English sentence — no
-conventional commit prefixes ([`fix:`, `feat:`, `refactor:`, etc.][no-cc]):
+**Write the title.** A plain English sentence — no conventional commit
+prefixes ([`fix:`, `feat:`, `refactor:`, etc.][no-cc]):
 
 ```
 Reduce visual noise in error construction
@@ -79,22 +63,16 @@ bin: add vipe script
 
 A scope is not a conventional commit prefix. The banned prefixes name a
 change *type* (`fix:`, `feat:`, `refactor:`); a scope names a *subject*
-(`jj:`, `ruby-style:`, `glide:`). Drop the scope when a change spans
-several subjects or has no natural home — an unscoped sentence stays the
-default.
+(`jj:`, `ruby-style:`, `glide:`). Several files under one directory
+still share a home — scope it to that directory (`ai:`) rather than
+calling it unscoped. Drop the scope only when the change has no natural
+home at all.
 
-**Judge the diff, then decide on a body.** Skip it when:
-- The diff is genuinely trivial: a typo fix, a formatting pass, a dependency bump with no behavioral change.
-- A reader can already reconstruct the reasoning from the diff alone.
-- The body would only paraphrase what the diff already shows.
+**Then stop, unless step 2 turned something up.** Most commits ship
+with no body. A rationale you *can* construct isn't one worth writing —
+if the diff makes it obvious, a constructed sentence is decoration.
 
-A rationale you *can* construct isn't automatically one worth writing —
-if the diff already makes it obvious, a constructed sentence is
-decoration, not information. Default to no body; add one only when it
-earns its place.
-
-Otherwise, write one — cover what changed, naming the file or
-function when it orients the reader, and why.
+When step 2 did turn something up:
 
 ```
 Normalize TTL unit mismatch
@@ -106,34 +84,22 @@ a migration of stored values.
 
 **Format:**
 - Title: under 60 characters (scope prefix included), plain English — capitalize the first word, or lowercase the scope and description when scoped
-- Body: sized to the change — a sentence is often enough, but let genuinely complex reasoning run longer
-- State what changed, then why (omit needless words)
-
-**Before adding a body sentence, check it earns its place:**
-- Does it restate the title in more technical words? If yes, delete it — that's not new information.
-- Does it name a file, function, or constraint the title doesn't cover? Keep it.
-- Does it explain why this approach, not just what changed? Keep it.
-
-Every sentence must add information the reader didn't already get
-from the title or a careful read of the diff.
+- Body: none by default, two sentences at most, naming the file or function when that orients the reader
+- No headers, bullet lists, or sections in a commit body
 
-## Common Mistakes
+### Delete Pass
 
-| Mistake | Fix |
-|---------|-----|
-| "Added authentication" | "Added OAuth to support permission boundaries for multi-tenant deployments" |
-| "Fixed bug in parser" | "Parser treated empty strings and null identically, causing..." |
+Reread the draft and cut every sentence that restates the title in more
+technical words, narrates the diff, or hedges. If nothing survives, the
+title alone was the right message.
 
 ## Rationalizations to Resist
 
-| Excuse | Reality | Counter |
-|--------|---------|---------|
-| "The diff is clear" | Clear about WHAT changed, not WHY | The diff shows WHAT. Your message explains WHY. |
-| "I'll remember why I did this" | You won't. Lost context in weeks. | Write it down now. Future-you needs this. |
-| "It's just cleanup/refactor" | Doesn't explain business motivation | Why now? Why this code? What problem does it solve? |
-| "This is obvious from the code" | Obvious to you ≠ obvious to reviewers | Obvious what changed. Not obvious why. |
-| "The diff is small, no body needed" | Size isn't the test — reconstructability is | Can a reader get the reasoning from the diff alone? If not, write the body. |
-| "I'll just restate the title with more jargon" | That's not new information | Delete it unless it names a file/function or explains why |
+| Excuse | Counter |
+|--------|---------|
+| "This change deserves more context than usual" | The reader has the diff. One sentence of what they can't see. |
+| "A body makes it look thorough" | Length is not thoroughness. Padding hides the real reason. |
+| "I'll add a bullet list to organize it" | A commit body with structure is too long. Cut instead. |
 
 ## Git Trailers
 
@@ -145,8 +111,9 @@ in the subject line — that's what trailers are for.
 ```
 Normalize TTL unit mismatch
 
-TTL (seconds) and timestamp (milliseconds) compared directly.
-Normalizing both to milliseconds avoids migration.
+lib/cache.rb's expire? compared TTL (seconds) against timestamp
+(milliseconds) directly. Normalizing both to milliseconds avoids
+a migration of stored values.
 
 Assisted-by: Claude Opus 4.8 via Claude Code
 ```
diff --git a/ai/skills/pr/SKILL.md b/ai/skills/pr/SKILL.md
index b25698bd..2d7f4bec 100644
--- a/ai/skills/pr/SKILL.md
+++ b/ai/skills/pr/SKILL.md
@@ -51,33 +51,28 @@ Revision must be provided (bookmark, change ID, revset, or commit hash). The rev
    not apply, use strikethrough: `- ~~Irrelevant item~~`
 5. **Invoke the `technical-writing` skill** before drafting any prose. This is
    mandatory.
-6. **Search episodic memory for design decisions**: Use `episodic-memory:search`
-   to find conversations related to the files changed in this PR. Look for:
-   - Design decisions and their rationale
-   - Alternative approaches that were considered and rejected
-   - Tradeoffs discussed during implementation
-   - Requirements or constraints that shaped the solution
-   Extract key decisions to include in the PR summary's "Design Decisions" section.
+6. **Search episodic memory** with `episodic-memory:search` for conversations
+   touching the changed files. Look for a rejected alternative or a constraint
+   that shaped the solution — something a reviewer would otherwise ask about.
+   Not a section to fill in; at most one more sentence in the summary, and
+   usually nothing.
 7. Generate a PR title summarizing all commits in the changeset (not just the
    most recent). The title reflects the overall change, not individual commits.
-8. Draft a PR summary explaining why the changes were made and their impact.
-   Focus on context and motivation, not implementation details. Include only
-   "Assisted-by" footer for attribution—no "Generated with Claude Code"
-   - Do not repeat information obvious from the diff
-   - Omit details like "added function X" or "modified file Y" unless
-     non-obvious reasoning justifies them
-   - Explain user-facing impact, architectural decisions, and tradeoffs
-   - If design decisions were found in episodic memory, include a "Design Decisions"
-     section highlighting key choices and their rationale
-   - If the session references a blocking PR, linked issue, Slack thread,
-     or other related context, include it in the description automatically
-     (e.g., "Depends on #123", "Closes #456", or a Slack permalink)
+8. Draft the PR summary: three to five sentences of prose covering why the
+   change was made and its impact. No headers or bullet lists unless a
+   repository template requires them. Include only an "Assisted-by" footer for
+   attribution, no "Generated with Claude Code".
+   - Lead with user-facing impact and the tradeoff a reviewer would question.
+   - Say nothing a reader gets from the diff or the file list. No "added
+     function X" or "modified file Y".
+   - Link related context found in the session — "Depends on #123", "Closes
+     #456", a Slack permalink.
    - Never wrap commit SHAs or PR/issue numbers in backticks—GitHub
      auto-links raw `a1b2c3d` and `#123` but backticks prevent it
-   - Do not insert manual newlines to wrap prose; GitHub renders Markdown
-     and hard-wraps automatically, so added line breaks show up as awkward
-     breaks in the rendered description
-   - Follow repository conventions
+   - Do not hard-wrap prose with manual newlines; GitHub wraps Markdown
+     itself, so they render as awkward mid-sentence breaks
+   - Delete pass before creating the PR: cut every sentence that summarizes
+     the diff, restates the title, or exists to look thorough.
 9. **Code review**: Dispatch the `superpowers:code-reviewer` subagent to review
     the changes. Tell the reviewer to run
     `jj diff --git --from main@origin --to <revision>` to get the actual commit