Compare commits

..
7 Commits
Author SHA1 Message Date
BluscreamandClaude Opus 5 3171595940 fix(python): PLR0915 counts statements, not lines
Verified with `ruff rule PLR0915`: it is `too-many-statements` (default 50),
not a line-length rule. Ruff has no line-based function-length rule at all.
python.md now says so and points both limits at a pytest case; coding-dev.md
gains the general distinction between a real length lint and a proxy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 12:56:12 +02:00
BluscreamandClaude Opus 5 32b47b075b fix(coding-dev): prefer a test over the build script for size limits
A shell check only runs where that script runs. A test travels with the repo,
needs no extra tooling, and fails with a message naming the offending files.
Enforcement is now a preference order: native linter rule > test > build
script as fallback, with a worked example and a note on why a per-compile
build hook usually is not worth the incremental-build cost.

Propagated per language: rust (clippy has no file-length lint -> workspace
root test), js-typescript (ESLint max-lines covers both natively, no test
needed), go/python/csharp (linter for functions, test for files), bash (don't
reimplement in the gate what a test can assert).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 12:52:37 +02:00
BluscreamandClaude Opus 5 5d96e69d2b refactor(skills): inline cross-links, drop Related sections, fix conventions
- all nine files: trailing "Related" sections removed, their unique links
  inlined where the topic actually arises (FFI boundary -> c-cpp, embedded
  frontend -> js-typescript, build scripts -> bash, etc.)
- README conventions: rule 2 mandated a Related Skills section, which now
  contradicts practice — replaced with inline-linking and one-home-per-fact
  rules. The <repo>/<rel path> rule was appended after the wrong section
  last commit; moved into the list where it belongs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 12:49:30 +02:00
BluscreamandClaude Opus 5 58525056a1 docs: use <repo>/<rel path> links throughout, document the convention
All cross-skill links now spell out the repository: skills-public/foo.md,
skills-private/bar.md. These resolve from the shared parent directory, so one
spelling works from either repo and from code-languages/ subdirectories —
replacing the previous mix of ./, ../ and absolute file:// URLs. Link labels
normalised to match. Convention added to the README conventions list.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 12:44:23 +02:00
BluscreamandClaude Opus 5 72834167b6 feat(cli-tooling): add generic CLI, tooling and script-placement rules
Extracted from the per-agent profile files and the private ai-agents skill,
where they were duplicated and environment-coupled:

- discovery over guessing: walk the help tree, never invent flags, never
  hand-roll a script when a dedicated CLI exists
- non-interactive invocation: explicit targets, the tool's own --wait
- machine-readable output flags (json/compact/csv) vs. never filtering
  diagnostics
- orient before starting: project instructions, git log, prior work records,
  and verifying TODOs against the code
- script placement: dedicated version-controlled dir, symlinked into PATH
- destructive/outward-facing command care; environment drift verification

