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

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

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

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

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

No files matched your search

+202
View File
@@ -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 `<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.
---
## 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.
- 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.
---
## 12. Related
- [`../coding-dev.md`](../coding-dev.md) — the general rules this extends.
- [`bash.md`](./bash.md) — for the build/release scripts around these projects.