| name | preferences-style-and-conventions |
| description | Style and formatting conventions for code, documentation, naming, and file organization. Load when reviewing style consistency or setting up new files. |
Style and Conventions
Markdown and text formatting
- Write one sentence per line in markdown, text, and documentation files.
- Prefer prose over bullet lists when explaining concepts or providing narrative flow. Reserve bulleted lists for genuinely discrete items or enumerations, not for breaking up what should be continuous explanation.
- Keep section header nesting shallow. Avoid deeply nested subsections (###, ####) when flatter structure with clear prose transitions would be more readable. Most documents should rarely need headers beyond three levels.
- Use bold text (
**) sparingly, primarily for critical emphasis within sentences. Avoid bolding section labels, definitions, or key terms when plain text suffices. Prefer italic (*) for subtle emphasis.
- Avoid using emojis in code, comments, documentation, markdown files, etc unless explicitly requested to do so.
- For documentation-specific markdown conventions (frontmatter titles, header levels), see "Markdown formatting conventions" in
~/.claude/skills/preferences-documentation/SKILL.md.
Naming and case conventions
- Prefer lowercase except when replicating code conventions like PascalCase or camelCase, in acronyms, or in proper nouns.
- Do prefer to capitalize the first letter of the first word and use Chicago Manual sentence-style capitalization for
- complete sentences that end with punctuation marks
- markdown file title frontmatter, section headings, and any level of subsection heading
- Do not use uppercase words for emphasis or notification purposes like "IMPORTANT", "URGENT", "WARNING", etc except in relevant situations like error handling, logging, or quoting usage by other sources.
- Do not name files with all uppercase letters. Use lowercase kebab-case specifically for markdown filenames or if there is no specific convention for the programming language or filetype (e.g. python uses snake_case).
Factual commentary
Avoid presumptive, negatively judgmental, or editorializing language in code comments, documentation, and commit messages.
State what code does, not speculative narratives about why.
- Do not speculate about causes, motivations, or intent when you only observed an effect.
- Do not attribute habits or patterns from single observations ("often", "always", "usually", "tends to").
- Do not add evaluative judgment where factual description suffices.
- Reference relevant sources like documentation rather than inventing explanations.
Code comments
Write code that explains itself through naming, structure, and types, and add a comment only when the code cannot carry the information on its own.
This extends the "Factual commentary" stance above: state what the code does through the code itself, and reserve comments for what the code genuinely cannot express.
Prefer docstrings and language-canonical doc comments over inline comments.
Doc comments are API-contract documentation and flow into generated docs/reference/ material, so the language skills' requirements for them (Rust /// and //!, Haskell Haddock, Python and TypeScript docstrings, nix module option descriptions) remain in force and are out of scope for the rule below.
An inline comment earns its place only when it records something a competent reader could not recover from the code itself.
Qualifying circumstances are narrow: a non-obvious why behind a choice that is true and not a speculative narrative; a surprising external constraint such as an API quirk, hardware limit, or protocol-mandated ordering not visible locally; a workaround for an external bug, paired with a link to the upstream issue or pull request; a correctness, security, concurrency, or numerical-stability footgun the next editor would plausibly reintroduce; or a deliberate, counterintuitive deviation from the obvious approach, stating why the obvious approach fails.
The operative test is whether deleting the comment would lose information that the code, its names, and its types do not already provide.
If nothing is lost, it does not qualify.
Comments that fail this bar are noise: restating the code in prose, narrating control flow the structure already shows, section-divider banners, commented-out code (delete it; version control retains the history), and TODO or FIXME notes without an issue reference or owner.
Treat removing such comments as a standing responsibility rather than one gated to the file or change you are currently working on: when you encounter noise comments in the project's own code, remove them, and a comment-only cleanup is a legitimate standalone change.
This breadth is bounded by safety rails that are never relaxed.
Never alter vendored, third-party, generated, upstream-mirrored, or otherwise externally-owned trees, including any path bearing a DO NOT EDIT marker or designated by the repository as a mirror.
Never remove any item in the load-bearing list below.
When uncertain whether a comment is load-bearing or whether a tree is externally owned, leave it and surface the question rather than deleting; the cost of deleting one rationale-bearing comment exceeds the cost of leaving several redundant ones.
Some comments are load-bearing and must never be removed or rewritten as incidental cleanup, regardless of the bar above:
- License and SPDX headers, and copyright banners required by license terms.
- Shebang lines (
#!/usr/bin/env bash, #!/usr/bin/env python3).
- Source encoding declarations (
# -*- coding: utf-8 -*-).
- Linter and type-checker pragmas:
# type: ignore, # noqa, # pyright: ignore, # ruff: noqa, # shellcheck disable=SCxxxx, // eslint-disable, // @ts-expect-error, GHC {-# ... #-} pragmas.
- Formatter directives:
# fmt: off and # fmt: on, // prettier-ignore, nix formatter control comments, // rustfmt::skip.
- Public-API docstrings and doc comments that generate published documentation. Preserve these even when terse; tighten wording only as a deliberate documentation edit.
- Rust
// SAFETY: comments on unsafe blocks, and the per-exclusion "explain why" comments other skills require (for example nix-unit package exclusions); these are the canonical truly-exceptional cases.
- Code-generation markers (
// Code generated ... DO NOT EDIT., # Generated by ..., managed-block BEGIN and END sentinels) and any comment parsed by tooling as a directive (//go:generate, // go:build and cfg conditional-compilation comments, cython: directives, dependency-pin annotations).
This policy is intentionally strong because self-documenting code is the default expectation across the fleet (nix, python, rust, haskell, typescript, shell).
Ousterhout's calibration applies: comments exist to capture design intent and rationale that genuinely cannot live in the code, so the goal is eliminating redundant comments, not the rationale-bearing ones the qualifying list protects.
File organization
- Never pollute the repository root or other working directory with markdown files. Always place these types of working notes in suitable paths like:
./docs/notes/[category]/[lower-kebab-case-filename.md] where you may need to create the directory if it doesn't exist before creating the file. See "Working notes" in ~/.claude/skills/preferences-documentation/SKILL.md for lifecycle management and integration with formal documentation.
File length and modularization
Files should remain comprehensible as cohesive units.
As files grow, split along responsibility boundaries rather than at arbitrary line counts.
Soft guidance thresholds:
- Under 500 lines: generally appropriate
- 500-800 lines: consider extracting distinct sections
- Beyond 800 lines: likely needs splitting unless genuinely single-purpose
For code, extract modules with clear interfaces using the language's import mechanism.
For documentation, create index files linking to subpages or organize into subdirectories.
Create subdirectories when extractions form natural hierarchies or exceed 4-5 related files; otherwise sibling files with cross-references suffice.
Avoid splitting when it would fragment a genuinely cohesive unit or create excessive coupling through circular references.
Development workflow and tooling
Pre-implementation checkpoint
Before transitioning from planning to implementation, materialize the plan into concrete commitments.
Determine precisely which files and directories will be modified, created, or removed.
Define the grouping and sequence of commits with draft commit messages.
Specify how each commit or collection of changes will be verified as useful progress: passing new or existing tests, producing observable output, improving conceptual clarity, or satisfying other criteria appropriate to the change.
This checkpoint converts abstract plans into auditable intentions, reducing rework from misaligned assumptions.
For each proposed change, identify the confidence level the verification plan is expected to achieve and whether the verification would be severe — would it fail under plausible incorrect implementations?
See preferences-validation-assurance for the severity criterion and confidence promotion chain.
When working within a beads issue graph, map each proposed file change to the issue it addresses and confirm the change will satisfy that issue's acceptance criteria.
- Always at least consider testing changes with the relevant framework like bash shell commands where you can validate output,
shellcheck for shell scripts, cargo test, pytest, vitest, nix eval or nix build, a task runner like just test or make test, or gh workflow run before considering any work to be complete and correct.
- Be judicious about test execution. If a test might take a very long time, be resource-intensive, or require elevated security privileges but is important, pause to provide the proposed command and reason why it's an important test.
- Local test execution is the primary feedback loop during development.
Run tests iteratively as you work, fixing issues before committing.
CI workflow verification is a distinct stage that occurs when validating a branch for merge/pull request, not during routine local development.
CI workflow log verification
When validating changes for merge, verify CI results by downloading and analyzing complete workflow logs rather than piecing together fragments via repeated API calls.
Wait for relevant workflows to complete:
gh run watch <run_id>
Download the complete logs archive:
run_id=<run_id>
mkdir -p logs
gh api "repos/<owner>/<repo>/actions/runs/${run_id}/logs" > "logs/gha-${run_id}.zip"
unzip -d "logs/gha-${run_id}" "logs/gha-${run_id}.zip"
Survey available jobs and steps:
tree --du -ah "logs/gha-${run_id}"
Unified PR check log retrieval
Given only a pull request number, the unified entry point for discovering which checks ran and where their logs live is PAGER=cat gh pr checks <N>.
This command emits one row per check with a direct link, spanning both CI backends used by repositories in this workspace.
GitHub Actions rows link to https://github.com/<owner>/<repo>/actions/runs/<run_id>/job/<job_id>.
Buildbot-nix rows link to https://buildbot.scientistexperience.net/#/builders/<builder_id>/builds/<build_number> and correspond to the buildbot/nix-eval and buildbot/nix-build status checks described in preferences-nix-ci-cd-integration.
Use the JSON output to separate the two categories and drive automation:
gh pr checks <N> --json name,link \
| jq -r '.[] | select(.name | test("^buildbot/")) | .link'
gh pr checks <N> --json name,link \
| jq -r '.[] | select(.link | test("/actions/runs/")) | .link'
Route each link to its backend-specific log-download recipe, writing artifacts under a single logs/ directory (already gitignored):
mkdir -p logs
run_id=<run_id>
gh api "repos/<owner>/<repo>/actions/runs/${run_id}/logs" > "logs/gha-${run_id}.zip"
unzip -d "logs/gha-${run_id}" "logs/gha-${run_id}.zip"
builder_id=<builder_id>
build_number=<build_number>
buildbot-logs "${builder_id}" "${build_number}" > "logs/buildbot-${builder_id}-${build_number}.log"
The buildbot-logs shell application is defined in this repository; see preferences-nix-ci-cd-integration for its interface, ssh transport, and authentication model.
After download, survey each artifact before dispatching subagents for targeted analysis:
tree --du -ah "logs/gha-${run_id}"
rg "error:" "logs/buildbot-${builder_id}-${build_number}.log"
Dispatch subagent Tasks to analyze specific log files for the problem at hand rather than manually reading large logs inline.
This approach provides a complete, well-organized view of CI results and avoids the antipattern of fragmented API-based log retrieval that never yields a clear picture of what happened.
- Use performant CLI tools matched to task intent:
- File search (by name/path): use
fd instead of find
- Content search (within files): use
rg (ripgrep) instead of grep
- Disk usage (directory sizes): use
diskus instead of du -sh
- Clipboard (copy to system clipboard): use
cb copy (clipboard-jh) instead of platform-specific pbcopy (macOS) or xclip/xsel (Linux)
- Notification (push alert to user): use
ntfy-send "<message>" where <message> includes the repo name and summarizes the completed task (default topic is the local hostname; override with ntfy-send "<message>" <topic>)
- When the user mentions a git repository by name (from GitHub or any other forge), or you are about to state a substantive technical claim grounded in a software project's source, options, defaults, API, or upstream documentation — first check for a local copy under
~/projects/ before reaching for web tools or asking the user for context. Treat the lookup as default for such claims; bypass it only with explicit justification. The steps:
- Search the full tree:
fd -t d -d 4 '^<repo>$' ~/projects (the canonical layout is ~/projects/<topic>-workspace/<repo>/, but copies may live elsewhere; repo names can have variants such as <repo>.jl or <repo>-rs).
- Verify the match:
cd candidate-dir && git remote -v to confirm the origin matches the expected forge URL. Name collisions are common with single-token repo names, so this step is required, not optional, before treating a candidate as authoritative.
- On hit: treat the local path as the source of truth. Dispatch subagent Tasks to that path for research, reference, or comparison, and use
~/projects/... paths in subsequent prompts and writeups.
- On miss: surface the failure to the user and ask them to clone or fork the repo to
~/projects/<topic>-workspace/<repo>/ (or to provide the path if it lives elsewhere) before proceeding. Do not silently fall back to WebFetch/WebSearch for substantive research when a local copy would be more authoritative; web tools remain appropriate only for genuinely web-native content (release notes pages, issue discussions, blog posts). When surfacing the miss, propose the exact command: git clone <url> ~/projects/<topic>-workspace/<repo>/ for reference-only work, or gh repo fork <org>/<repo> --clone --remote -- ~/projects/<topic>-workspace/<repo>/ when contribution back to upstream is anticipated. Pause the work thread until the clone is in place or the user provides an alternative path.
- When given a GitHub file URL (e.g.,
https://github.com/org/repo/blob/ref/path/to/file.ext#L119-L131), apply the lookup above for repo, then Read the file with the line range. Use WebFetch only if no local copy exists and the user has declined to clone.
- When given a GitHub issue/PR URL (e.g.,
https://github.com/org/repo/issues/2491), use gh issue view 2491 -R org/repo or gh pr view 2491 -R org/repo to access discussion content and metadata.
- When the user appends
(see local) — or a close variant such as (see local clone) or (local) — to a name, treat it as a mandatory directive to apply the lookup above for that name before responding. The marker removes the "could plausibly identify a git repository" judgment from the preceding bullet: with the marker present, the lookup is unconditional. Do not answer from general knowledge, do not reach for WebFetch/WebSearch, and do not ask whether a clone exists. If step 1 returns no hit, fall through to the on-miss handling in step 4.