Compare commits
7
Commits
ba95f171b2
...
3171595940
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3171595940 | ||
|
|
32b47b075b | ||
|
|
5d96e69d2b | ||
|
|
58525056a1 | ||
|
|
72834167b6 | ||
|
|
9571eb52d2 | ||
|
|
57968904bf |
No files matched your search
@@ -6,7 +6,51 @@ A collection of public, reusable agent skills, cheatsheets, and operational work
|
|||||||
|
|
||||||
## 📂 Skills Index
|
## 📂 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
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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
@@ -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.
|
||||||
Reference in new issue
Block a user