feat(skills): add coding-dev.md and per-language rules under code-languages/

Generic coding rules and preferences distilled from existing agent rule
files, project AGENTS.md/.rules, and saved memories:

- strictness maximal; suppressions must be reported to the user
- never filter build/lint/test output through grep/tail/head
- reuse > fork > self-implement; generic code becomes its own package
- 1000-line file / 100-line function limits, split by concern
- regression test every fixed bug; never test against real user data
- events over polling; scope discipline; honest reporting

Per-language: rust, js-typescript, bash, python, go, csharp-dotnet, c-cpp.
Each extends coding-dev.md rather than repeating it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
BluscreamandClaude Opus 5 committed 2026-09-18 12:33:44 +02:00
1 parent ba95f171b2
commit 57968904bf
9 files changed
+1499 -1

No files matched your search

+293
View File
@@ -0,0 +1,293 @@
---
name: coding-dev
description: >-
Use this skill whenever writing, reviewing, refactoring, building, testing, or releasing code
in any language. Defines standing preferences on strictness, dependency choice, file and function
size limits, test discipline, build output handling, error/warning policy, and commit hygiene.
Links to per-language rules under code-languages/ for Rust, TypeScript/JavaScript, Bash, Python,
Go, C#/.NET, and C/C++.
---
# Coding & Development — Standing Rules and Preferences
These are the defaults for **all** code work, in any language. They are preferences with
reasons attached, not style trivia — most exist because ignoring them caused a real problem.
> [!IMPORTANT]
> **A project's own rules win.** If a repository has an `AGENTS.md`, `CLAUDE.md`, `AGENT.md`,
> `.rules`, or `CONTRIBUTING.md`, read it first — it is more specific than this document and
> overrides it on conflict. This file is the default for everything those files do not say.
Per-language specifics live in [`code-languages/`](./code-languages/):
| Language | Rules |
| :--- | :--- |
| Rust | [`code-languages/rust.md`](./code-languages/rust.md) |
| TypeScript / JavaScript | [`code-languages/js-typescript.md`](./code-languages/js-typescript.md) |
| Bash / Shell | [`code-languages/bash.md`](./code-languages/bash.md) |
| Python | [`code-languages/python.md`](./code-languages/python.md) |
| Go | [`code-languages/go.md`](./code-languages/go.md) |
| C# / .NET | [`code-languages/csharp-dotnet.md`](./code-languages/csharp-dotnet.md) |
| C / C++ | [`code-languages/c-cpp.md`](./code-languages/c-cpp.md) |
---
## 1. Strictness — Maximum, Always
**Configure every linter, compiler, and type checker as strictly as the toolchain allows.**
Strict mode is the starting point, not an aspiration to migrate toward later.
- Warnings are errors in CI and in the build gate (`-warnaserror`, `-D warnings`, `--strict`).
- Enable the pedantic/extra lint tiers, not just the defaults.
- Type checking must pass with **zero** errors — `tsc --noEmit`, `mypy`, `cargo check` alike.
- Never weaken a check to make a build pass. Fix the failing step. If a check is genuinely
wrong for the codebase, disable it **project-wide in one place with a comment explaining
why**, so the decision is visible and reviewable — never with a scattered inline suppression.
### Suppressions are a last resort, and they expire
- Try hard to actually fix an error or warning before suppressing it.
- When you do suppress one, **say so explicitly in your report to the user** — name the
warning, where it is, and why it could not be fixed. A silent suppression is the failure
mode this rule exists to prevent.
- **Re-test suppressions periodically.** Removing an `#[allow]` / `// eslint-disable` /
`#pragma warning disable` that no longer suppresses anything should produce no warning —
if it doesn't, delete it. Stale suppressions accumulate and hide real regressions.
---
## 2. Never Hide Build, Test, or Lint Output
**Do not pipe build, compile, lint, format, or test output through `grep`, `tail`, `head`,
`sed`, or any other filter.** Run the command so its complete output is visible.
**Why:** filtering hides warnings, notes, and diagnostics that did not match the pattern.
Grepping for `^warning|^error` drops multi-line notes, help text, deprecation notices, and
anything a tool emits in an unexpected format.
```bash
# WRONG — silently drops notes, helps, and deprecations
cargo clippy 2>&1 | grep -E "^(warning|error)"
# RIGHT
cargo clippy --workspace --all-targets --all-features
```
If output is genuinely too large, **narrow the command** (one test, one package, one target)
rather than filtering its output. The same applies to logging: do not swallow stderr, do not
redirect diagnostics to `/dev/null`, and do not set a log level that hides warnings during a
verification run.
---
## 3. Dependencies — Reuse Before You Build
A strict ordering for obtaining any non-trivial functionality:
1. **Use an existing library / crate / package / official SDK** that already does it.
2. **If one is close but not exactly right, fork it** (as a git submodule or vendored fork)
and modify it.
3. **Write your own implementation only when nothing else fits** — and say why, explicitly.
**Why:** the ecosystem has already solved correctness, edge cases, and platform quirks that
a fresh implementation will rediscover slowly and incompletely.
Before hand-rolling any subsystem — a protocol client, a parser, a date formatter, a
debounce, a retry/backoff, a semver comparator — **search first and state the finding.**
Prefer official/first-party SDKs over third-party wrappers.
### The corollary: new generic code becomes its own package
**When you implement something generic that no existing package provided — a parser or
reader for a file format, a protocol implementation, a client for an undocumented API —
it becomes its own package/crate/module in its own repository**, which the application then
depends on like any other dependency.
**Why:** it keeps the app's tree focused on the app, it forces a clean public API boundary,
and it gives back the library that was missing in the first place. Structure the split so
the reusable core has no UI, no framework, and no app-specific types — only the app shell
depends on those.
> [!TIP]
> Design for this from the start: put **all reusable logic in a core library** and keep the
> UI/CLI layer as presentation only. A core without GUI dependencies also builds and tests
> far faster, often without the app's heavyweight toolchain.
---
## 4. Size Limits — Files and Functions
| Unit | Soft target | Split at | Hard limit |
| :--- | :--- | :--- | :--- |
| **Source file** | ~400 lines | 600 lines | **1000 lines** |
| **Function** | under 50 lines | — | **100 lines** |
| **Function parameters** | 3–4 | — | ~5, then pass a struct/object |
| **Nesting depth** | 2 | — | 3, then extract |
- **Enforce the function limit with the linter, not by eye** (e.g. Clippy's `too_many_lines`).
A function that trips it wants extracting, not a suppression.
- **Enforce the file limit in the build script** — a check that fails over the hard limit and
*names* anything past the soft split point, so the seam gets found while splitting is cheap.
- **Split by concern, never by line count.** A large `strip.rs` becomes
`strip/fader.rs`, `strip/knobs.rs`, `strip/routing.rs` — never `strip1.rs` / `strip2.rs`.
- One coherent responsibility per module. A module that draws widgets should not also own
application state.
---
## 5. Tests
- **Write tests for every feature where testing is possible.** State plainly when something
is untestable (GUI, hardware, live third-party service) rather than quietly skipping it.
- **Every bug that was fixed gets a regression test.** A bug that broke once will break again;
the test is what makes the second time cheap. This is the highest-value test you can write.
- **A skipped test fails the gate.** Silently skipped tests are worse than missing ones —
they report green.
- Unit tests for logic; integration tests for protocol/boundary behavior.
- **Never test destructive operations against real data.** Use fixtures, temporary
directories, injected backends, and disposable test accounts. The user's real config,
data, devices, accounts, and files must never be used as scratch space.
- **Prove the test exercises the path under test.** A tool that silently falls back to a
default target when the requested one does not resolve will produce a passing measurement
of the wrong thing. Verify the wiring before trusting the result.
- **Test the value, not the presence of the key.** A check that counts entries which exist
will pass against a cleared or null value — and reads exactly like a working fix.
---
## 6. Verification — Before Claiming Anything Works
Run the **whole** gate, not the convenient part of it. A typical full gate:
> format check → lint (strict/pedantic) → type check → build → tests (with race/sanitizer
> where available) → docs build → size limits → smoke run
- **Prefer the project's build script** (`./scripts/build.sh`, `make check`, `./build.sh --lint`)
over hand-typed commands. It encodes steps you will otherwise forget, and it stays correct
when the toolchain moves.
- **Read the numbers, not just the verdict.** A suppression count that has grown is worth
noticing even when everything passes.
- **Confirm the binary you are testing contains the change.** A file existing proves a file
exists, not that it is current. Compare its mtime against the commit, or wait for the build
to actually report success — testing a stale binary reads exactly like "the fix didn't work".
- **A change to visual/UI code is not done until it has been looked at.** Build, run,
screenshot, compare.
- **State plainly what is stubbed.** A component that draws its frame but has no behavior is a
scaffold, not a finished component — say so rather than implying completion.
---
## 7. Architecture Preferences
- **Events over polling — always.** When there is an event, callback, channel, or watch API to
subscribe to, use it. Do **not** settle for a polling loop because a benchmark shows it
currently costs nothing; a measurement of 0% CPU does not make polling the right design. It
adds latency and can miss or coalesce events. If existing code polls where an event exists,
treat that as a finding worth fixing. Only poll when no event source exists — and say so.
- **Don't over-guard.** Check only conditions that can actually occur. A null check on a
non-optional value, a `try/catch` around code that cannot throw, or an optional-chain on a
type-guaranteed value is dead defensive code, not safety.
- **No abstraction for one or two call sites.** A factory used once is just a function call.
No class wrapping a single function, no state machine for a boolean, no
`Manager`/`Handler`/`Strategy`/`Provider` suffix unless multiple implementations exist.
- **Less code with the same behavior wins.** Between equivalent solutions, the shorter ships.
Three similar lines beat a premature helper.
- **Delete dead code, don't comment it out.** That is what version control is for.
- **Don't cache what a store already owns.** Read from the source of truth on demand; cache
only what has no source of truth.
- **A search-and-replace that matches nothing is a silent failure.** Assert the target string
exists before replacing, and verify the result afterward.
---
## 8. Scope Discipline
- **Solve the problem at hand.** No refactors unless asked. No "while I was in there" cleanups.
- **Smallest diff that correctly solves it.**
- **Don't refactor code you weren't asked to touch**, and don't mix unrelated concerns in one
change — backend work and UI work land separately.
- When you notice adjacent work worth doing, **describe it** rather than absorbing it.
- **Check `git status --short` before editing** and preserve unrelated in-progress changes.
- **Verify a TODO before acting on it.** Task documents are leads, not facts — grep for the
symbol, read the function, check the history. Work has been redone from scratch because a
stale TODO claimed something was missing that had shipped hours earlier.
---
## 9. Comments & Naming
- **Comment the *why*, never the *what*.** "A gap here, matching the reference layout" is
useful; `// draw the button` is not.
- **Don't narrate changes in comments** ("changed this to fix X") — that is git's job.
- Keep existing comments; they carry context you do not have.
- **Natural human text in user-facing strings** — errors, descriptions, toasts. Sentence case,
ending in a period, no robotic phrasing.
- **Never hardcode** what a constant, config file, or generated table already defines —
endpoints, colors, class names, translated strings, magic identifiers.
- Names checked mechanically stay correct; names checked by memory drift. If a project has a
naming rule that matters (a one-letter difference between the app and its library, say),
**put it in the build gate**.
---
## 10. Errors, Logging & Security
- Use the language's structured error type; do not stringify errors as the primary mechanism.
- **Never swallow an error silently.** No empty catch blocks.
- Use a real logger with levels — `debug` for traces, `info` for lifecycle, `warn` for
recoverable, `error` for unrecoverable. Not bare `print`/`console.log`.
- **Never log secrets** — tokens, auth headers, passwords, message content, personal data.
- **Never commit secrets.** Reference an environment file or secret store; commit only an
`.env.example` with empty values.
- **Treat every input from outside the trust boundary as hostile** — validate type, length,
and content. Normalize and bounds-check any path before touching the filesystem with it.
Never pass an externally-derived string to a shell.
- **Be extremely careful automating against real third-party services.** Use conservative
rate limits, generous delays, retries with backoff, and abort fast when a response looks
wrong. Assume you are one mistake away from a ban or a real-world side effect.
---
## 11. Git & Commits
- **Conventional Commits**: `feat(scope): …`, `fix(translator): …`, `test: …`, `docs: …`.
- Short, imperative subject lines.
- **Commit incrementally as work lands.** Never leave a large batch of uncommitted work at
the end of a task.
- **Submodules**: commit and push the submodule first, then the updated pointer in the parent.
Never leave a submodule commit unpushed — the parent would reference a commit nobody can fetch.
- Match the repository's existing trailer and attribution conventions; some repositories
deliberately carry none.
---
## 12. Reporting Work Honestly
- If tests fail, **say so and show the output**. If a step was skipped, say that.
- If a warning was suppressed rather than fixed, **name it**.
- Distinguish verified from unverified: GUI behavior, packaging, hardware, and live
third-party interactions are usually unverified unless you actually exercised them.
- Do not claim a visual match without having compared the two images.
- **Prefer the boring explanation.** A typo, a stale binary, or a self-matching pattern is
far more likely than a toolchain or sandbox bug — and an environment explanation is more
flattering and arrives faster, which is exactly why it is the one to distrust.
---
## 13. Environment & Tooling
- **Read `<tool> --help` before using a CLI.** Do not guess flags, and do not write an ad-hoc
script when an existing tool already does the job.
- **Build inside the project's designated environment** (container, venv, toolchain manager)
rather than the host, when the project specifies one. Note that entering a container
typically resets the working directory — chain `cd` inside the same command string.
- **Don't saturate the machine.** A full release build that consumes every core makes the
workstation unusable. When something latency-sensitive is running, halve the job count and
`nice` the build.
- **Prefer long-running work in the background** over blocking wait loops. Never write
`until <check>; do sleep 2; done` — it hangs the terminal and has to be interrupted by hand.
---
## 14. Related Skills
- [`code-languages/`](./code-languages/) — per-language rules that extend and specialize this document.