| name | commits:create |
| description | Analyze staged changes and create conventional commits with intelligent grouping. Use when creating commits, or when invoked from other skills. |
| argument-hint | [optional commit context] [--autopilot] |
| allowed-tools | ["Bash(git *)","Bash(gh *)","Read","AskUserQuestion","Skill(autopilot:preflight-check)","Skill(autopilot:pr-update)"] |
Create Commit
Analyze changes and create git commits with conventional commit messages. Supports intelligent grouping of changes into multiple atomic commits for better review and changelog.
When to Use
- When changes need to be committed
- When invoked from
/autopilot:commits-restructure after soft reset
- When invoked from other skills that need to create commits
Input
Arguments: $ARGUMENTS
Expected form:
- (no arguments) — auto-analyze staged changes
"<context>" — free-form context that helps generate a better commit message (e.g., "add auth feature")
--autopilot — non-interactive mode used by /autopilot:run. Skips commit-strategy, commit-message, and PR-update prompts and commits directly using the auto-generated messages.
Input resolution
Arguments are optional. When $ARGUMENTS is empty:
- Commit context — skip; rely on the diff itself (
git diff --staged) plus recent conversation history (skill analyses, user instructions) to generate the message. Do NOT prompt.
--autopilot — $ARGUMENTS only. Never inferred. Default: false (interactive mode). Strip from $ARGUMENTS before parsing the remainder as commit context.
- Repository conventions — read
CONTRIBUTING.md directly from the repository root.
- Existing PR — detect via
gh pr view --json number,url 2>/dev/null at Phase 5. No user prompt needed.
AskUserQuestion Contract (MANDATORY)
Autopilot bypass: When autopilotMode is true (from Phase 1), this entire contract is moot — every AskUserQuestion call in Phases 3, 4, and 5 is skipped. Generate the commit message(s), commit directly, and exit without prompting.
Every AskUserQuestion call that presents content for review (commit messages) MUST follow these exact rules. Simple choice dialogs (Phase 3 commit strategy, Phase 5 PR update offer) are exempt from the preview requirement.
question is FIXED TEXT — use the EXACT string specified in each phase. NEVER add commit messages, file names, diffs, metadata, or any other content to the question field.
header is FIXED TEXT — use the EXACT string specified in each phase.
preview is MANDATORY — every option MUST include a preview field. The commit message goes ONLY in preview. NEVER put content in question, label, or description.
label values are EXACT — use the exact text specified (e.g., "Commit", "Edit", "Cancel"). No abbreviations, no paraphrasing, no creative alternatives.
description values are EXACT — use the exact text specified. No rewording.
- ALL options are REQUIRED — include every option listed in the phase. NEVER omit "Cancel".
- Same
preview on all options — the user chooses an action, not content. All options show identical preview text.
- SUBSTITUTE every placeholder in
preview — templates below use <commit message> as a placeholder. Before invoking AskUserQuestion, replace <commit message> with the full commit message string (title + body, literal \n escape sequences for line breaks). NEVER pass the literal string <commit message>, nor the shorthand "...", "<same>", or any placeholder. Every option's preview must contain the fully resolved commit message string.
WRONG — content in question field
AskUserQuestion({
question: "feat(auth): add jwt refresh endpoint\n\n- Added /auth/refresh endpoint\n- Added 7-day expiry\n\nProceed?",
header: "Commit",
options: [
{ label: "Yes", description: "Commit" },
{ label: "Edit", description: "Change message" }
]
})
WRONG — content in label
AskUserQuestion({
question: "Review the commit message and choose an action.",
header: "Commit",
options: [
{ label: "feat(auth): add jwt refresh endpoint", description: "Proceed with this commit" },
{ label: "Edit message", description: "Modify" }
]
})
WRONG — no preview, missing Cancel, content in description
AskUserQuestion({
question: "Review the commit message and choose an action.",
header: "Commit",
options: [
{ label: "Commit", description: "feat(auth): add jwt refresh endpoint - Added /auth/refresh" },
{ label: "Edit", description: "Modify the commit message" }
]
})
CORRECT
AskUserQuestion({
question: "Review the commit message and choose an action.",
header: "Commit",
options: [
{ label: "Commit", description: "Proceed with this commit message", preview: "feat(auth): add jwt refresh endpoint\n\n- Added /auth/refresh endpoint\n- Added 7-day expiry" },
{ label: "Edit", description: "Modify the commit message", preview: "feat(auth): add jwt refresh endpoint\n\n- Added /auth/refresh endpoint\n- Added 7-day expiry" },
{ label: "Cancel", description: "Abort commit creation", preview: "feat(auth): add jwt refresh endpoint\n\n- Added /auth/refresh endpoint\n- Added 7-day expiry" }
]
})
Phase 0: Preflight Check
Invoke Skill(autopilot:preflight-check) with mode: commits from this conversation context. The skill verifies the current branch is appropriate for committing and warns if you are on main. If it outputs a "cancelled" message, stop immediately — do not proceed to Phase 1.
Phase 1: Check for Changes
- Parse
$ARGUMENTS: if it contains --autopilot, strip the flag and set autopilotMode = true. Otherwise autopilotMode = false. The remainder (if any) is the commit context.
- Run
git status to see current state
- If there are unstaged changes:
- Show the list of modified/untracked files
- Ask user which files to stage (specific files or all)
- Stage the selected files with
git add
- If no staged changes after this step, abort with message
Phase 2: Analyze and Categorize Changes
Invoke the analyze-staged-changes sub-agent to categorize staged files, assess changeset size, and recommend a commit strategy:
Use the Agent tool with:
- `subagent_type`: "autopilot:analyze-staged-changes"
- `prompt`: "Analyze staged changes in the current repository."
- `description`: "Analyze staged changes"
After the agent completes, store the structured results (categories, file lists, strategy recommendation, recent commit style).
Phase 3: Choose Commit Strategy
Autopilot bypass: If autopilotMode is true, do NOT call AskUserQuestion. Use single commit flow when singleCommitRecommended: true; otherwise use grouped commit flow. Proceed to Phase 4.
Use the agent's analysis to decide the commit flow:
-
If agent recommends singleCommitRecommended: true: single commit flow (Phase 4)
-
If agent recommends singleCommitRecommended: false: the changeset is large enough to consider splitting. Evaluate whether the changes represent genuinely distinct areas. If a single coherent commit message can describe all changes, use single commit flow. Otherwise, ask the user:
Formatting Note: Do not use markdown formatting (bold, italic, headers) in the question parameter — it renders as raw text. Use plain text with line breaks and simple labels instead.
Tool parameters:
question: "How would you like to commit these changes?"
header: "Commit strategy"
options: [
{ label: "Single commit (Recommended)", description: "One commit with a comprehensive message" },
{ label: "Separate commits", description: "Create N atomic commits by category" }
]
multiSelect: false
-
If user chooses "Separate commits": Continue to Phase 4 with grouped flow
-
If user chooses "Single commit": Continue to Phase 4 with single commit flow
Phase 4: Execute Commits
Single Commit Flow
- Run
git diff --staged to see what will be committed
- Read the diff carefully and identify:
- The specific technical change (what was added, removed, or replaced)
- The concrete modifications made (what files, functions, values, or behaviors changed)
- Generate commit message following the format below — the title must name the specific thing that changed, and the body must list the concrete modifications
- WHAT-not-WHY validation: Check the generated title against the WHY signal words and vague signal words listed in the WHAT-not-WHY Rule section below. If the title contains any of those words followed by abstract goals (not technical specifics), or contains the words "review", "feedback", "comments", or "suggestions", regenerate the title using only concrete technical details from the diff. Repeat up to 3 times. If the title still fails, present it to the user with a note that it may need manual rewording.
- Validate the composed candidate message with Commit Message Validation — the full inline floor (length,
subject-case, and the other checkable commitlint rules) plus commitlint when installed. On any violation, regenerate and re-validate (≤3 attempts); do not present or commit a message that still fails.
Autopilot bypass: If autopilotMode is true, skip steps 6–9 below. Run git commit -m "<message>" directly with the generated message and continue to Phase 5.
-
Present using AskUserQuestion tool with preview:
Preview content rules:
- The
preview MUST contain ONLY the commit message (title + body). DO NOT include file lists, diff content, or any other metadata in the preview
Tool call structure: See AskUserQuestion Contract above. All rules are mandatory.
Tool parameters:
question: "Review the commit message and choose an action."
header: "Commit"
options: [
{ label: "Commit", description: "Proceed with this commit message", preview: "" },
{ label: "Edit", description: "Modify the commit message", preview: "" },
{ label: "Cancel", description: "Abort commit creation", preview: "" }
]
multiSelect: false
-
If "Edit" selected, ask for changes and regenerate, then re-run Commit Message Validation on the edited message and re-present — a hand-typed subject is validated too, never committed unchecked
-
If "Cancel" selected, abort with "Commit cancelled."
-
Only proceed with git commit after "Commit" selected
-
When executing git commit, run git commit -m "<message>"
Grouped Commit Flow
Commit Ordering Principle
Every commit in a PR must leave the branch in a stable state — CI passes, lint rules are satisfied, tests pass for the code present at that point. Undocumented or untested code is acceptable; broken CI/lint is not.
Default order: ci → chore/build → feat/fix/refactor → test → docs
ci first — CI/CD pipeline changes establish the rules
chore/build — configuration and dependencies
feat/fix/refactor — main implementation satisfies those rules
test — tests verify implementation
docs last — informational, never breaks stability
This is the default for the common case. Reason about the specific changes and deviate when needed. For example, if implementation changes are prerequisites for CI changes to pass, use feat → ci → ... instead. The goal is stability at every checkout, not rigid adherence to a fixed sequence.
Process categories in this order: ci → chore/build → feat/fix/refactor → test → docs
Step 1: Analyze all categories upfront
For each category that has files:
git reset HEAD (unstage all)
git add <category files>
git diff --staged — read the diff and identify what specifically changed (files, functions, values, behaviors)
- Generate commit message for this category
- Validate this category's composed candidate message with Commit Message Validation — the full inline floor plus commitlint when installed. On any violation, regenerate and re-validate (≤3 attempts) before moving to the next category; a message that still fails must not be presented or committed.
After analyzing all categories, git reset HEAD to unstage everything.
Step 2: Present all commits in one dialog
Autopilot bypass: If autopilotMode is true, skip this step entirely. Skip to Step 3 and execute each commit directly with its generated message.
Use a single AskUserQuestion with multiple questions (one per commit), each with preview.
Tool call structure: See AskUserQuestion Contract above. All rules are mandatory.
Tool parameters:
questions: [
{
question: "Commit 1/N: \nReview the commit message and choose an action.",
header: "Commit 1/N",
options: [
{ label: "Commit", description: "Proceed with this commit message", preview: "" },
{ label: "Edit", description: "Modify the commit message", preview: "" },
{ label: "Cancel", description: "Abort all commits", preview: "" }
],
multiSelect: false
},
{
question: "Commit 2/N: \nReview the commit message and choose an action.",
header: "Commit 2/N",
options: [...same structure with preview...],
multiSelect: false
},
...one question per category
]
All options in each question use the same preview content (commit message only for that category). DO NOT include file lists in the preview.
If "Cancel" is selected for any commit, abort the entire grouped commit operation with "Commits cancelled." — do not execute any of the commits.
If there are 5 categories (exceeds the 4-question limit): present the first 4 in one dialog, then present the 5th in a follow-up single-question dialog.
Step 3: Process responses and execute commits
-
For each commit where "Edit" was selected: ask a follow-up single AskUserQuestion for that commit's new message (question with the original message, header "Edit N/M", same options), then re-run Commit Message Validation on the edited message before re-presenting. Repeat until "Commit" is selected. If "Cancel" is selected, abort all commits with "Commits cancelled." A hand-typed message must pass validation before it reaches the commit loop in step 2.
-
Execute all commits sequentially in category order:
git add <category files>
git commit -m "<message>"
After all commits:
✓ All N commits created successfully.
Commit Message Format
<type>[optional scope]: <description>
- <what specifically changed>
- <what specifically changed>
The body is required for feat, fix, and refactor commits. It may be omitted for docs, test, style, chore, ci, build, perf, and revert commits where the title alone is fully descriptive.
Types
feat - New feature
fix - Bug fix
docs - Documentation changes
style - Code style (formatting, semicolons)
refactor - Code restructure (no behavior change)
perf - Performance improvements
test - Tests
build - Build system or dependencies
ci - CI/CD configuration
chore - Maintenance (tooling, deps)
revert - Reverting a previous commit
Rules
- Title: lowercase, no period, imperative mood. The subject (text after
type(scope): ) MUST be all lowercase — camelCase identifiers are not allowed (subject-case) — MUST NOT exceed 50 characters, and the whole header line (type(scope): subject) MUST NOT exceed 100 characters. Enforced by commitlint (subject-case / subject-max-length / header-max-length) and CI, and by the skill itself via Commit Message Validation before every commit
- Title must name the specific thing that changed, not just the action
- Body required for
feat, fix, and refactor. Body bullet points list concrete modifications
- Never use GitHub issue numbers or PR references in commit messages (issue linking happens on the PR via magic words)
- Never include AI agent
Co-authored-by trailers (Claude, ChatGPT, Copilot, Codex). Disable co-authorship in your AI tool settings.
- Every commit must leave the branch in a stable state — CI passes, lint passes, tests pass for the code present at that point
WHAT-not-WHY Rule (MANDATORY)
Both the title and body MUST describe WHAT changed, NEVER WHY it changed. Context from calling skills (e.g., "fixes for PR review comments") must NOT influence the title — the title must describe what changed in the code.
Title: Name the specific technical change. Do NOT state the motivation, goal, or intent behind the change.
| WHY-focused (WRONG) | WHAT-focused (CORRECT) |
|---|
fix: close coverage gaps | fix(auth): add null-check and expiry validation |
fix: address review feedback | fix(parser): replace bcrypt with argon2 for hashing |
refactor: ensure compliance with rules | refactor(lint): change nesting depth threshold to 2 |
feat: improve error handling | feat(api): add retry with exponential backoff |
fix: cover edge cases | fix(validator): handle null and empty-string inputs |
refactor: address code quality concerns | refactor(db): extract connection pool into module |
Body: List the concrete modifications. Do NOT explain reasoning or reference rules.
| WHY-focused body bullet (WRONG) | WHAT-focused body bullet (CORRECT) |
|---|
- CLAUDE.md enforces max-depth of 2 | - Change nesting depth threshold from >5 to >2 |
- Tests were missing for auth edge cases | - Add tests for expired token and null user inputs |
- Review requested switching to argon2 | - Replace bcrypt with argon2 in hashPassword() |
- Needed to close coverage gap in validation | - Add boundary checks for negative and zero values |
WHY signal words to avoid in titles: "close", "address", "ensure", "improve", "cover", "resolve", "satisfy", "comply", "meet" (when followed by abstract goals rather than technical specifics — e.g., "handle edge cases" is WHY, "handle null input in parseToken" is WHAT)
Anti-patterns
The title must "contain the answer" — a reader should understand what changed without opening the diff.
| Bad (vague) | Good (specific) | Why |
|---|
fix: review updates | fix(auth): replace bcrypt with argon2 for hashing | Names the actual replacement |
fix: resolve issue | fix(api): return 404 instead of 500 for missing users | States the concrete behavior change |
feat: add new feature | feat(billing): add monthly invoice PDF export | Names the specific feature |
refactor: clean up code | refactor(db): extract query builder from repository | Names what was extracted |
chore: update dependencies | chore(deps): upgrade zod from 3.21 to 3.23 | Names the package and versions |
fix: close coverage gaps | fix(auth): add null-check and expiry validation | Names what was actually added |
refactor: address feedback | refactor(parser): extract tokenizer into separate file | Names the structural change |
Vague signal words to avoid in titles: "update", "fix stuff", "changes", "improvements", "tweaks", "adjustments", "various", "some", "misc", "review updates", "address feedback", "resolve issue", "close gaps", "cover edge cases", "ensure compliance", "improve handling", "address concerns", "satisfy requirements"
Commit Message Validation
Run this on every fully-composed candidate message (title + optional body) BEFORE git commit — in both the Single and Grouped flows and in autopilot mode. It is the gate that guarantees a valid commit even when the husky commit-msg hook is absent (a fresh worktree that has not run bun install has no active hook, so nothing else catches a bad message). It mirrors the rules in commitlint.config.mjs — that file is the source of truth; keep this list in sync with it. Run the shell snippets under LC_ALL=C.UTF-8 so length and case folding are stable.
Extract type, subject (the text after type(scope): ), and body from the candidate message.
Inline floor (ALWAYS runs — no dependencies; the real gate in fresh worktrees). Each check maps to a commitlint rule; the first miss is a violation:
subject-max-length / header-max-length — subject ≤ 50 (printf '%s' "<subject>" | wc -m), full header ≤ 100 (printf '%s' "<title>" | wc -m).
subject-case (lower-case) — the subject must equal its lowercased form: printf '%s' "<subject>" | tr '[:upper:]' '[:lower:]' must be byte-identical to <subject>. Any difference means an uppercase letter is present (e.g. a camelCase identifier like localeForEmail). This is an ASCII approximation; when commitlint runs below it is authoritative for non-ASCII.
subject-full-stop — the subject must not end with ..
no-issue-id-in-subject — the subject must not match [A-Za-z]+-[0-9]+.
body-required-for-types — if type is feat, fix, or refactor, body must be non-empty.
no-ai-coauthored-by — the raw message must not contain a Co-authored-by: trailer naming Claude, ChatGPT, Copilot, Codex, Devin, or Cursor.
type-enum / type-case — type must be one of the lowercase types listed under Types.
Full commitlint (best-effort — only when installed). If the binary is present ([ -x node_modules/.bin/commitlint ]), also run the same command the husky hook uses:
printf '%s\n' "<full message>" | bunx --no -- commitlint
Treat any reported problem as a violation. Gating on the binary's existence avoids an accidental network install in a bare worktree. If it is absent, skip this step silently — the inline floor already ran and is the gate.
On any violation — regenerate the message to fix the specific rule, then re-run the whole validation. Do NOT string-lowercase a subject to satisfy subject-case (that mangles identifiers, e.g. localeForEmail → localeforemail); instead rephrase the subject to avoid the mixed-case token (hyphenate or use plain words), or name the exact identifier in the backticked body — then re-confirm the subject still describes the change. Shorten to fix length. Retry up to 3 times.
Success = the whole inline floor passes (plus commitlint when it ran), not just one rule. NEVER git commit a message that still fails. After 3 failed attempts:
- Interactive mode — present the message and the failing rule(s) and ask the user to reword.
- Autopilot mode — abort loudly with the failing rule(s); leave the index/staged state untouched and create no partial commit.
Phase 5: Offer PR Update
Autopilot bypass: If autopilotMode is true, skip this entire phase — the calling skill (/autopilot:run) creates or updates the PR itself in its next step.
After all commits are created successfully:
-
Check if a PR exists for the current branch: gh pr view --json number,url 2>/dev/null
-
If the command fails (no PR), skip silently — do not show any message
-
If a PR exists, ask using AskUserQuestion tool:
Formatting Note: Do not use markdown formatting (bold, italic, headers) in the question parameter — it renders as raw text. Use plain text with line breaks and simple labels instead.
Tool parameters:
-
question: "A pull request exists for this branch: #. Would you like to update it to reflect the new commits?"
-
header: "Update PR"
-
options: [
{ label: "Update PR", description: "Refresh PR title and description from all commits" },
{ label: "Skip", description: "Keep the PR as-is" }
]
-
multiSelect: false
-
If "Update PR" selected: invoke Skill(autopilot:pr-update)
-
If "Skip" selected: finish normally
Examples
Single Commit
feat(auth): add jwt token refresh endpoint
- Added /auth/refresh endpoint that issues new access token from refresh token
- Added 7-day expiry validation for refresh tokens
- Returns 401 with "refresh_expired" code when token is past expiry
fix(api): return 404 instead of 500 for missing user lookup
- Changed UserService.findById to return null instead of throwing
- Added explicit 404 response in GET /users/:id handler
docs: add environment variables reference to readme
Grouped Commits (Quiz Mode)
Analyzing staged changes...
Detected 3 categories:
- impl: 3 files (auth.ts, auth.types.ts, index.ts)
- test: 1 file (auth.test.ts)
- docs: 2 files (docs/auth.md, docs/api-reference.md)
How would you like to commit these changes?
User selects "Separate commits" via AskUserQuestion tool.
All commits are analyzed upfront, then presented in a single dialog with previews:
AskUserQuestion with:
questions: [
{
question: "Commit 1/3: impl\nReview the commit message and choose an action.",
header: "Commit 1/3",
options: [
{ label: "Commit", description: "Proceed with this commit message", preview: "feat(auth): implement jwt validation\n\n- Added token validation logic\n- Added refresh token support" },
{ label: "Edit", description: "Modify the commit message", preview: "feat(auth): implement jwt validation\n\n- Added token validation logic\n- Added refresh token support" },
{ label: "Cancel", description: "Abort all commits", preview: "feat(auth): implement jwt validation\n\n- Added token validation logic\n- Added refresh token support" }
],
multiSelect: false
},
{
question: "Commit 2/3: test\nReview the commit message and choose an action.",
header: "Commit 2/3",
options: [
{ label: "Commit", description: "Proceed with this commit message", preview: "test(auth): add jwt validation tests\n\n- Added token expiry edge case tests\n- Added refresh flow integration test" },
{ label: "Edit", description: "Modify the commit message", preview: "test(auth): add jwt validation tests\n\n- Added token expiry edge case tests\n- Added refresh flow integration test" },
{ label: "Cancel", description: "Abort all commits", preview: "test(auth): add jwt validation tests\n\n- Added token expiry edge case tests\n- Added refresh flow integration test" }
],
multiSelect: false
},
{
question: "Commit 3/3: docs\nReview the commit message and choose an action.",
header: "Commit 3/3",
options: [
{ label: "Commit", description: "Proceed with this commit message", preview: "docs: update authentication documentation" },
{ label: "Edit", description: "Modify the commit message", preview: "docs: update authentication documentation" },
{ label: "Cancel", description: "Abort all commits", preview: "docs: update authentication documentation" }
],
multiSelect: false
}
]
User selects "Commit" for all three.
✓ Created commit: feat(auth): implement jwt validation
✓ Created commit: test(auth): add jwt validation tests
✓ Created commit: docs: update authentication documentation
All 3 commits created successfully.
If user selects "Edit" for commit 2/3, a follow-up dialog appears only for that commit:
AskUserQuestion with:
question: "Review the updated commit message and choose an action."
header: "Edit 2/3"
options: [
{ label: "Commit", description: "Proceed with this commit message", preview: "test(auth): add jwt validation tests\n\n- Added token expiry edge case tests\n- Added refresh flow integration test" },
{ label: "Edit", description: "Modify the commit message", preview: "test(auth): add jwt validation tests\n\n- Added token expiry edge case tests\n- Added refresh flow integration test" },
{ label: "Cancel", description: "Abort all commits", preview: "test(auth): add jwt validation tests\n\n- Added token expiry edge case tests\n- Added refresh flow integration test" }
]
Single Category (No Grouping Offered)
Analyzing staged changes...
All changes are in 1 category (impl).
AskUserQuestion with:
question: "Review the commit message and choose an action."
header: "Commit"
options: [
{ label: "Commit", description: "Proceed with this commit message", preview: "feat(auth): implement jwt validation\n\n- Added token validation logic\n- Added refresh token support" },
{ label: "Edit", description: "Modify the commit message", preview: "feat(auth): implement jwt validation\n\n- Added token validation logic\n- Added refresh token support" },
{ label: "Cancel", description: "Abort commit creation", preview: "feat(auth): implement jwt validation\n\n- Added token validation logic\n- Added refresh token support" }
]
User selects "Commit".
✓ Created commit: feat(auth): implement jwt validation
With PR Update Prompt
After committing on a branch with an existing PR:
✓ Created commit: feat(auth): add password reset flow
AskUserQuestion with:
question: "A pull request exists for this branch: #15. Would you like to update it to reflect the new commits?"
header: "Update PR"
options: [
{ label: "Update PR", description: "Refresh PR title and description from all commits" },
{ label: "Skip", description: "Keep the PR as-is" }
]
User selects "Update PR". Invokes Skill(autopilot:pr-update).
✓ Updated PR #15: https://github.com/org/repo/pull/15
Reference formatting & readability
These rules govern references — when you point the reader at a real file, standard, section, commit, or issue. (A token named only as an example, with no real target, is a code specimen in backticks, like any code identifier.) Every reference must resolve: render it as a real link whose target exists, and prefer the most stable link form so it does not rot. Render the same kind of reference the same way everywhere:
- Code specimens — backticks, e.g.
buildReviewComments, reviewOutput.ts. A backticked token names a thing as an example; it is not a reference and carries no link.
- Files, docs, skills, agents, and actions you point the reader at — link them, e.g.
[release field spec](<repo-blob-url>/docs/06-release-field.md). Use a repo-relative path in repository files and the absolute <repo-blob-url> form in generated output posted outside the repo (PR/issue bodies, review comments, release notes), where relative paths do not resolve. Any prose mention of a file or path that exists in the repo is such a reference — link it so it resolves on the default branch at writing time; a path that does not exist yet (a file the text proposes to create) or one shown inside a command or fenced block is a code specimen, not a reference.
- Standards and conventions — ALWAYS link the versioned RFC by its stable ID, e.g.
[RFC-0001](<repo-blob-url>/rfc/0001-reference-formatting.md); an Accepted RFC is immutable except through an explicit version bump, so the link never rots.
- External resources — articles, posts, vendor docs, and web standards or specs you cite — link them inline as
[title](url) to the canonical source, taking the title from the source (or the site name). Use only a URL present in your input or context — never produce one from memory; a source with no known URL stays plain prose. When several sources back one document, they may be gathered into a short references list.
- Sections — link the heading by its anchor. Same document: a bare
#anchor, e.g. [Phase 6](#phase-6-reply-to-review-threads). Another document: path#anchor — a repo-relative path in repository files, the absolute <repo-blob-url>/path#anchor form in generated output. A GitHub anchor is the heading lower-cased, spaces turned to hyphens, punctuation dropped.
- Commit SHAs — ALWAYS a link, e.g.
[0328a61](<repo-commit-url>/0328a61); a commit is immutable. If you cannot build the URL, leave the bare SHA un-backticked.
- Issue / PR references — leave the bare number (GitHub auto-links it) or write a full link. A tracker ID GitHub does not auto-link (e.g. Linear
ENG-123) is dead text when bare: in prose, ALWAYS render it as a markdown link, e.g. [ENG-123](https://linear.app/<workspace>/issue/ENG-123) — a slug-less issue URL resolves. On a magic-word line (Closes/Fixes/Related to in a PR body's **Issues:** section) use plain forms only: bare #N for GitHub, the plain issue URL for other trackers — never a markdown-bracket link, which breaks the close-parsers.
Backticks suppress GitHub autolinking: a commit SHA or issue/PR number inside a code span renders as dead text — that is why a backticked SHA was un-clickable in a prior review. Never wrap a SHA or issue/PR number in backticks; link it, or leave it bare so GitHub auto-links it.
Write the most helpful, readable output you can: plain, direct prose; every reference resolvable; explain the "why", not the obvious "what".