coding-dev.md and code-languages/bash.md now cross-reference it instead of
carrying their own one-line versions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 12:40:55 +02:00
BluscreamandClaude Opus 5 9571eb52d2 fix(coding-dev): scoped suppressions, contribution-dependent precedence
- suppressions: prefer the narrowest scope that covers the problem (line,
  function, module) with a reason comment; a project-wide disable silences
  the rule in code nobody has written yet. Always name the specific rule.
  Propagated to rust (#[allow]), csharp (#pragma/SuppressMessage).
- precedence: a project's own rules win only when contributing upstream.
  For local/private work these rules win, adopting only the project's
  load-bearing conventions.
- type checking must pass with zero errors *or warnings*.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 12:34:51 +02:00
BluscreamandClaude Opus 5 57968904bf 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>
2026-09-18 12:33:44 +02:00
10 changed files with 1744 additions and 1 deletions

No files matched your search

+45 -1
View File
@@ -6,7 +6,51 @@ A collection of public, reusable agent skills, cheatsheets, and operational work
## 📂 Skills Index
*(Public skills will be added here as generic, open workflows)*
| Skill File | Scope / Topics Covered |
| :--- | :--- |
| **[`coding-dev.md`](skills-public/coding-dev.md)** | Standing rules for all code work: maximum linter/compiler strictness, never filtering build output, reuse-before-reinvent dependency ordering, file/function size limits, test & regression discipline, verification gates, scope control, commit hygiene, and honest reporting. |
| **[`cli-tooling.md`](skills-public/cli-tooling.md)** | Using command-line tools correctly: reading the help tree instead of guessing flags, preferring an existing CLI over an ad-hoc script, non-interactive invocation, machine-readable output formats, orienting on prior work before starting, script placement & symlinking, and care around destructive commands. |
### 🔤 Per-Language Rules ([`code-languages/`](skills-public/code-languages/))
| Language | Skill File | Highlights |
| :--- | :--- | :--- |
| **Rust** | [`rust.md`](skills-public/code-languages/rust.md) | `--workspace` clippy gate, `[workspace.lints]` single source of truth, no `unwrap`/`unsafe`, core-crate split |
| **TypeScript / JavaScript** | [`js-typescript.md`](skills-public/code-languages/js-typescript.md) | TS > JSDoc > plain JS, banned constructs table, React & hooks rules, resource lifecycle, sandbox boundaries |
| **Bash / Shell** | [`bash.md`](skills-public/code-languages/bash.md) | `set -euo pipefail`, shellcheck, no wait loops, destructive-command guards, build-gate structure |
| **Python** | [`python.md`](skills-public/code-languages/python.md) | `mypy --strict`, broad ruff rule set, asyncio task discipline, rate-limit queueing |
| **Go** | [`go.md`](skills-public/code-languages/go.md) | strict `golangci-lint` set, concrete types over `interface{}`, context everywhere, CGO-free static builds |
| **C# / .NET** | [`csharp-dotnet.md`](skills-public/code-languages/csharp-dotnet.md) | `TreatWarningsAsErrors`, nullable enabled, no `.Result`, trimming/single-file publish caveats, SDK shims |
| **C / C++** | [`c-cpp.md`](skills-public/code-languages/c-cpp.md) | full warning set + `-Werror`, sanitizers, RAII/Rule of Zero, target-based CMake, FFI ownership |
Each language file extends `coding-dev.md` rather than repeating it. A project's own
`AGENTS.md` / `CLAUDE.md` / `.rules` always overrides both.
---
## ✍️ Skill Document Conventions
Skills in this repo follow the same structure as the private counterpart repo, so agents can load either interchangeably:
1. **YAML frontmatter** with `name` (kebab-case, matching the filename) and a `description` starting with *"Use this skill when …"* — the description is what an agent matches against to decide relevance, so it must enumerate concrete triggers.
2. **Numbered `##` sections**, sequential and unique.
3. **Cross-link inline, at the point the other skill becomes relevant** — never in a trailing
"Related Skills" list. A link inside the sentence that raises the topic gets followed; a
list at the bottom of a long document does not, and it drifts out of sync with the body.
4. **One home per fact.** Content that belongs to another skill is *replaced* by a link to it,
not duplicated. Where a skill needs its own specific values for something another skill
owns the rules for (a container's own template fields, say), keep only the values and link
out for the rules.
5. **Link between skills as `<repo>/<relative path>`** — e.g.
`` [`cli-tooling.md`](skills-public/cli-tooling.md) `` or
`` [`network.md`](skills-private/network.md) ``. Links resolve from the shared parent
directory holding both repos, so the same spelling works from either repo and from any
subdirectory. Never `./`, `../`, or absolute `file://` paths. The sole exception is the
per-agent profile files, which state each repository root once in full, since they are
loaded as global instructions with no implied working directory.
6. **CLI discovery over enumeration** — document the *non-obvious* workflows, gotchas, and constraints. For the routine surface, point at `<tool> --help` rather than transcribing flag lists that will go stale.
7. **No secrets inline** — reference the environment file that holds them (e.g. `~/.config/environment.d/…`), never the values themselves.
8. **Verify before documenting** — confirm hostnames, container names, and versions against the live system rather than carrying forward stale claims.
---
+182
View File
@@ -0,0 +1,182 @@
---
name: cli-tooling
description: >-
Use this skill whenever invoking a command-line tool, choosing between a CLI and an ad-hoc
script, writing a standalone utility script, or orienting on unfamiliar work before starting.
Covers discovering a CLI's real interface instead of guessing flags, non-interactive and
machine-readable invocation, script placement and symlinking, and care around destructive
or outward-facing commands.
---
# CLI & Tooling — Discovery, Invocation, and Script Placement
How to use command-line tools correctly, and where utility scripts belong.
Companion to [`coding-dev.md`](skills-public/coding-dev.md), which covers writing the code itself.
---
## 1. Discovery Over Guessing
> [!IMPORTANT]
> **Read the tool's own help before using it. Never guess subcommands or flags.**
> ```bash
> <tool> --help
> <tool> <subcommand> --help
> ```
A tool's built-in help is authoritative and current; memory and training data are neither.
Flags get renamed, subcommands get restructured, and a confidently-invented flag either
errors out or — far worse — silently means something else.
**This applies to listing capabilities, too.** When asked what a tool can do, run its help
and report what it actually says. Do not produce a speculative command list; a plausible-
looking invented flag is indistinguishable from a real one to whoever reads it next.
### Walk the whole help tree
Multi-level CLIs hide most of their surface one level down. `<tool> --help` shows the
subcommand list; the flags that matter usually live in `<tool> <subcommand> --help`.
### Don't reinvent a tool that exists
**When a dedicated CLI already provides the functionality, use it** rather than writing an
ad-hoc script, a one-off `curl` pipeline, or a bespoke API client. The CLI already handles
authentication, retries, pagination, error formatting, and the edge cases you have not
thought of yet.
Before writing a script to do something, check whether:
- a dedicated CLI for that system is already installed,
- the tool you are already using has a subcommand for it,
- the project's own `scripts/` directory already contains it.
---
## 2. Non-Interactive Invocation
Anything run from a script, a CI job, or an agent must not depend on a human being present.
- **Pass required targets explicitly.** A CLI that manages multiple objects will, when no
target is given, either prompt interactively or fail — both of which break automation.
Always pass `--server <id>`, `--profile <name>`, `--target <x>` rather than relying on a
default or a prompt.
- **Use the tool's own blocking flag** (`--wait`, `--follow`, `--no-detach`) when you need an
operation to finish before the next step. Do not build a polling loop around a command that
already knows how to wait — and never write a shell `until`/`while` wait loop
(see [`code-languages/bash.md`](skills-public/code-languages/bash.md)).
- **Prefer a `--dry-run` rehearsal** for anything that deploys, releases, or deletes, when the
tool offers one.
- Set `--yes` / `--non-interactive` deliberately and only where the action is genuinely safe —
not as a reflex to stop a tool from asking.
---
## 3. Machine-Readable Output
When output feeds another program — or an agent's context — ask the tool for a structured
format rather than parsing its human-facing table.
```bash
tool list -o json # full fidelity
tool list -o compact # minimal tokens, good for agent context
tool list -o csv # structured columns for a spreadsheet or script
```
- **Never scrape a table meant for humans.** Column widths, truncation, colour codes, and
alignment all change without notice; a structured format is a contract.
- Strip colour explicitly (`--color never`, `NO_COLOR=1`) when capturing output, or escape
sequences end up in your parsed values.
- Keep in mind that a "compact" or summary format is the right default for feeding an agent,
while `json` is right for a script that needs every field.
> [!WARNING]
> **Never pipe build, lint, or test output through a filter to make it shorter.** That is a
> different situation and the rule there is the opposite — see
> [`coding-dev.md` §2](skills-public/coding-dev.md). Structured output flags are for data; `grep` on
> diagnostics hides warnings.
---
## 4. Orient Before Starting
Before beginning work in an unfamiliar project or one you have not touched recently, spend a
moment establishing what has already happened. Cheap, and it prevents the most expensive
failure mode: rebuilding something that already exists.
In rough order of value:
1. **The project's own instructions** — `AGENTS.md`, `CLAUDE.md`, `.rules`, `CONTRIBUTING.md`,
`README.md`. Read them before touching code.
2. **Recent history** — `git log --oneline -20`, and `git status --short` to see what is
already in flight so you do not clobber it.
3. **Prior work records** — session/conversation history tooling, issue trackers, or notes
files, scoped to the project directory.
4. **The code itself** — `grep` for the symbol before trusting any document that says it is
missing.
> [!CAUTION]
> **Task documents are leads, not facts.** A `TODO.md` entry can be stale, duplicated, or
> simply wrong — including when it sounds confident and especially when it sounds familiar.
> Verify against the code before acting on one. A single `grep` is cheaper than rediscovering
> that the work shipped last week.
---
## 5. Where Scripts Live
> [!IMPORTANT]
> **Never write a standalone script directly into a `PATH` directory** (`~/.local/bin`,
> `/usr/local/bin`, or similar). Write it into a **dedicated scripts directory** that is
> version-controlled, then **symlink** it into `PATH`.
```bash
# write the script to its real home
$EDITOR /path/to/scripts/my-tool.sh
chmod +x /path/to/scripts/my-tool.sh
# then expose it
ln -s /path/to/scripts/my-tool.sh ~/.local/bin/my-tool
```
**Why:** one versioned source of truth. A script living directly in `PATH` is invisible to
version control, easy to clobber on a reinstall, impossible to diff, and indistinguishable
from a package-manager-owned binary sitting beside it. The symlink makes the provenance
obvious — `ls -l` shows exactly where the real file is.
The same applies within a project: scripts belong in `scripts/`, referenced by the build
system, not scattered at the repository root.
---
## 6. Destructive and Outward-Facing Commands
- **Look at the target before deleting or overwriting it.** Inspect it first — the cost of a
wrong `rm` or a wrong overwrite is unbounded and the check is one command.
- **Read-only first.** When exploring an unfamiliar tool, use its inspection subcommands
(`status`, `list`, `show`, `get`) before any mutating one.
- **Mutating commands that rewrite shared configuration** — syncing settings across machines,
purging caches or state, patching installed applications — should be confirmed before
running, even when they are reversible in principle.
- **Never use real data as scratch space.** Real accounts, real configuration directories,
live services, and the user's actual files are not disposable test fixtures. Use temporary
directories and disposable targets.
- Anything that sends, publishes, deploys, or purchases is outward-facing: confirm first, and
treat approval as applying to that one action rather than the category.
---
## 7. Environment Constraints
- **Entering a container or changing shells resets the working directory.** Chain `cd` inside
the same command string rather than relying on it persisting:
```bash
container-run <name> -- bash -c "cd /path/to/project && make"
```
- **Verify what actually exists rather than assuming from documentation.** Container names,
tool versions, and installed toolchains drift. A one-command check (`<tool> list`,
`<tool> --version`, `command -v <tool>`) costs nothing and prevents a confident invocation
of something that was removed months ago.
- **Check for an optional dependency before the step that needs it**, and fail loudly if it
is missing. A packaging step that silently skips when its tool is absent ships an
incomplete release.
- **Don't saturate the machine.** A full build consuming every core makes the workstation
unusable. When something latency-sensitive is running, reduce the job count and `nice` the
work — and say that you did.
+171
View File
@@ -0,0 +1,171 @@
---
name: bash
description: >-
Use this skill when writing or modifying Bash/shell scripts, build pipelines, release
scripts, or when running shell commands as an agent. Covers strict mode, shellcheck,
quoting, the no-wait-loops rule, build-script structure, and destructive-command safety.
---
# Bash / Shell — Rules & Preferences
Extends [`coding-dev.md`](skills-public/coding-dev.md). Everything there applies; this adds shell specifics.
Bash is for **orchestration** — sequencing tools, wiring a build pipeline, gluing CLIs.
Once a script grows real data structures, arithmetic, or parsing, it wants to be
[Python](skills-public/code-languages/python.md) or a compiled tool instead.
---
## 1. Strict Mode & Linting
Every script starts with:
```bash
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
```
- `-e` exit on error, `-u` error on unset variable, `-o pipefail` so a failure mid-pipe is
not masked by a successful final command.
- **`shellcheck` must be clean** and belongs in the build gate. Do not silence a finding with
a `# shellcheck disable=` without a comment saying why.
- Format with `shfmt`.
> [!IMPORTANT]
> `set -e` does **not** fire inside `if`, `&&`, `||`, or command substitution used in a
> condition. Do not assume a failing command inside those aborts the script — check
> explicitly where it matters.
---
## 2. Quoting & Expansion
- **Quote every expansion**: `"$var"`, `"${arr[@]}"`, `"$(cmd)"`. Unquoted expansion word-splits
and glob-expands, which is how a path with a space becomes two broken arguments.
- `"${var:-default}"` for defaults; `"${var:?message}"` for required values.
- Use arrays for argument lists — never build a command as a string and `eval` it.
- `[[ ]]` over `[ ]`. `$(…)` over backticks.
- `local` every variable inside a function.
- `printf` over `echo` for anything with escapes or leading dashes.
---
## 3. Never Write Wait Loops
> [!CAUTION]
> **Do not write `until <check>; do sleep 2; done` or `while ! <check>; do sleep …; done`.**
> They hang the terminal, and the only way out is an interrupt.
To wait for something:
- Run the work in the **background** and wait for its completion signal.
- To check external state, **take a single sample per invocation** rather than looping.
- If a tool has a real blocking wait (`--wait`, `wait-for`, `systemctl is-active --wait`),
use that instead of polling around it.
This mirrors the general events-over-polling preference: prefer a notification to a loop.
---
## 4. Never Filter Build Output
Repeating the global rule because shell is where it gets violated:
```bash
# WRONG
cargo build 2>&1 | grep -E "error|warning"
npm run build | tail -20
# RIGHT
cargo build
npm run build
```
Tee to a log if you need a copy — `cmd 2>&1 | tee build.log` preserves the full output — but
never `grep`/`head`/`tail` it away. Narrow the command instead when output is genuinely huge.
---
## 5. Destructive Commands
- **Look at the target before deleting or overwriting it.** `ls` it, `stat` it, read it.
- Never `rm -rf "$VAR"` without first asserting `$VAR` is non-empty and is what you think:
```bash
[[ -n "${BUILD_DIR:-}" && -d "$BUILD_DIR" ]] || { echo "refusing: BUILD_DIR unset" >&2; exit 1; }
```
- **A shared output directory belongs to nobody.** If two scripts write to `dist/`, neither
may `rm -rf` it — each cleans only its own outputs.
- Write scratch files to a `mktemp -d` and trap cleanup:
```bash
tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT
```
- Prefer a `--dry-run` flag on anything that deploys, releases, or deletes — and implement it
honestly.
### `pgrep -f` matches its own shell
> [!WARNING]
> The command line containing the pattern **is itself a process**, so `pgrep -f foo.conf`
> matches the shell running it. Piping that into `kill` kills the running script — which reads
> exactly like "the thing I was testing never happened".
>
> Write the pattern so it cannot match itself: `pgrep -f 'my[a]pp\.conf'`
---
## 6. Build Script Structure
A build script is the project's **quality gate**, and it should be exhaustive enough to catch
mistakes before a human does.
- **Fixed action order regardless of flag order**, with any failure aborting the rest:
```
bump → lint → build → artifacts → stop → deploy → start → commit → push → release
```
- **A skipped test fails the gate.** Never let a skip report green.
- **Print a summary with numbers**, not just a verdict — counts of warnings, suppressions, and
file-size violations. A suppression count that has grown is worth seeing even on a pass.
- **Don't reimplement in the gate what a test can assert.** Checks like file-size limits
belong in the test suite where they travel with the repo and fail with a useful message;
the build script is the fallback for projects with no suite to hang them on. See
[`coding-dev.md` §4](skills-public/coding-dev.md).
- Keep per-step logs in a known directory so a failure can be read after the fact.
- **Never bypass a failed gate** by copying a binary or running the install step by hand. Fix
the failing step; do not weaken the check to make it pass.
- Support `--dry-run` for the full release path.
- **Throttle heavy builds.** Detect when something latency-sensitive is running and drop to
half the cores at `nice -n 10`, announcing that it did so. Provide an override env var.
---
## 7. Paths & Portability
- Resolve the script's own directory rather than assuming the caller's `cwd`:
```bash
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
```
- Prefer absolute paths in anything non-interactive.
- **Entering a container resets the working directory** — chain `cd` inside the same command
string: `container-run -- bash -c "cd /path && make"`.
- Don't hardcode `/tmp` when the content must survive a cleanup; `/var/tmp` persists longer.
- `command -v tool >/dev/null || { echo "tool required" >&2; exit 1; }` before using an
optional dependency. **A silently skipped step is worse than a failure** — a packaging step
that quietly skips when its tool is missing will ship an incomplete release.
---
## 8. Secrets
- Read secrets from an environment file or secret store; **never** inline them in a script.
- Never `echo` a secret, and never pass one as a command-line argument — argv is world-readable
in the process table. Use an env var or stdin.
- Never commit a file containing real credentials; commit an example with empty values.
---
## 9. Script Placement
Standalone utility scripts belong in a **dedicated, version-controlled scripts directory**,
symlinked into `PATH` rather than copied there. Never write a loose script directly into
`~/.local/bin` or another `PATH` directory — the source of truth should be one versioned
location. See [`cli-tooling.md` §5](skills-public/cli-tooling.md) for the reasoning and the pattern.
+146
View File
@@ -0,0 +1,146 @@
---
name: c-cpp
description: >-
Use this skill when writing, building, or debugging C or C++ — including compiler warning
configuration, sanitizers, modern C++ resource management, build systems (CMake/Meson),
32-bit/cross builds, and FFI boundaries exposed to other languages.
---
# C / C++ — Rules & Preferences
Extends [`coding-dev.md`](skills-public/coding-dev.md). Everything there applies; this adds C/C++ specifics.
C and C++ are for the cases with no better option: existing codebases, hard real-time paths,
and FFI boundaries. **For new standalone work, prefer [Rust](skills-public/code-languages/rust.md)** — the memory-safety
guarantees are worth more than the familiarity.
---
## 1. Warnings — Everything On, Errors
```cmake
target_compile_options(mytarget PRIVATE
-Wall -Wextra -Wpedantic -Werror
-Wshadow -Wconversion -Wsign-conversion
-Wnon-virtual-dtor -Wold-style-cast -Wcast-align
-Wunused -Woverloaded-virtual -Wdouble-promotion
-Wformat=2 -Wnull-dereference -Wimplicit-fallthrough
)
```
- **`-Werror` in the build gate.** `-Wconversion` and `-Wshadow` are noisy on first contact and
catch real bugs — do not drop them because a legacy file complains; fix the file or scope the
suppression to it.
- Run **both** GCC and Clang where possible; each diagnoses things the other misses.
- `clang-tidy` with `bugprone-*`, `cert-*`, `performance-*`, `modernize-*`.
- `clang-format` with a committed `.clang-format`. No hand-formatting.
---
## 2. Sanitizers — Use Them
```bash
cmake -DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer"
```
- **ASan + UBSan on every debug/test build.** They find the bugs that warnings cannot.
- TSan for anything threaded (separate build — it is incompatible with ASan).
- Valgrind when a sanitizer build is not possible.
- **A sanitizer finding is a bug, not noise.** Fix it.
---
## 3. Resource Management (C++)
- **RAII for everything.** Every raw `new`/`delete`, `malloc`/`free`, `fopen`/`fclose`, and
native handle pair is a leak waiting for an early return or a throw.
- `std::unique_ptr` by default; `std::shared_ptr` only where ownership is genuinely shared.
`std::weak_ptr` to break cycles.
- **No owning raw pointers.** A raw pointer or reference in a signature means "borrowed,
non-owning, outlives the call".
- `std::string_view` / `std::span` for non-owning views — and never return one that outlives
its backing storage.
- **The Rule of Zero**: design so no destructor, copy, or move needs writing. If you must write
one, write all five.
- `constexpr`, `const`, and `noexcept` wherever they hold. `[[nodiscard]]` on anything whose
return value must not be dropped.
---
## 4. C Specifics
- C17 or later unless the target forbids it.
- **Check every allocation and every return value.** In C, unchecked returns are the primary
bug source.
- Fixed-width types from `<stdint.h>` (`uint32_t`), never bare `int` for anything with a
defined width on the wire.
- Always the bounded string functions (`snprintf`, `strncpy` with explicit termination); never
`strcpy`, `sprintf`, `gets`.
- `static` on every function and global not exported from the translation unit.
- Prefer a struct-and-functions module boundary with an opaque pointer over exposing layout.
---
## 5. Build Systems
- **CMake** (3.20+, target-based) or **Meson** for new projects. Never a hand-written Makefile
for anything non-trivial.
- Target-based CMake only: `target_link_libraries`, `target_include_directories`,
`target_compile_options`. **No directory-level `include_directories` or global
`CMAKE_CXX_FLAGS` mutation.**
- `PRIVATE` / `PUBLIC` / `INTERFACE` chosen deliberately — this is what keeps include leaks out
of dependents.
- **Register every new source file with the build system** in the same commit that adds it. A
source file the build never compiles will sit there looking correct while every edit to it
does nothing.
- Vendor dependencies with `FetchContent` or submodules; document which and why.
---
## 6. Cross & Multi-Arch Builds
- Keep 32-bit and 64-bit outputs in **separate build directories**; never reuse one.
- **A component built for one architecture cannot load one built for another.** An
`ELFCLASS64` / `ELFCLASS32` error means a loader path is reaching the wrong build — check the
library search path, not the code.
- When a build must match an already-installed runtime's version, **pin it explicitly** and
note the constraint. A silently mismatched pair fails at load time with a message that
points nowhere useful.
- Record the exact toolchain and container used for a cross build. "It built on my machine" is
not reproducible for native code.
---
## 7. FFI Boundaries
When exposing C/C++ to another language:
- `extern "C"` on the boundary, with a hand-written, stable, documented header.
- **Ownership across the boundary must be explicit** — who allocates, who frees, and with which
function. Provide a matching `free` for every allocation you hand out.
- **Never let an exception cross an `extern "C"` boundary.** Catch everything at the edge and
convert to an error code.
- Bindings generated by a tool (`bindgen`, etc.) are regenerated, never hand-edited.
- On the consuming side, keep all `unsafe`/unmanaged interop in **one thin wrapper module**
that exposes a safe API — never scattered through call sites.
> [!WARNING]
> **Binding crates/packages that reach into a library's struct fields break when that library
> makes the struct opaque.** Pin to a binding version that matches the installed library
> generation, and note the constraint where someone will read it before bumping.
---
## 8. Testing & Debugging
- GoogleTest or Catch2; CTest integration so `ctest` runs the suite.
- **Regression test every fixed bug.**
- Debug builds keep frame pointers (`-fno-omit-frame-pointer`) so stack traces are usable.
- `-g` even in release builds; ship separate debug symbols rather than none.
- **Record dead ends in the project's notes**, especially for native debugging — the
investigation that ruled something out is expensive and will otherwise be repeated. Note
what did *not* work alongside what did.
- When a binary search through a packed/embedded blob finds nothing, that is not evidence the
content is absent — packed asset formats defeat naive string searches. Re-check without
anchoring before concluding.
+150
View File
@@ -0,0 +1,150 @@
---
name: csharp-dotnet
description: >-
Use this skill when writing, building, or releasing C# / .NET code — including nullable
reference types, warnings-as-errors, analyzer configuration, async/await rules, trimming
and single-file publishing, and multi-generation SDK compatibility shims.
---
# C# / .NET — Rules & Preferences
Extends [`coding-dev.md`](skills-public/coding-dev.md). Everything there applies; this adds .NET specifics.
---
## 1. Project Settings — Strict By Default
```xml
<PropertyGroup>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<AnalysisLevel>latest-all</AnalysisLevel>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
</PropertyGroup>
```
Put these in a `Directory.Build.props` at the repository root so every project inherits them
and none can quietly drift.
**Every build carries `-warnaserror`:**
```bash
dotnet build MySolution.sln -warnaserror
```
- **Nullable warnings are real bugs.** Never silence one with `!` (null-forgiving) — narrow
the type or fix the contract. `!` is an assertion, and an assertion you cannot prove is a
future `NullReferenceException`.
- Never blanket-disable an analyzer. When a rule is genuinely wrong for a piece of code,
scope the suppression to it — `#pragma warning disable CA1822` around the smallest region
that needs it (with a matching `restore`), or a targeted `[SuppressMessage]` attribute —
and always with a `Justification` / comment saying why. An `.editorconfig` entry turns the
rule off for every file it covers; use it only when the rule is wrong codebase-wide.
---
## 2. Async
- **`async`/`await` throughout; never `.Result` or `.Wait()`** — both deadlock in any context
with a synchronization context, and turn an exception into an `AggregateException`.
- `async Task`, never `async void`, except for genuine event handlers.
- `ConfigureAwait(false)` in library code; not needed in app code on modern hosts.
- Accept a `CancellationToken` on every async public method and actually honor it.
- `IAsyncEnumerable<T>` for streaming rather than materializing a whole collection.
- **Events over polling** — subscribe to the framework's event/observable API; don't run a
timer loop. (Global rule; it applies fully here.)
---
## 3. Errors, Disposal & Logging
- Specific exception types; never `catch (Exception)` without a reason and a log.
- **Never an empty catch block.**
- `using`/`await using` declarations for everything `IDisposable`/`IAsyncDisposable`.
Disposal is not optional for streams, HTTP handlers, DB contexts, or native handles.
- **One static `HttpClient` (or `IHttpClientFactory`)** — a per-request `HttpClient` exhausts
sockets under load.
- Structured logging (Serilog or `ILogger<T>`) with message templates, not string interpolation:
`logger.LogInformation("Loaded {Count} items", count)` — the template is what makes the log
queryable.
- **Never log secrets.**
---
## 4. Style
- File-scoped namespaces, `var` for obvious types, target-typed `new()`.
- `record` for immutable value types; `readonly struct` for small value types.
- Pattern matching and switch expressions over `if`/`else` ladders.
- `IReadOnlyList<T>` / `IReadOnlyDictionary<K,V>` in public signatures returning collections.
- `internal` by default; `public` only for genuine API surface.
- File and function size limits from the general rules apply — put the file limit in a test
rather than an MSBuild target (see [`coding-dev.md` §4](skills-public/coding-dev.md)).
- `dotnet format` before committing.
---
## 5. Publishing — Trimming & Single File
> [!IMPORTANT]
> **The trimmer only runs on a Release/trimmed/single-file publish.** A Debug build cannot
> reproduce the trimmer dropping a type that only reflection reaches. If your release pipeline
> ships trimmed binaries, the publish step is the **only** one that exercises the trimmer —
> so it must run before release, not after.
- Annotate reflection-reached types with `[DynamicallyAccessedMembers]` or a
`TrimmerRootDescriptor`, and **smoke-test the published binary**, not just the build.
- Enable `<IsTrimmable>` per library deliberately; blanket trimming of a plugin host breaks
plugin loading.
- **A release is a fixed, complete set of assets.** Define what that set is, and have the
release step verify it is complete rather than trusting whatever happens to be in the output
directory. A packaging tool that silently skips a step when a dependency is missing will
otherwise ship a release with a missing platform.
---
## 6. Multi-Generation SDK Compatibility
When targeting several generations of a host SDK whose API changed shape (a class becoming an
interface, a type being renamed):
- **All compatibility code lives in dedicated shim files** — e.g. `GlobalUsings.cs` and
`<Host>SdkShims.cs`. Never scatter `#if` directives through feature code.
- Select the target with an MSBuild property so the build is explicit about which generation
it produced:
```bash
dotnet build MyModules.csproj -c Release -p:HostTarget=stable
dotnet build MyModules.csproj -c Release -p:HostTarget=beta
```
- Document the differences between generations in the project's agent/contributor file —
which types changed, and which direction each shim adapts.
- Keep a local package feed for SDK versions not published to the public registry, and script
the packing step rather than doing it by hand.
---
## 7. Testing
- xUnit (or the project's existing framework); `FluentAssertions` for readable assertions.
- **Regression test every fixed bug.**
- Inject abstractions for filesystem, clock, and network so they can be faked — do not reach
for a static.
- **Never test mutations against real user profiles, real config directories, or live
services.** Fixtures and injected backends only.
- Test the **published** artifact for anything involving trimming, single-file packing, or
native dependencies — the assembly that passed unit tests is not the one you shipped.
---
## 8. Toolchain Notes
- When the SDK is not installed on the host (immutable OS, mismatched glibc), build inside the
project's designated container. Entering one resets the working directory — chain `cd` in the
same command string.
- Prefer the project's build script over hand-typed `dotnet` calls; it carries `-warnaserror`,
the correct action ordering, and the asset completeness checks — see
[`bash.md`](skills-public/code-languages/bash.md) for how that script should be structured.
- **Never stop, deploy to, or restart an application while the user has it running**, unless
they have said to.
+153
View File
@@ -0,0 +1,153 @@
---
name: go
description: >-
Use this skill when writing, building, linting, testing, or releasing Go — including
golangci-lint configuration, strict typing over map[string]interface{}, official SDK
preference, CGO-free static builds, context handling, and the build gate.
---
# Go — Rules & Preferences
Extends [`coding-dev.md`](skills-public/coding-dev.md). Everything there applies; this adds Go specifics.
---
## 1. The Gate
```bash
gofmt -l . # must print nothing
go vet ./...
golangci-lint run
go test -race ./...
go build ./...
```
Wire this into a single entry point (`make check`, `scripts/build.sh`) so it cannot be
partially run. **A skipped test fails the gate.** Add `govulncheck` for binary vulnerability
scanning on anything that ships.
---
## 2. Linting — Strict Set
`golangci-lint` with a deliberately broad selection, not the defaults:
```yaml
linters:
enable:
- staticcheck
- errcheck # every returned error is handled
- gosec # security findings
- noctx # no HTTP request without a context
- revive
- misspell
- unconvert
- bodyclose # every response body is closed
- goconst
- ineffassign
- nilerr
```
`errcheck` and `bodyclose` catch the two most common real Go bugs. As always: fix rather than
suppress, and when suppressing use a specific `//nolint:name // reason` with the reason.
---
## 3. Typing — Concrete, Not Dynamic
- **Strongly typed structs everywhere.** Avoid `map[string]interface{}` and reflection when a
struct can be defined — including for JSON, config, and API payloads.
- **Typed constants/enums**, not bare strings:
```go
type SyncStatus string
const (
StatusSynced SyncStatus = "synced"
StatusOutdated SyncStatus = "outdated"
StatusDirty SyncStatus = "dirty"
)
```
- **Concrete error types** with `errors.Is` / `errors.As`, not string comparison.
- Keep types shared with a frontend structurally in sync with that frontend's types; when both
exist, a change to one is incomplete without the other.
---
## 4. Errors
- **Every error is handled.** `errcheck` enforces it; do not `_ =` an error away without a
comment explaining why it cannot matter.
- Wrap with context: `fmt.Errorf("fetching %s: %w", name, err)`. Always `%w`, never `%v`, when
the caller might need to inspect it.
- Sentinel errors as package-level `var ErrNotFound = errors.New(...)`; typed errors when the
caller needs data from them.
- **`panic` only for genuinely unrecoverable programmer error**, never for expected runtime
conditions. A library never panics on input it was designed to receive.
- `defer` the cleanup immediately after acquiring the resource, always.
---
## 5. Context
- **Every function that does I/O takes `ctx context.Context` as its first parameter.**
- Never store a context in a struct field.
- Never pass `nil` — `context.TODO()` marks a genuine gap.
- Honor cancellation: select on `ctx.Done()` in loops, and pass ctx down to every HTTP call
(`noctx` enforces this).
- Set timeouts on outbound requests. A default `http.Client` has no timeout at all.
---
## 6. Dependencies — Official SDKs First
Follow the global reuse-first ordering. In Go, strongly prefer **official/first-party SDKs**
over third-party wrappers — they track API changes and handle auth and pagination correctly.
Choose **pure-Go implementations over CGO ones** whenever the trade is acceptable: a CGO
dependency turns a static single-binary build into a toolchain and glibc problem, and rules
out a `FROM scratch`/Alpine container image.
```bash
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o bin/app ./cmd/app
```
`go mod tidy` before committing, and commit `go.sum`.
---
## 7. Concurrency
- **Share memory by communicating.** Channels for handoff; `sync.Mutex` for guarding a struct's
own fields. Don't mix both for the same state.
- **`go test -race` is not optional** — it belongs in the gate, not in an occasional manual run.
- Every goroutine has a defined exit path. `errgroup.Group` for a set of related goroutines so
errors propagate and cancellation fans out.
- **Events over polling** — subscribe to a watch/notify API rather than looping on a ticker.
Where a ticker is genuinely necessary, make the interval configurable and document why.
- Never leak a goroutine on a channel nobody reads.
---
## 8. Structure
- `cmd/<binary>/` for entry points; `pkg/` or `internal/` for the actual code.
- `internal/` for anything that must not become public API — it is compiler-enforced.
- One package per concern; **package names are lowercase, single-word, no underscores**, and
never `util` or `common`.
- Keep the exported surface small. Unexported by default.
- Embed frontend/static assets with `//go:embed` for single-binary distribution — the frontend
itself follows [`js-typescript.md`](skills-public/code-languages/js-typescript.md).
- File and function size limits from the general rules apply — `funlen` in `golangci-lint`
covers functions; put the file limit in a test (see [`coding-dev.md` §4](skills-public/coding-dev.md)).
---
## 9. Testing
- Table-driven tests are the default idiom.
- `t.Parallel()` where tests are genuinely independent.
- `t.TempDir()` for filesystem work — it cleans itself up.
- `httptest` for HTTP handlers and for faking upstreams; do not hit real services in unit tests.
- **Regression test every fixed bug.**
- **Never run destructive operations against real accounts or user data during validation** —
temporary fixtures only.
+199
View File
@@ -0,0 +1,199 @@
---
name: js-typescript
description: >-
Use this skill when writing, building, linting, testing, or releasing JavaScript or
TypeScript — including Node, Bun, Deno, React, Next.js, and Astro projects. Covers the
TypeScript-over-JSDoc-over-JS preference, strict compiler settings, banned constructs,
React/hooks rules, resource lifecycle, and package/tooling choices.
---
# TypeScript / JavaScript — Rules & Preferences
Extends [`coding-dev.md`](skills-public/coding-dev.md). Everything there applies; this adds JS/TS specifics.
---
## 1. Language Choice — A Strict Preference Order
1. **TypeScript** — always the default for new code.
2. **JavaScript with JSDoc type annotations** — acceptable when a project genuinely cannot
adopt a build step, and `checkJs` is enabled so the annotations are actually enforced.
3. **Plain untyped JavaScript** — last resort only.
Migrating an existing JS project to TS is worth proposing; do not do it unasked (scope rule).
In a plain-JS codebase, **match it** — do not introduce a lone `.ts` file and a build step
into a project that has neither.
---
## 2. Strict Compiler Settings
```jsonc
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true,
"isolatedModules": true
}
}
```
**`tsc --noEmit` must pass with zero errors *and* zero warnings**, and it belongs in the build
gate alongside lint and tests — not as a thing someone runs occasionally in their editor.
> [!NOTE]
> Some established codebases deliberately relax `noImplicitAny` or `noUncheckedIndexedAccess`
> because their idioms depend on it. When **contributing upstream**, respect the project's
> `tsconfig.json` as-is. In **our own repos and non-upstreamed forks**, the settings above are
> the target — tightening them is fair game (as its own change, not smuggled into a feature).
---
## 3. Banned Constructs
| Never | Use instead |
| :--- | :--- |
| `any` | `unknown` + narrowing, or a real generic |
| `as unknown as T` | a type guard, or fix the source type |
| `@ts-ignore` | `@ts-expect-error` with a one-line reason, only when unavoidable |
| `!` non-null assertion | narrow with `if` or an early return |
| `enum` / `const enum` | `as const` object + literal union |
| `namespace` | ES modules |
| Boxed primitives (`Number`, `String`, `Object`, `{}`, `Function`) | the primitive types |
| `../../..` relative climbs | the configured path alias (`@/*`) |
| `JSON.parse(JSON.stringify(x))` | `structuredClone` or `lodash.cloneDeep` |
| `await new Promise(r => setTimeout(r, ms))` | a `sleep` helper from your utils |
| `console.log` in shipped code | a real logger with levels |
| Empty `catch {}` | handle it, or let it propagate |
- **`satisfies T` over `as T`** when the value already conforms. `as` is for genuine narrowing
only — it is an assertion that silences the checker, not a conversion.
- **Zero `any` in shared model types.** Frontend types that mirror a backend schema must stay
structurally in sync with it and fully typed.
---
## 4. Use What Exists
**Search the codebase's own `utils/`, `common/`, and shared modules before writing anything.**
Re-implementing an export that already exists is the single most common avoidable diff.
Never hand-roll: debounce/throttle (`lodash`), deep clone, clamp, date formatting
(`date-fns` / `dayjs` / project standard), URL parsing, regex escaping, UUID generation,
retry/backoff, or a semver comparator.
In a UI framework or client mod, **never recreate a component the host already provides** —
a hand-rolled `<button>` loses theming, focus rings, and accessibility.
---
## 5. React
- **Conditional rendering returns `null`**, never `undefined` or `false`.
- **List keys are stable identifiers.** Never `key={index}` on a mutable list.
- **Don't mirror external state into `useState`.** Subscribe to the store with its own hook.
- No `useMemo` / `useCallback` on cheap values that do not feed a dependency array. They are
not free, and they obscure what is actually expensive.
- **Hoist constant dependency arrays** to module scope (`const KEYS = [...] as const`). A fresh
array literal in the render body re-subscribes on every render.
- Prefer `<>…</>` fragments; `React.Fragment` only when the fragment needs a `key`.
- Type props with a named `interface`, not `React.FC`.
- Wrap anything injected into a host application's tree in an error boundary — an
uncaught throw in injected code takes down the host's render, not just yours.
- Components used as dynamic render targets must live at **module scope**. Defining a
component inside another component remounts its whole subtree every render.
---
## 6. Resource Lifecycle — Everything Started Must Be Stopped
Every `setInterval`, long `setTimeout`, `addEventListener`, subscription, `ResizeObserver`,
`IntersectionObserver`, `AbortController`, and `WebSocket` created in a setup path must be
torn down in the matching teardown path — `stop()`, `useEffect` cleanup, or `finally`.
> [!CAUTION]
> **`addEventListener` needs a named function reference.** An inline arrow passed to
> `addEventListener` cannot be matched by `removeEventListener`, so the listener leaks
> silently and accumulates across every mount.
```ts
let intervalId: ReturnType<typeof setInterval> | undefined;
function start() { intervalId = setInterval(tick, 30_000); }
function stop() { if (intervalId !== undefined) { clearInterval(intervalId); intervalId = undefined; } }
```
Attach an `AbortController` to every long-lived `fetch` and abort it in cleanup.
---
## 7. Structure & Naming
- **Components**: PascalCase `.tsx`.
- **Modules / utilities**: kebab-case `.ts`.
- **Imports**: prefer the configured alias (`@/…`) over relative climbs.
- Indentation and quote/semicolon style: **match the file you are editing**. Do not introduce
a new formatter or lint config into a project that already has one.
- **One file until it earns a split** — around 250 lines for a self-contained plugin/feature,
or when a second file is genuinely reusable (`utils.ts`, `types.ts`, `components/`).
Hard limits from the general rules still apply: ESLint's `max-lines` and
`max-lines-per-function` cover both natively, so prefer those over a separate test here.
---
## 8. Async & Data
- `async`/`await` over raw `.then()` chains. No mixing both in one flow.
- **Never leave a promise unawaited** unless you explicitly mean to, and mark it (`void p`)
when you do.
- `Promise.all` for independent work; sequential `await` only when there is a real dependency.
- **Subscribe to events; never poll.** (Global rule — it applies with full force here.)
- **Never store secrets in `localStorage` / `sessionStorage`.** Use the platform's real
storage API, and namespace your keys.
---
## 9. Node / Runtime Boundaries
- In any sandboxed or privileged-split architecture (Electron, browser extensions, plugin
hosts), the untrusted side must **never** import `fs`, `path`, `child_process`, `net`,
`http`, or `electron` directly. Route through the designated IPC/native boundary.
- Every IPC handler argument is **untrusted** — validate type, length, and content.
- Filesystem paths from the other side: `normalize()` then assert `startsWith(<allowed root>)`
before any read or write.
- Subprocesses: `spawn` with an argv **array**. Never `exec` a constructed string, never
`shell: true` with untrusted input.
- Network: allowlist by origin, cap response size, cap redirect depth.
- Errors crossing the boundary must be scrubbed — a raw `Error` leaks absolute paths.
---
## 10. Tooling & Tests
- Respect the project's runtime and package manager (`npm` / `pnpm` / `yarn` / `bun`) and its
lockfile. Do not switch package managers or add a second lockfile. Build and release scripts
around it follow [`bash.md`](skills-public/code-languages/bash.md).
- Test runner: whatever the project uses (`vitest`, `bun test`, `jest`). Tests live beside the
code as `*.test.ts(x)` unless the project says otherwise.
- **Add unit tests for new logic and route tests for new handlers.** Prioritize coverage on
auth, persistence, and anything that talks to a third party.
- Lint with the project's config (`eslint.config.mjs` and friends). **Do not add a new lint or
format config** to a repo that already has one.
- **Know the project's expected test baseline.** Some inherited suites are not all-green on a
clean checkout; judge regressions against the documented baseline rather than a raw run —
and if a baseline file exists, use it.
---
## 11. Framework Versions Move Fast
> [!WARNING]
> Next.js, Astro, React, and the major bundlers ship breaking changes regularly, and their
> current APIs frequently differ from what is in a model's training data. **Read the version's
> own docs in `node_modules/<pkg>/dist/docs/` or the project's pinned documentation before
> writing code against them.** Heed deprecation notices rather than pattern-matching from memory.
+152
View File
@@ -0,0 +1,152 @@
---
name: python
description: >-
Use this skill when writing, linting, type-checking, testing, or packaging Python —
including async code, CLI tools, bot cogs, and library packages. Covers strict typing with
mypy, ruff configuration, virtualenv discipline, async patterns, and packaging.
---
# Python — Rules & Preferences
Extends [`coding-dev.md`](skills-public/coding-dev.md). Everything there applies; this adds Python specifics.
For the surrounding build and deploy scripts, see [`bash.md`](skills-public/code-languages/bash.md).
---
## 1. The Gate
```bash
ruff format --check .
ruff check .
mypy --strict .
pytest
```
All four clean before anything is called done. Configure them in `pyproject.toml` so the
editor, the CLI, and CI all read the same settings.
---
## 2. Typing — Strict
**Every function gets annotated**, including `-> None`. Type hints are not optional
documentation; they are the checker's input.
```toml
[tool.mypy]
strict = true
warn_unreachable = true
warn_return_any = true
disallow_untyped_defs = true
```
- **Never bare `Any`.** Use a `Protocol`, a `TypeVar`, or `object` + narrowing.
- `X | None` over `Optional[X]`; `list[str]` over `List[str]` (modern syntax, no `typing` import).
- `TypedDict` or a dataclass for structured dicts — not `dict[str, Any]`.
- `Literal` and `Enum` for closed sets of values, not bare strings.
- `@dataclass(frozen=True, slots=True)` for value objects.
- Third-party library missing stubs: install `types-*` if it exists, otherwise a narrowly
scoped `# type: ignore[import-untyped]` with a comment — never a blanket module-level ignore.
---
## 3. Linting
`ruff` is the linter and formatter (it replaces flake8, isort, black, pyupgrade, and most
plugins). Enable a broad rule set rather than the default minimum:
```toml
[tool.ruff.lint]
select = ["E", "F", "W", "I", "N", "UP", "B", "A", "C4", "SIM", "RUF", "ASYNC", "S", "PTH"]
```
`S` (bandit security rules) and `ASYNC` are worth the noise. As always: fix, don't suppress —
and when you must suppress, use a specific code (`# noqa: S603`) with a reason.
---
## 4. Environments
- **Always a virtualenv or project-managed environment** (`uv`, `poetry`, `pdm`, `venv`).
Never install into the system interpreter, and never `pip install --user` as a workaround
for an immutable OS — use a container or a venv.
- `uv` is the preferred installer/resolver where free choice exists; it is dramatically faster
and lockfile-native.
- Commit the lockfile. Pin the Python version in `pyproject.toml` (`requires-python`).
- `pathlib.Path` over `os.path` everywhere (ruff's `PTH` rules enforce this).
---
## 5. Errors & Logging
- **Specific exceptions only.** `except Exception:` needs a reason; bare `except:` is never
acceptable (it catches `KeyboardInterrupt` and `SystemExit`).
- **Never swallow silently.** If you catch and continue, log at `warning` or above.
- `raise ... from err` to preserve the chain.
- Custom exception types deriving from a single package-level base, so callers can catch the
whole family.
- **`logging` module, never `print`,** in anything that isn't a CLI writing to stdout as its
actual output. One `logger = logging.getLogger(__name__)` per module, at module scope.
- **Never log secrets** — tokens, auth headers, message content, personal data.
---
## 6. Async
- `asyncio` with `async`/`await`. **Never block the event loop** — no `time.sleep`, no
synchronous `requests`, no blocking file I/O in a coroutine. Use `asyncio.to_thread` for
genuinely blocking calls.
- `httpx` (or the framework's own client) over `requests` in async code.
- **`asyncio.TaskGroup`** (3.11+) over bare `create_task` — it propagates exceptions and
cancels siblings instead of losing them to an unawaited task.
- **Every `create_task` result must be held in a reference**, or the garbage collector can
cancel it mid-flight.
- Cancellation is cooperative: clean up in `finally`, and never swallow `CancelledError`.
- **Events over polling.** Subscribe to the library's event/callback API; do not loop on a
timer. Where polling is genuinely unavoidable (no event source exists), use a **staggered
backoff** rather than a fixed interval, and say why polling was necessary.
---
## 7. Rate Limiting & External Services
When talking to a rate-limited API (a chat platform, a store, a tracker):
- **Funnel outbound calls through a single queue/worker** rather than calling from arbitrary
call sites. It is the only place a limit can actually be enforced.
- **Backoff with jitter** on retry; never a tight retry loop.
- Respect `Retry-After` and documented limits; assume undocumented ones exist.
- **Be extremely careful scraping real consumer services.** Conservative delays, realistic
headers, plenty of failsafes, and abort fast when a response looks unexpected. Assume you
are one mistake away from a ban.
---
## 8. Structure
- Package layout: `src/<package>/`, tests in `tests/`.
- **Shared behavior goes in a real shared module**, imported by each consumer — not
copy-pasted between sibling packages. When three components grow the same queue/retry/
formatting logic, that is the signal to extract it.
- Reusable, generic functionality (a format parser, a protocol client) becomes **its own
installable package** with its own repo and version, consumed as a dependency.
- Keep persistent state in the framework's own config/storage system so it survives restarts;
do not invent a parallel JSON file beside it.
- File and function size limits from the general rules apply. Ruff has **no line-based**
function-length rule — `PLR0915` (`too-many-statements`, default 50) and `C901`
(cyclomatic complexity) are the closest proxies and are worth enabling, but neither
measures lines. Put **both** the function-length and file-length limits in a pytest
case (see [`coding-dev.md` §4](skills-public/coding-dev.md)).
---
## 9. Testing
- `pytest`, with `pytest-asyncio` for coroutines.
- Fixtures over setup/teardown methods; `tmp_path` for anything touching the filesystem.
- **Regression test every fixed bug.**
- Mock at the network boundary (`respx`, `responses`), not by monkeypatching your own
internals — a test that patches the code under test proves nothing.
- **Never exercise destructive operations against real accounts or real data.** Temporary
fixtures only.
- `pytest -p no:randomly` only when order genuinely matters, and note why.
+195
View File
@@ -0,0 +1,195 @@
---
name: rust
description: >-
Use this skill when writing, building, linting, testing, or releasing Rust code — including
workspace lint configuration, clippy/fmt gates, error handling with Result/thiserror,
unsafe policy, async patterns, and cargo workspace/submodule layout.
---
# Rust — Rules & Preferences
Extends [`coding-dev.md`](skills-public/coding-dev.md). Everything there applies; this adds Rust specifics.
---
## 1. The Gate
Nothing is "done" until all of these are clean:
```bash
cargo fmt --all --check
cargo clippy --workspace --all-targets --all-features
cargo test --workspace
cargo doc --workspace --no-deps
cargo build --release
```
> [!IMPORTANT]
> **`--workspace` is not optional.** Without it only the root package is checked and every
> member crate's warnings stay invisible. This has hidden real warnings for weeks at a time.
> `--all-targets` additionally covers tests, benches, and examples — which are also code.
- `cargo fmt` before every commit. **No hand-formatting**, ever.
- `cargo doc` matters: a broken intra-doc link is a reference to something that no longer
exists, which is worth knowing about.
- Run the project's build script (`./scripts/build.sh`) if it has one — it wires these
together plus size checks and a smoke run.
---
## 2. Lints — Pedantic On, Configured In One Place
```toml
# Cargo.toml (workspace root)
[workspace.lints.clippy]
pedantic = { level = "warn", priority = -1 }
too_many_lines = "warn"
unwrap_used = "warn"
[workspace.lints.rust]
unsafe_code = "forbid"
```
```toml
# every member crate
[lints]
workspace = true
```
> [!CAUTION]
> **Lint config lives in `[workspace.lints]` only.** A package that repeats the configuration
> carries a second copy that drifts — which is how a binary once kept reading its own stale
> allows while the workspace's were being actively edited.
- **Never `#[allow]` on a whim** — but when a lint is genuinely wrong for a piece of code,
**allow it at the narrowest scope that covers it** (the expression, the function, the
module) with a comment saying why:
```rust
// The FFI callback signature is fixed by the C API; we cannot take fewer arguments.
#[allow(clippy::too_many_arguments)]
extern "C" fn on_event(/* … */) { }
```
- A **workspace-wide** allow in `[workspace.lints]` is for the rare case where the lint is
wrong for the *entire* codebase — not a shortcut for one stubborn call site. It silences
the rule in code nobody has written yet.
- **Re-test every `#[allow]` periodically.** If removing one produces no warning, delete it.
- `too_many_lines` is the mechanical enforcement of the 100-line function limit. A function
that trips it wants extracting, not an allow.
- Clippy has **no file-length lint**, so the 1000-line file limit belongs in a test — see
[`coding-dev.md` §4](skills-public/coding-dev.md) for the pattern. Put it in the workspace
root's `tests/` so one test covers every member crate, walking `src/` plus each
`crates/*/src`. `cargo test --workspace` then enforces it for free.
---
## 3. Errors & Panics
- **`Result` and `Option` everywhere.** `thiserror` for library error enums;
`anyhow` is acceptable at the application/binary boundary only.
- **No `unwrap()` or `expect()` outside tests and `main`.** Anything that can be absent at
runtime — a device, a file, a network peer, an env var — must be handled. Hardware and
network resources appear and disappear while the program runs.
- No `panic!` as flow control. A library never panics on input it was designed to receive.
- Never `?`-swallow context — attach it (`.context(...)` / a typed variant) so the error says
where it came from.
---
## 4. `unsafe`
**Forbidden by default**, enforced in `Cargo.toml` with `unsafe_code = "forbid"`.
If an FFI binding genuinely requires it, it goes behind a **safe wrapper in exactly one
module** (see [`c-cpp.md`](skills-public/code-languages/c-cpp.md) for the other side of that boundary), with a `// SAFETY:` comment on every block stating the invariant being upheld.
`unsafe` scattered through call sites is not acceptable.
---
## 5. Dependencies & Workspace Layout
Follow the global ordering (reuse → fork → write). In Rust specifically:
- Search crates.io and check maintenance status before adding. Prefer pure-Rust crates over
ones requiring a C toolchain when the difference is not material — a C dependency turns a
one-command build into a container requirement.
- **Pin precisely when it matters.** If you need an API that only exists on a git revision,
pin the revision and **write down why** — a future reader will otherwise "helpfully"
bump it to the crates.io release that lacks the API.
- **Reusable logic goes in its own crate**, published separately, with the application as a
consumer. The workspace layout that follows from this:
```
crates/<name>-core/ # all reusable logic, no UI, no framework deps
crates/<name>-proto/ # wire types & protocol parsing, dependency-light
src/ # or crates/<name>-ui — presentation only
```
- **Submodule hygiene**: commit and push inside the submodule crate first, then commit the
updated SHA pointer in the parent. Match each repo's own commit-trailer and identity
conventions — a public library repo and a private app repo often differ deliberately.
---
## 6. Async
- Tokio runtime with `futures` streams; `async_trait` where trait methods must be async.
- **Model new flows as streams or async methods**, matching what the codebase already does.
- **Never block in async context.** Use `spawn_blocking` for genuinely blocking work.
- Subscribe to event streams; never poll on a timer (see the global events-over-polling rule).
- Don't short-circuit a hand-written `Stream` impl that manually pumps backends — it usually
encodes ordering guarantees that are not obvious from the signature.
---
## 7. Feature Flags & `cfg`
- Feature flags declared in the root `Cargo.toml`.
- **Gate at module level, not per function** — per-function `cfg` leaves a trail of empty
stubs and makes the real platform differences hard to see.
- Adding a platform backend: declare the feature, create the gated module, and **log which
backend was selected at startup**. Silent backend selection is very hard to debug remotely.
- Use `build.rs`-exported configs for conditions that aren't plain features.
---
## 8. Style Details
- `unsafe`-free, `unwrap`-free, and formatted — see above.
- **No `pub` on anything that doesn't need to cross a module boundary.** Default to private
and widen deliberately.
- Max ~5 parameters; past that, pass a struct.
- Prefer `f32` for UI/geometry when the framework's native unit is `f32` — mixing in `f64`
produces a stream of casts and rounding inconsistencies.
- Derive rather than hand-implement (`Debug`, `Clone`, `Default`, `PartialEq`) unless the
hand implementation is doing something a derive cannot.
- `#[non_exhaustive]` on public enums/structs that are likely to grow.
---
## 9. Testing
- Unit tests inline in `#[cfg(test)] mod tests`; integration tests in `tests/`.
- `cargo test --workspace` — plus `--release` when timing or optimization matters to the
behavior under test.
- **Regression test every fixed bug.**
- A dummy/mock backend that exercises the full pipeline without real hardware is worth
building early — it is the only way most device code gets tested at all.
- Never point test output at real hardware. Route test signal to a virtual/null endpoint and
**verify the route resolved** — most tools silently fall back to the system default when
the requested target does not exist, which sends your test straight at the user.
---
## 10. Building in Containers
When the crate needs system headers the host does not have (audio, graphics, webkit),
build inside the project's designated container. Chain `cd` inside the same command string —
entering a container resets the working directory:
```bash
distrobox enter <container> -- bash -c "cd /path/to/project && cargo build --release"
```
Prefer the project's `scripts/build.sh` over a hand-typed invocation; it also handles job
throttling so a release build does not saturate the machine.
**Split so that as little as possible needs the container**: a `-core` crate with no GUI
dependencies builds and tests directly on the host, which is seconds instead of minutes.
+351
View File
@@ -0,0 +1,351 @@
---
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]
> **Whose rules win depends on where the code is going.** Always read a repository's
> `AGENTS.md`, `CLAUDE.md`, `AGENT.md`, `.rules`, or `CONTRIBUTING.md` first — but what you do
> on a conflict differs:
>
> | Situation | Precedence |
> | :--- | :--- |
> | **Contributing upstream** — a PR, a patch, or anything landing in someone else's repo | **The project's rules win.** Match their conventions exactly, even where they contradict this document. A PR that fights the house style gets rejected on style instead of reviewed on substance. |
> | **Local / private work** — our own repos, forks we don't upstream, private branches, local patches | **These rules win.** A project's conventions are a data point, not an obligation. Adopt the ones that are load-bearing (their build gate, their architecture contracts, anything that breaks if ignored) and apply our own preferences for the rest. |
>
> When in doubt about which mode you are in, **ask** — it changes the answer on strictness,
> file size, typing, and test policy all at once.
Per-language specifics live in [`code-languages/`](skills-public/code-languages/):
| Language | Rules |
| :--- | :--- |
| Rust | [`code-languages/rust.md`](skills-public/code-languages/rust.md) |
| TypeScript / JavaScript | [`code-languages/js-typescript.md`](skills-public/code-languages/js-typescript.md) |
| Bash / Shell | [`code-languages/bash.md`](skills-public/code-languages/bash.md) |
| Python | [`code-languages/python.md`](skills-public/code-languages/python.md) |
| Go | [`code-languages/go.md`](skills-public/code-languages/go.md) |
| C# / .NET | [`code-languages/csharp-dotnet.md`](skills-public/code-languages/csharp-dotnet.md) |
| C / C++ | [`code-languages/c-cpp.md`](skills-public/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 or warnings** — `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 a specific piece of code, **suppress it at the narrowest scope that covers the
problem — the line, the function, or the module — with a comment explaining why.** A
project-wide disable is a bigger hammer than it looks: it silences the rule everywhere,
including in code written later by someone who never saw the reason. Reach for it only when
the rule is genuinely wrong for the *whole* codebase, never as a shortcut for a stubborn
call site.
### 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.
- **Every suppression carries a comment saying why.** A bare `#[allow]` / `// eslint-disable`
/ `# noqa` with no reason is indistinguishable from someone giving up, and nobody will ever
dare remove it.
- **Name the specific rule**, never a blanket suppression: `# noqa: S603`, not `# noqa`;
`//nolint:errcheck`, not `//nolint`.
- **Re-test suppressions periodically.** Removing one 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 both mechanically, never by eye
A limit nobody checks is a suggestion. Pick the **highest** applicable option:
1. **A linter rule**, where one exists — the function limit is usually covered natively
(Clippy's `too_many_lines`, ESLint's `max-lines-per-function`, `golangci-lint`'s `funlen`).
A function that trips it wants extracting, not a suppression. Note that some linters only
offer *proxies* for length — Ruff's `PLR0915` counts statements, not lines — in which case
enable the proxy and put the real limit in a test.
2. **A test**, for anything the linter does not cover — file length almost always falls here.
A test is the best home for it: it runs with the suite everyone already runs, it needs no
extra tooling, it fails with a message naming the offending files, and it travels with the
repository rather than living in one machine's shell script.
3. **The build script**, only as a fallback when the project has no test suite to hang it on.
Whichever you use, it should **fail over the hard limit** and separately *name* anything past
the soft split point — so the seam gets found while splitting is still cheap, rather than at
the moment the hard limit finally trips.
```rust
#[test]
fn no_source_file_exceeds_limits() {
const HARD: usize = 1000;
const WARN: usize = 600;
let mut over = Vec::new();
for path in source_files() {
let lines = std::fs::read_to_string(&path).unwrap().lines().count();
if lines > HARD {
over.push(format!("{} — {lines} lines (limit {HARD})", path.display()));
} else if lines > WARN {
// Not a failure: surfaces the seam while splitting is still cheap.
eprintln!("note: {} is {lines} lines, approaching the {HARD} limit", path.display());
}
}
assert!(over.is_empty(), "files over the size limit:\n{}", over.join("\n"));
}
```
> [!NOTE]
> A test runs with the suite, not on every compile. If you genuinely need the check on every
> build, most toolchains have a pre-build hook for it (Cargo's `build.rs`, an MSBuild target,
> a bundler plugin) — but that cost is paid on every incremental build forever, and the suite
> is usually soon enough. Reach for the hook only if oversized files keep landing anyway.
- **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
Full version in [`cli-tooling.md`](skills-public/cli-tooling.md); the essentials:
- **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.
- **Standalone scripts live in a dedicated, version-controlled scripts directory**, symlinked
into `PATH` — never written directly into `~/.local/bin` or another `PATH` directory.
- **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.