File Naming & Path Standards
Prerequisites: @smith-principles/SKILL.md
CRITICAL: Naming Separators
Underscore (_): Multi-word phrases as single concept
user_authentication,query_processor,semantic_search- Think: "What kind of X?" → underscore
Hyphen (-): Hierarchical/parallel relationships
auth-login(login is part of auth)api-restvsapi-graphql(variants)- Dates:
2025-01-15 - Tickets:
JIRA-1234
File Patterns
Test files: test_«module»_«function»_«type».py
JSON test data: «module»_«function»_cases.json
Documentation: «topic».md (hyphen for hierarchy, underscore for phrases)
Config: .env, pyproject.toml, AGENTS.md
Path References
Use code blocks, not Markdown links:
**Core Principles**: @smith-principles/SKILL.md - Description
**Related Skill**: @skill-name/SKILL.md - Description
Variables: $WORKSPACE_ROOT, $REPO_ROOT, $HOME
Conventional Commits
Format: type: description or type(scope): description
Types: feat, fix, docs, refactor, style, test, chore, perf, build, ci
Length limits (50/72 rule):
- Subject: 50 chars target, 72 max
- Body: 72 chars per line
Examples
feat(auth): add token refresh
fix: resolve CORS issues
docs: update deployment guide
Also keep in mind:
- Scope each commit to a single logical change — avoid mixing unrelated changes ("add X and fix Y")
- Use
docsonly when the commit doesn't also change code
Special prefixes (outside conventional commits):
#WIP— Work-in-progress checkpoint, not a conventional commit (used when the user accepts a WIP checkpoint commit)
Assisted-by attribution
Smith policy: every commit, PR body, PR review comment, and Slack message
produced with AI assistance ends with an Assisted-by: trailer naming the agent
and model. The trailer format follows the Linux kernel coding-assistants policy
(https://docs.kernel.org/process/coding-assistants.html),
which mandates it for commits; extending it to PR bodies, review comments, and
Slack is a Smith convention.
Format: Assisted-by: «AGENT_NAME»:«MODEL_VERSION» [«tool»]…
- Smith value:
Assisted-by: Claude:«session-model-id»—«MODEL_VERSION»is the actual running session model id, not a frozen string. - Optional bracketed
[«tool»]entries name specialized analysis tools only (e.g. coccinelle, sparse, semgrep); never basic dev tools (git, editors).
Deterministic source — do not hand-type the trailer. Hand-typing is where
the format drifts (a parenthetical instead of the colon form).
smith-style/scripts/attribution.sh is the single source: it prints
Assisted-by: «AGENT_NAME»:«MODEL_VERSION» from the first model source listed
in smith-style/scripts/attribution-model-sources.txt that knows the session
(for Claude Code, smith-ctx-claude/scripts/attribution-model-source.sh, kept
current by the attribution-model-stamp hook). Pull it, never type it. For a
commit message, PR body or review comment, write the body to a draft file,
append the trailer in a separate shell call with …/attribution.sh >> «file»,
show the file, and pass its absolute path with git commit -F «file» or
gh … --body-file «file» as a command of its own. gh pr-review has no --body-file: run the script first and end a
single-quoted --body '…' with the line it printed, unchanged. For an MCP
message (Slack/Jira), run the script and paste its output.
One rule, checked where the agent writes.
smith-style/scripts/lib/attribution.mjs holds the rule. It asserts only that
the script's line did not reach a text: the line is missing, differs from what
the script prints, or an Assisted-by:-shaped line differs from it. A
matching line proves nothing more, so the rule makes no claim that the script
ran. Agent adapters call it at the agent's own tool boundary and block; humans
are not checked. A write whose text cannot be read from a declared field (a
-m message, the editor, a merge or revert message git generates, a literal
body where a body-file flag exists) is not allowed. Covered writes and their allowed
shapes are data, smith-style/scripts/attribution-signatures.json: git
commits, merges, pulls and reverts, pull request and issue comment text, and
Slack drafts. The Claude Code adapter is
smith-ctx-claude's attribution-guard. Registration in README "Hooks".
Never add a Signed-off-by: trailer yourself — only a human can certify the
DCO, so the agent must not add one (a human may still add their own), and
Assisted-by: does not replace human authorship/sign-off. It names the AI that
assisted, not the human who directed the work — the commit's author field, or
the account it posts under, already carries that name, so do not pair it
with an "on behalf of" line (@smith-gh-pr).
Claude Code's own default trailer competes with this. The harness appends
its own Co-Authored-By:/"Generated with Claude Code" text to commits and PR
bodies unless settings.json's attribution.commit/attribution.pr keys are
set to "" (the deprecated includeCoAuthoredBy boolean predates this and is
superseded by it). Set both to empty rather than to a literal Assisted-by:
string: attribution.commit/.pr take only a static string with no
model-name templating, so hardcoding one there reintroduces the exact
staleness problem attribution.sh exists to avoid. Emptying them removes the
competing default; attribution-guard.sh (@smith-ctx-claude) is what
actually enforces the correct trailer's
presence.
Branch Names
Pattern: type/description — the Conventional Branch specification
(https://conventionalbranch.org/), adopted verbatim
instead of a smith-invented hyphen/underscore split.
descriptionis one or more lowercase-and-digit segments joined by a single hyphen (-) — never an underscore, space, or uppercase letter. No consecutive, leading, or trailing hyphens. A segment MAY contain dots (.) — the spec allows this for version-like text, e.g.chore/deps-node18.20-bump.typematches the Conventional Commits type of the work (feat, fix, docs, refactor, style, test, chore, perf, build, ci) — branch type MUST match commit type.- Prefer full words over abbreviations (
cmd,cfg,auth) unless the full word exceeds 15 chars OR the abbreviation is a domain-standard term in this repo (gh= GitHub,pr= pull request,ci= continuous integration,mcp= Model Context Protocol).command,configuration,authenticationall fit — spell them out. - Never put
post-review/after-reviewin a branch or commit — that names the change's ORIGIN (a review round), not what the change does. Name the effect instead:fix/auth-token-expiry, notfix/auth-post-review.
Examples: feat/user-authentication, fix/plan-claude-model-detection,
docs/gh-pr-attribution-wording.
Why not encode scope hierarchy in the branch name. A two-separator
convention (hyphen = hierarchy, underscore = single concept) leaves the
choice between the two to judgement, so names get chosen by feel and
rationalized afterward instead of generated from an algorithm. The scope information also matters
to no tooling that exists. release-please
(https://github.com/googleapis/release-please) — the standard tool for
automating versions/changelogs from Conventional Commits, should this repo
ever adopt it — parses only commit messages on the default branch; it never
reads branch names. Scope belongs in the commit's type(scope): description
header, where release-please-class tooling actually looks; the branch name
only needs to be unambiguous and human-readable, which a single separator
already guarantees.
Enforced deterministically by smith-git/scripts/hooks/branch-name-guard.mjs
(PreToolUse, matchers: Bash + EnterWorktree) — blocks git commands and
pushes with non-conforming names, warns on EnterWorktree (cannot block,
rename immediately after). Bash matcher blocks branch-creating/renaming git
invocations (git branch, git checkout -b, git switch -c) and git push
with non-conforming target names. EnterWorktree matcher warns when creating
worktrees (tool always creates worktree-{name}, which violates the pattern)
and instructs immediate rename via git branch -m <type>/description. If the
name was not explicitly given by the user, still confirm it with them before
the first push — the hook checks format, not what the user actually wanted
named.
Older branch names follow the two-separator convention described above. This section governs new branches; do not rename already-merged branches to match retroactively.
External Communication Standards
Language matching:
- Match the language of the source context (English PR → English reply; zh-Hant Notion → zh-Hant reply)
- Code artifacts always English: variable names, commits, branch names, inline code comments
- Default for user-facing explanations when context is ambiguous: zh-Hant (user preference)
- Never switch language unprompted — if unsure, ask once
Wiki-link leakage prevention:
[[Page Title]]renders as broken literal text outside Notion- When copying to Slack/GitHub/Jira: convert to plain name or full URL
Issue format (Job Story):
- "When «situation», I want to «motivation», so I can «expected outcome»"
- Describe the problem and outcome, not the implementation approach
Related
@smith-git/SKILL.md- Branch and commit workflows- @smith-principles/SKILL.md - Core principles (DRY, KISS, YAGNI)
@smith-slack/SKILL.md- Slack message formatting and pre-send gate (owns all Slack rules)
Before You Finish
Before naming a file (branches: see Branch Names):
- Is it a single concept? → underscore
- Is it a part/variant? → hyphen
- Is it a date/ticket? → hyphen
Before committing:
- Subject ≤72 chars?
- Single atomic change?
- Type matches branch?
Assisted-by:trailer present, and no AI-addedSigned-off-by:?