diff --git a/README.md b/README.md index 817d338..8930364 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,36 @@ A collection of public, reusable agent skills, cheatsheets, and operational work ## 📂 Skills Index -*(Public skills will be added here as generic, open workflows)* +| Skill File | Scope / Topics Covered | +| :--- | :--- | +| **[`coding-dev.md`](./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. | + +### 🔤 Per-Language Rules ([`code-languages/`](./code-languages/)) + +| Language | Skill File | Highlights | +| :--- | :--- | :--- | +| **Rust** | [`rust.md`](./code-languages/rust.md) | `--workspace` clippy gate, `[workspace.lints]` single source of truth, no `unwrap`/`unsafe`, core-crate split | +| **TypeScript / JavaScript** | [`js-typescript.md`](./code-languages/js-typescript.md) | TS > JSDoc > plain JS, banned constructs table, React & hooks rules, resource lifecycle, sandbox boundaries | +| **Bash / Shell** | [`bash.md`](./code-languages/bash.md) | `set -euo pipefail`, shellcheck, no wait loops, destructive-command guards, build-gate structure | +| **Python** | [`python.md`](./code-languages/python.md) | `mypy --strict`, broad ruff rule set, asyncio task discipline, rate-limit queueing | +| **Go** | [`go.md`](./code-languages/go.md) | strict `golangci-lint` set, concrete types over `interface{}`, context everywhere, CGO-free static builds | +| **C# / .NET** | [`csharp-dotnet.md`](./code-languages/csharp-dotnet.md) | `TreatWarningsAsErrors`, nullable enabled, no `.Result`, trimming/single-file publish caveats, SDK shims | +| **C / C++** | [`c-cpp.md`](./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, ending with a **Related Skills** section that cross-links sibling documents. +3. **CLI discovery over enumeration** — document the *non-obvious* workflows, gotchas, and constraints. For the routine surface, point at ` --help` rather than transcribing flag lists that will go stale. +4. **No secrets inline** — reference the environment file that holds them (e.g. `~/.config/environment.d/…`), never the values themselves. +5. **Verify before documenting** — confirm hostnames, container names, and versions against the live system rather than carrying forward stale claims. --- diff --git a/code-languages/bash.md b/code-languages/bash.md new file mode 100644 index 0000000..06faedf --- /dev/null +++ b/code-languages/bash.md @@ -0,0 +1,172 @@ +--- +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`](../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 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 ; do sleep 2; done` or `while ! ; 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. +- 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 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. + +--- + +## 10. Related +- [`../coding-dev.md`](../coding-dev.md) — the general rules this extends. +- [`python.md`](./python.md) — where a shell script should have become a Python script. diff --git a/code-languages/c-cpp.md b/code-languages/c-cpp.md new file mode 100644 index 0000000..87f1540 --- /dev/null +++ b/code-languages/c-cpp.md @@ -0,0 +1,153 @@ +--- +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`](../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** — 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 `` (`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. + +--- + +## 9. Related +- [`../coding-dev.md`](../coding-dev.md) — the general rules this extends. +- [`rust.md`](./rust.md) — the preferred language for new native work, and the usual consumer + of these FFI boundaries. diff --git a/code-languages/csharp-dotnet.md b/code-languages/csharp-dotnet.md new file mode 100644 index 0000000..8e8474a --- /dev/null +++ b/code-languages/csharp-dotnet.md @@ -0,0 +1,151 @@ +--- +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`](../coding-dev.md). Everything there applies; this adds .NET specifics. + +--- + +## 1. Project Settings — Strict By Default + +```xml + + enable + true + true + latest-all + enable + latest + +``` + +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. If a rule is genuinely wrong for the codebase, configure + it once in `.editorconfig` with a comment. + +--- + +## 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` 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`) 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` / `IReadOnlyDictionary` in public signatures returning collections. +- `internal` by default; `public` only for genuine API surface. +- File and function size limits from the general rules apply. +- `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 `` 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 + `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. +- **Never stop, deploy to, or restart an application while the user has it running**, unless + they have said to. + +--- + +## 9. Related +- [`../coding-dev.md`](../coding-dev.md) — the general rules this extends. +- [`bash.md`](./bash.md) — for the release pipeline scripts around a .NET build. diff --git a/code-languages/go.md b/code-languages/go.md new file mode 100644 index 0000000..ccc6fb2 --- /dev/null +++ b/code-languages/go.md @@ -0,0 +1,157 @@ +--- +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`](../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//` 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. +- File and function size limits from the general rules apply. + +--- + +## 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. + +--- + +## 10. Related +- [`../coding-dev.md`](../coding-dev.md) — the general rules this extends. +- [`js-typescript.md`](./js-typescript.md) — for the embedded frontend in a Go service. diff --git a/code-languages/js-typescript.md b/code-languages/js-typescript.md new file mode 100644 index 0000000..79eeffa --- /dev/null +++ b/code-languages/js-typescript.md @@ -0,0 +1,202 @@ +--- +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`](../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 0 errors**, 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. Respect the project's `tsconfig.json`; the table above is +> the default for **new** projects. + +--- + +## 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 `