pakhale 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,71 @@
1
+ # React / Next.js slop
2
+
3
+ Stack-specific tells. Run these alongside the main hunt, not instead of it.
4
+
5
+ ## Effects
6
+
7
+ `rg -n "useEffect" <changed files>` and triage every one the diff added. Reference:
8
+ https://react.dev/learn/you-might-not-need-an-effect
9
+
10
+ | The effect… | Verdict |
11
+ | --- | --- |
12
+ | sets state derived from props/state already in scope | delete — compute during render |
13
+ | runs in response to a click/submit/change | delete — move into the event handler |
14
+ | resets state when a prop changes | delete — pass a `key` instead |
15
+ | fetches, then sets state, with no cleanup | keep but fix — needs abort/ignore flag, or move to the framework's data layer |
16
+ | subscribes to the DOM, a store, a socket, an observer | keep — this is what effects are for |
17
+ | has an empty dep array but reads props/state | bug — stale closure |
18
+
19
+ Also: an effect whose dep array was hand-trimmed to silence the lint rule is a bug, not a
20
+ style choice. And every subscription/listener/interval/observer needs a cleanup return —
21
+ missing one is a leak.
22
+
23
+ ## Memo theater
24
+
25
+ `useMemo` / `useCallback` / `React.memo` added around cheap work is noise that costs more
26
+ than it saves. Tell: memoizing a string concat, an object literal of primitives, a `.filter`
27
+ over a handful of items.
28
+
29
+ Ask: what re-render does this actually prevent? Nothing measurable → remove it.
30
+
31
+ The inverse is a real finding: a memoized child that re-renders anyway because a parent
32
+ passes a fresh object/array/arrow prop each render. Fix the prop, not the child.
33
+
34
+ ## Client/server boundary (Next.js App Router)
35
+
36
+ - `'use client'` added to a component that renders no interactivity, no hooks, no browser
37
+ API — remove it and let it stay a server component.
38
+ - `'use client'` on a high-level layout or page, dragging its whole subtree client-side.
39
+ Push the directive down to the leaf that actually needs it.
40
+ - Server-only imports (`fs`, DB client, secret env) reachable from a client component —
41
+ that's a leak and a build error waiting to happen.
42
+ - `useEffect` + `fetch` for data a server component or route handler could fetch directly.
43
+ - Server Actions: no auth check, no input validation, or returning raw DB rows to the client.
44
+ - Data fetched but never revalidated/invalidated after a mutating action.
45
+
46
+ ## State
47
+
48
+ - State duplicating a prop (`useState(props.x)`) that then drifts — derive it instead.
49
+ - Multiple `useState` calls always set together — one object or reducer.
50
+ - State that's never read, or only read by the effect that set it.
51
+ - A ref used where state is required (value drives render) or state where a ref would do
52
+ (value never rendered).
53
+ - Context provider added for a value consumed by one component two levels down.
54
+
55
+ ## Rendering & markup
56
+
57
+ - Missing or index-based `key` in a list that can reorder or filter.
58
+ - Conditional rendering with `&&` on a number or a possibly-`""` value — renders `0`.
59
+ - Deeply nested ternaries in JSX; extract or early-return.
60
+ - A wrapper `<div>` added for no styling reason — use a fragment.
61
+ - New interactive behavior on a `<div>`/`<span>` instead of `<button>`/`<a>` — kills keyboard
62
+ and screen-reader access. Also check: focus moved into new modals/drawers and returned on close.
63
+ - Async UI with no loading and no error branch — a permanently blank region on failure.
64
+ - `dangerouslySetInnerHTML` on anything derived from user input.
65
+
66
+ ## Visual consistency
67
+
68
+ Before reading the new component's code, find the closest existing one and read *that*. Then
69
+ diff. Most UI slop is invisible in isolation and obvious side by side.
70
+
71
+ First understand what part of UI you are building, then look for existing similar sections, components, sub-components and patterns established in our app, and try to reuse the UI as much as possible and try to keep the typography, spacings, layouting everything consistent.
@@ -0,0 +1,62 @@
1
+ # TypeScript / Node slop
2
+
3
+ Stack-specific tells. Run these alongside the main hunt, not instead of it.
4
+
5
+ ## Escape hatches
6
+
7
+ `rg -n ": any|as any|as unknown as|@ts-ignore|@ts-expect-error|!\." <changed files>`
8
+
9
+ Every hit is a claim the author couldn't prove. For each: is the real type knowable here?
10
+ Usually yes — a generic, a narrowing check, or a proper return type on the function upstream.
11
+ `as any` on a test mock is fine; `as any` to make a call site compile is a bug in waiting.
12
+
13
+ Non-null `!` on something the diff didn't just check is the same category — it's an assertion
14
+ that the next reader will trust and the runtime won't.
15
+
16
+ ## Type slop
17
+
18
+ - A type declared and used once, inline-able at its single use site.
19
+ - A generic parameter appearing exactly once in the signature — it isn't doing anything.
20
+ - `interface` vs `type` inconsistent with the surrounding file.
21
+ - A hand-written type duplicating something already derived elsewhere — prefer
22
+ `z.infer`, `ReturnType`, `Awaited`, `typeof`, or the ORM's generated types over a
23
+ parallel definition that will drift.
24
+ - Optional fields (`x?: T`) everywhere because the shape wasn't decided — each one forces a
25
+ guard at every consumer.
26
+ - A union whose members are never discriminated at any call site.
27
+ - `Record<string, any>` where the keys are actually known.
28
+
29
+ ## Redundant narrowing
30
+
31
+ - `if (x)` where `x` is already `NonNullable` per its type.
32
+ - `typeof x === 'string'` on a `string`.
33
+ - `?.` chained on a value the line above proved non-null.
34
+ - `x ?? default` where `x`'s type excludes null/undefined.
35
+ - A type guard function that duplicates a check the caller just performed.
36
+
37
+ ## Async
38
+
39
+ - `await` inside a `for` loop over independent items — `Promise.all` (or a bounded pool).
40
+ - A promise created and never awaited; a floating `.then()` with no `.catch`.
41
+ - `try/catch` wrapping an entire function body to log-and-rethrow.
42
+ - `catch (e)` that swallows, or that returns a success-shaped value on failure.
43
+ - An `async` function with no `await` in it.
44
+ - No timeout / no abort signal on outbound `fetch`.
45
+ - Sequential DB round-trips in a loop (N+1) where one query with an `IN` clause works.
46
+
47
+ ## Structure & modules
48
+
49
+ - A barrel `index.ts` added purely to re-export one module.
50
+ - A `utils.ts` / `helpers.ts` grab-bag created for a single function — put it next to its use.
51
+ - A constant defined in the new file that already exists in the shared constants module —
52
+ `rg` the literal value, not just the name.
53
+ - Env vars read via `process.env.X` inline when the project has a validated config module.
54
+ - A new dependency doing what the stdlib or an existing dep already does. Check `package.json`
55
+ before accepting any new import.
56
+
57
+ ## Validation
58
+
59
+ - A Zod/Valibot schema and a TS interface maintained side by side — infer one from the other.
60
+ - External input (request body, query params, webhook payload, JSON file) typed by assertion
61
+ instead of parsed.
62
+ - `.parse()` in a request path with no error handling — a malformed body becomes a 500.
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: sitedrop
3
+ description: Deploy static HTML pages or sites to the user's personal sitedrop service for easy sharing, then open the returned URL. Use when the user asks to deploy, publish, share, or "put up" an HTML page, artifact, prototype, or static site — or after building one, when they want a shareable link use this one instead of default confiured for you.
4
+ ---
5
+
6
+ # sitedrop
7
+
8
+ Publish static files to a subdomain using the `sitedrop` CLI. The endpoint and password are already configured via `SITEDROP_ENDPOINT` and `SITEDROP_PASSWORD` env vars — never ask the user for them and never pass `-e`/`-p` manually.
9
+
10
+ ## Quick start
11
+
12
+ ```bash
13
+ sitedrop <path...> [-n <subdomain>]
14
+ ```
15
+
16
+ - A path may be a folder, a `.zip`, or a file; mix them freely.
17
+ - Folder/archive contents land at the site root (a single wrapper directory is stripped).
18
+ - A lone `.html` file is renamed to `index.html` automatically.
19
+ - `-n, --name <subdomain>` sets the site name; omitted → random subdomain.
20
+ - `-f, --force` publishes without an `index.html` (root will 404) — only use when intentional.
21
+
22
+ ## Workflow
23
+
24
+ 1. Make sure the site is self-contained (inline or relative assets — no paths outside the deployed folder).
25
+ 2. Pick a short, descriptive kebab-case name from the page's purpose (e.g. `-n perf-dashboard`). Omit `-n` if the user wants something unguessable/private.
26
+ 3. Deploy and capture output:
27
+ ```bash
28
+ out=$(sitedrop ./path -n my-page); echo "$out"
29
+ ```
30
+ 4. Extract the site URL from the output and open it in the browser:
31
+ ```bash
32
+ open "$(echo "$out" | grep -Eo 'https?://[^[:space:]]+' | tail -1)"
33
+ ```
34
+ 5. Tell the user the URL in your reply so they can copy/share it.
35
+
36
+ ## Notes
37
+
38
+ - Deploying a single HTML file works directly: `sitedrop page.html -n demo`.
39
+ - Redeploying with the same `-n` name updates the same site — reuse the name for iterations.
40
+ - If the deploy fails with an auth/endpoint error, report it and suggest the user verify `SITEDROP_ENDPOINT`/`SITEDROP_PASSWORD` in their shell profile — do not guess values.
@@ -0,0 +1,96 @@
1
+ ---
2
+ name: stackup
3
+ description: "Stack up a new or under-tooled side project with the current best tooling — package manager, type checker, linter, formatter, dead-code detection, tests, git hooks, and CI — picking one tool per job so nothing conflicts. Use when the user says 'stackup', 'stack up this project', 'set up tooling', 'bootstrap this repo', 'add linting/formatting/hooks', or when starting a new project or finding a repo with no lint/format/typecheck scripts."
4
+ metadata:
5
+ author: pratikpakhale
6
+ version: "1.0.0"
7
+ ---
8
+
9
+ # Stackup
10
+
11
+ Stack a project up with the current best tool for each job. **One tool per job** — the failure
12
+ mode this skill exists to prevent is two tools owning the same job and fighting each other
13
+ (ESLint next to oxlint, Prettier next to oxfmt, black next to ruff, husky next to lefthook).
14
+
15
+ ## Rules
16
+
17
+ - **Detect, don't ask.** Read the repo first — `package.json`, `pyproject.toml`, lockfiles,
18
+ existing configs. Install only what's missing; never re-pin what's already working.
19
+ - **Replace, don't stack.** If a job already has an owner you're replacing, remove the old
20
+ one in the same change — config file, dependency, and scripts. A half-migration is worse
21
+ than either tool alone.
22
+ - **Every tool gets a script and a hook.** A linter nobody runs is dead weight. Each tool
23
+ lands in `package.json` scripts (or `pyproject.toml`), then in the hook config, then in CI.
24
+ - **Pin the toolchain.** The versions in CI, in hooks, and on the machine must match.
25
+ - **Green before you leave.** Run the full check once at the end. Config that has never been
26
+ executed is not configured.
27
+
28
+ ## The stack
29
+
30
+ | Job | Tool | Never also install |
31
+ | --- | --- | --- |
32
+ | Runtime, package manager, workspaces | `bun` | npm/yarn/pnpm, `tsx`, `ts-node` |
33
+ | Type checking | `typescript@7` (`tsc`) | `tsgo`, `@typescript/native-preview` |
34
+ | Linting | `oxlint` + `oxlint-tsgolint` | ESLint, Biome |
35
+ | Formatting | `oxfmt` | Prettier, Biome, dprint |
36
+ | Unused files/exports/deps | `knip` | depcheck, ts-prune |
37
+ | Tests | `bun test` (`vitest` for DOM) | jest, mocha |
38
+ | Python everything | `uv` | pip, poetry, pyenv, pipx |
39
+ | Python lint + format | `ruff` | black, isort, flake8, autopep8 |
40
+ | Python type checking | `ty` | mypy, pyright (see reference) |
41
+ | Git hooks | `lefthook` | husky, lint-staged, pre-commit, prek |
42
+ | Secret scanning | `gitleaks` | — |
43
+ | Toolchain versions | `mise` | nvm, pyenv, asdf |
44
+
45
+ `typescript@7` is the Go compiler, shipped stable July 2026 — it is the `typescript` package
46
+ and the `tsc` binary. `tsgo` and `@typescript/native-preview` were the preview channel and are
47
+ obsolete; if you find them in a repo, migrate off.
48
+
49
+ ## Order of operations
50
+
51
+ Do these in order — later steps depend on scripts the earlier ones create.
52
+
53
+ 1. **Pin the toolchain** — `mise.toml` at the repo root (see [references/hooks-ci.md](references/hooks-ci.md)).
54
+ 2. **Install per-stack deps and configs** — read the reference for each stack present:
55
+
56
+ | Stack | Read |
57
+ | --- | --- |
58
+ | `.ts`, `.js`, Node, Bun | [references/typescript.md](references/typescript.md) |
59
+ | `.tsx`, React, Next.js | [references/react.md](references/react.md) |
60
+ | `.py` | [references/python.md](references/python.md) |
61
+
62
+ 3. **Write the scripts** — every tool reachable by name, plus one aggregate `check`.
63
+ 4. **Wire hooks and CI** — [references/hooks-ci.md](references/hooks-ci.md).
64
+ 5. **Run `bun run check` and fix the fallout.** Expect the first run to be noisy on an
65
+ existing codebase: auto-fix what's mechanical, and for the rest either fix it or
66
+ downgrade the specific rule in config with a one-line comment saying why. Do not
67
+ blanket-disable a plugin to get to green.
68
+
69
+ ## Fast vs. whole-project
70
+
71
+ This split matters everywhere — hooks, CI, and which script you reach for.
72
+
73
+ - **Per-file, milliseconds** — formatting, non-type-aware lint, secret scan. Runs on staged
74
+ files at `pre-commit`.
75
+ - **Whole-program, seconds** — `tsc`, type-aware lint, `ty`, `knip`, tests. These cannot be
76
+ scoped to staged files without being wrong. Runs at `pre-push` and in CI.
77
+
78
+ Putting `tsc` in `pre-commit` is the most common way to make a team disable hooks entirely.
79
+
80
+ ## Also worth setting up
81
+
82
+ Raise these with the user rather than installing silently — they are project-shaped, not
83
+ universal:
84
+
85
+ - **Conventional commits** — `commitlint` on the `commit-msg` hook. Cheap, and it's the
86
+ commit convention already in use.
87
+ - **`AGENTS.md`** at the repo root with a `CLAUDE.md` symlink to it — the project's own
88
+ instructions for coding agents. Worth writing on day one, while the architecture is a
89
+ decision rather than an archaeology problem.
90
+ - **Env var validation** — parse `process.env` once at startup through a schema
91
+ (`arktype` or `zod`) and export the typed object. Only for projects with real config.
92
+ - **Publishing** (libraries only) — `tsdown` to build, `publint` and
93
+ `@arethetypeswrong/cli` to verify the published shape.
94
+ - **Dependency freshness** — `bun outdated` / `bun audit`, `uv lock --upgrade`. Renovate
95
+ only if the project will outlive your attention span.
96
+ - **`.editorconfig`** — one file, stops editors from re-indenting what `oxfmt` formatted.
@@ -0,0 +1,132 @@
1
+ # Hooks, CI, and toolchain pinning
2
+
3
+ ## Toolchain versions — mise
4
+
5
+ One file pins every language runtime, so the machine, the hooks, and CI agree. This replaces
6
+ nvm, pyenv, and asdf, and it handles a polyglot repo that `.nvmrc` alone can't.
7
+
8
+ ```toml
9
+ # mise.toml
10
+ [tools]
11
+ bun = "1.3"
12
+ python = "3.13"
13
+ ```
14
+
15
+ `mise install` on a fresh clone. CI uses `jdx/mise-action`, so the pin is honoured there too
16
+ rather than being re-declared in the workflow.
17
+
18
+ ## Git hooks — lefthook
19
+
20
+ A single Go binary, one YAML file, parallel execution, and no dependency on Node or Python —
21
+ which is what makes it the right pick for a repo with both. It replaces husky **and**
22
+ lint-staged; `pre-commit`/`prek` cover the same ground but drag in the Python toolchain and
23
+ run sequentially. Don't install two hook managers — they fight over the same
24
+ `.git/hooks` entries and the loser silently stops running.
25
+
26
+ ```bash
27
+ bun add -D lefthook && bunx lefthook install
28
+ ```
29
+
30
+ `lefthook install` writes `.git/hooks`. It has to be re-run on a fresh clone — put it in a
31
+ `prepare` script so `bun install` does it.
32
+
33
+ ```yaml
34
+ # lefthook.yml
35
+ pre-commit:
36
+ parallel: true
37
+ jobs:
38
+ - name: format
39
+ glob: "*.{ts,tsx,js,jsx,json,css,md}"
40
+ run: bunx oxfmt {staged_files}
41
+ stage_fixed: true
42
+
43
+ - name: lint
44
+ glob: "*.{ts,tsx,js,jsx}"
45
+ run: bunx oxlint --fix {staged_files}
46
+ stage_fixed: true
47
+
48
+ - name: python
49
+ glob: "*.py"
50
+ run: uv run ruff check --fix {staged_files} && uv run ruff format {staged_files}
51
+ stage_fixed: true
52
+
53
+ - name: secrets
54
+ run: gitleaks git --staged --redact --no-banner
55
+
56
+ pre-push:
57
+ parallel: true
58
+ jobs:
59
+ - name: check
60
+ run: bun run check
61
+ - name: python
62
+ run: uv run ty check && uv run pytest
63
+ - name: test
64
+ run: bun test
65
+
66
+ commit-msg:
67
+ jobs:
68
+ - name: conventional
69
+ run: bunx commitlint --edit {1}
70
+ ```
71
+
72
+ Why the split: `pre-commit` holds only per-file work that finishes in milliseconds, and
73
+ `stage_fixed: true` re-stages what the fixers rewrote so the commit contains the formatted
74
+ version. `tsc`, type-aware lint, `ty`, `knip`, and tests need the whole program — they can't
75
+ be scoped to staged files without being wrong, so they run at `pre-push`.
76
+
77
+ A `pre-commit` hook that takes ten seconds is a hook that gets bypassed with `--no-verify`
78
+ within a week. Keep it fast and the rest at push.
79
+
80
+ ## Secret scanning — gitleaks
81
+
82
+ `brew install gitleaks`. The `git --staged` invocation above scans only what's being
83
+ committed, so it costs nothing per commit. This is the one hook worth keeping even in a repo
84
+ where you skip everything else — a leaked key in git history is not fixable by a later commit.
85
+
86
+ Add a `.gitleaksignore` for the false positives (test fixtures, example configs) rather than
87
+ loosening the rules.
88
+
89
+ ## Conventional commits — commitlint
90
+
91
+ ```bash
92
+ bun add -D @commitlint/cli @commitlint/config-conventional
93
+ ```
94
+
95
+ ```js
96
+ // commitlint.config.js
97
+ export default { extends: ['@commitlint/config-conventional'] }
98
+ ```
99
+
100
+ Optional, and worth it on anything that will get a changelog. It's the one hook that rejects
101
+ rather than fixes, so it's also the most annoying — skip it on scratch repos.
102
+
103
+ ## CI — run the same commands
104
+
105
+ The workflow must not invent its own commands. It runs what the hooks run, so passing locally
106
+ means passing in CI.
107
+
108
+ ```yaml
109
+ # .github/workflows/ci.yml
110
+ name: ci
111
+ on: [push, pull_request]
112
+
113
+ jobs:
114
+ check:
115
+ runs-on: ubuntu-latest
116
+ steps:
117
+ - uses: actions/checkout@v5
118
+ with: { fetch-depth: 0 } # gitleaks needs history
119
+ - uses: jdx/mise-action@v3
120
+ - run: bun install --frozen-lockfile
121
+ - run: bun run check
122
+ - run: bun test
123
+ - run: uv sync --frozen && uv run ty check && uv run pytest
124
+ - run: gitleaks git --redact --no-banner
125
+ ```
126
+
127
+ `--frozen-lockfile` and `uv sync --frozen` are what make CI meaningful: an out-of-date
128
+ lockfile fails the build instead of being quietly resolved around, which is exactly the
129
+ drift that makes "works on my machine" happen.
130
+
131
+ Drop the `uv` line for a JS-only repo and the `bun` lines for a Python-only one — but keep
132
+ both `check` and the lockfile flags.
@@ -0,0 +1,81 @@
1
+ # Python
2
+
3
+ ## uv owns everything
4
+
5
+ `uv` replaces pip, pip-tools, pipx, poetry, virtualenv, and pyenv. There is no reason to
6
+ install any of them alongside it, and no reason to activate a venv by hand — `uv run` does it.
7
+
8
+ ```bash
9
+ uv init # new project: pyproject.toml + .python-version + .venv
10
+ uv add --dev ruff ty pytest
11
+ uv run pytest # runs in the project env, syncing it first if stale
12
+ uvx ruff check # one-off tool run, no project install
13
+ ```
14
+
15
+ `uv.lock` is committed. `uv sync --frozen` in CI, so a stale lockfile fails the build instead
16
+ of being silently resolved around.
17
+
18
+ Single-file scripts get inline dependencies (PEP 723) rather than a project — `uv run
19
+ script.py` reads the header and builds the env:
20
+
21
+ ```python
22
+ # /// script
23
+ # requires-python = ">=3.13"
24
+ # dependencies = ["httpx"]
25
+ # ///
26
+ ```
27
+
28
+ ## ruff owns lint and format
29
+
30
+ One tool, one config, no ordering conflicts. black, isort, flake8, pyupgrade, pydocstyle, and
31
+ autopep8 are all subsumed — if any are in the project, they're being removed.
32
+
33
+ ```toml
34
+ # pyproject.toml
35
+ [tool.ruff]
36
+ line-length = 100
37
+
38
+ [tool.ruff.lint]
39
+ select = ["E", "F", "W", "I", "N", "UP", "B", "SIM", "RUF", "ASYNC", "S", "PTH"]
40
+ ignore = ["E501"] # the formatter owns line length
41
+
42
+ [tool.ruff.lint.per-file-ignores]
43
+ "tests/**" = ["S101"] # assert is the point in tests
44
+ ```
45
+
46
+ `E501` is ignored because `ruff format` already wraps — leaving it on means the linter
47
+ complains about lines the formatter deliberately left long (URLs, strings).
48
+
49
+ ## ty for type checking
50
+
51
+ Astral's checker, Rust, 10–100× faster than mypy and pyright, and it shares uv's and ruff's
52
+ config surface.
53
+
54
+ ```bash
55
+ uv run ty check
56
+ ```
57
+
58
+ It's **beta** as of 2026, targeting 1.0. That's fine for a side project — fast feedback beats
59
+ completeness — but know the tradeoff: expect occasional missing features and false positives
60
+ on heavy metaprogramming. If ty chokes on a specific project, `basedpyright` is the fallback
61
+ (strictly better defaults than pyright, same engine lineage). Swap it in wholesale; don't run
62
+ two checkers.
63
+
64
+ Annotate as you go rather than retrofitting. A checker on unannotated code reports almost
65
+ nothing and gives false confidence.
66
+
67
+ ## Scripts
68
+
69
+ Python has no `package.json` scripts. Either define them in `pyproject.toml` under a task
70
+ runner, or — simpler for a side project, and what the hooks call anyway — keep them as plain
71
+ commands in the lefthook config and the README:
72
+
73
+ ```bash
74
+ uv run ruff check --fix .
75
+ uv run ruff format .
76
+ uv run ty check
77
+ uv run pytest
78
+ ```
79
+
80
+ In a mixed JS/Python repo, put these behind `bun run check` too, so one command still covers
81
+ the whole repo.
@@ -0,0 +1,79 @@
1
+ # React
2
+
3
+ Everything in [typescript.md](typescript.md) applies first. This adds what's React-specific.
4
+
5
+ ## Linting — no ESLint needed
6
+
7
+ oxlint's `react` plugin covers `eslint-plugin-react`, `eslint-plugin-react-hooks`,
8
+ `eslint-plugin-react-refresh`, **and** the React Compiler rules. `jsx-a11y` is a separate
9
+ plugin. So a React project needs no ESLint and no `eslint-plugin-*` dependencies at all —
10
+ if you find them, they're being replaced, not kept alongside.
11
+
12
+ ```json
13
+ {
14
+ "plugins": ["typescript", "unicorn", "oxc", "import", "promise", "react", "jsx-a11y"],
15
+ "categories": { "correctness": "error", "suspicious": "warn", "perf": "warn" }
16
+ }
17
+ ```
18
+
19
+ Note that listing `plugins` overwrites the defaults — `typescript`, `unicorn`, and `oxc` are
20
+ in the list because they'd otherwise be dropped.
21
+
22
+ ## The three dev tools
23
+
24
+ All three are development-only and none of them belong in a production bundle. They do
25
+ different jobs; installing one is not a reason to skip the others.
26
+
27
+ ### react-doctor — static audit, runs in CI
28
+
29
+ Scans the codebase and returns a health score with file-level diagnostics across ~60 rules:
30
+ state and effects, performance, architecture, security, bundle size, accessibility, dead code.
31
+ It's a CLI, so it's the only one of the three that can gate a build.
32
+
33
+ ```bash
34
+ bunx react-doctor@latest
35
+ ```
36
+
37
+ Wire it as a `doctor` script. Treat the score as a trend line, not a gate — it moves when
38
+ rules change upstream. If you do gate on it, gate on "no new *errors*", never on the number.
39
+
40
+ ### react-scan — render performance, runs in the browser
41
+
42
+ Highlights components that re-render and why, live in the running app. This is the tool for
43
+ "the page feels slow" — it shows you the actual offending component instead of you guessing
44
+ at `memo` placement.
45
+
46
+ ```ts
47
+ // entry file, before React renders
48
+ if (import.meta.env.DEV) import('react-scan').then(({ scan }) => scan())
49
+ ```
50
+
51
+ Import it **before** React, or it can't instrument the renderer. There's also
52
+ `bunx react-scan@latest <url>` to point it at a running app without touching the code.
53
+
54
+ ### react-grab — hand UI to the agent
55
+
56
+ Hover an element in the running app, `⌘C`, and the component stack with source locations
57
+ (`<a> in LoginForm (at components/login-form.tsx:46:19)`) lands on the clipboard, ready to
58
+ paste into an agent prompt. It's the fastest way to answer "which file is this button in".
59
+
60
+ ```bash
61
+ bunx grab@latest init
62
+ ```
63
+
64
+ The init command wires it for the detected framework. Whatever it writes, verify the setup is
65
+ dev-guarded — `import.meta.env.DEV` for Vite, `process.env.NODE_ENV === "development"` for
66
+ Next.js.
67
+
68
+ ## React Compiler
69
+
70
+ If the project is on the compiler, oxlint's react plugin already reports compiler rule
71
+ violations, so you don't need a separate healthcheck pass in CI. If it isn't on the compiler,
72
+ enabling it is usually a better performance investment than hand-placed `memo`/`useMemo` —
73
+ raise it, but don't turn it on unasked; it changes runtime behavior.
74
+
75
+ ## Testing
76
+
77
+ Component tests need a DOM, which `bun test` can provide via `happy-dom`. That's enough for
78
+ render-and-assert. Use `vitest` with browser mode instead when the tests need real layout,
79
+ real events, or visual assertions — that's the one case where the extra dependency pays.
@@ -0,0 +1,114 @@
1
+ # TypeScript / JavaScript
2
+
3
+ ## Install
4
+
5
+ ```bash
6
+ bun add -D typescript oxlint oxlint-tsgolint oxfmt knip
7
+ ```
8
+
9
+ `knip` ships a `knip-bun` binary — use that one in a Bun project, it resolves Bun's module
10
+ graph correctly.
11
+
12
+ ## `tsconfig.json`
13
+
14
+ TypeScript 7 changed the defaults. `strict` is now **on** by default, and `types` defaults to
15
+ `[]` instead of hoovering up every `@types` package in `node_modules` — list what you actually
16
+ need. Gone entirely: `target: es5`, AMD/UMD/SystemJS modules, `baseUrl`, classic module
17
+ resolution. If a repo's config uses any of them, it needs migrating, not copying.
18
+
19
+ ```json
20
+ {
21
+ "compilerOptions": {
22
+ "lib": ["ESNext", "DOM"],
23
+ "module": "preserve",
24
+ "moduleResolution": "bundler",
25
+ "target": "ESNext",
26
+ "types": ["bun"],
27
+ "noEmit": true,
28
+ "verbatimModuleSyntax": true,
29
+ "noUncheckedIndexedAccess": true,
30
+ "noImplicitOverride": true,
31
+ "erasableSyntaxOnly": true
32
+ },
33
+ "include": ["**/*.ts"],
34
+ "exclude": ["node_modules", "dist"]
35
+ }
36
+ ```
37
+
38
+ `noUncheckedIndexedAccess` is the one strict-adjacent flag `strict` doesn't turn on and the
39
+ one that catches the most real bugs. `erasableSyntaxOnly` keeps the code runnable by anything
40
+ that only strips types — no `enum`, no parameter properties, no namespaces.
41
+
42
+ ## `.oxlintrc.json`
43
+
44
+ `plugins` **overwrites** the default set, so list every plugin you want, including the ones
45
+ that were on by default (`typescript`, `unicorn`, `oxc`).
46
+
47
+ ```json
48
+ {
49
+ "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
50
+ "plugins": ["typescript", "unicorn", "oxc", "import", "promise", "node"],
51
+ "categories": { "correctness": "error", "suspicious": "warn", "perf": "warn" },
52
+ "options": { "typeAware": true },
53
+ "ignorePatterns": ["dist", "node_modules"]
54
+ }
55
+ ```
56
+
57
+ Type-aware linting is stable and covers 59 of typescript-eslint's 61 type-aware rules. It
58
+ needs `oxlint-tsgolint` installed, and it is the slow path — see the fast/whole-project split
59
+ in `SKILL.md`. On a very large codebase watch memory; if it thrashes, keep `typeAware` out of
60
+ the config and pass `--type-aware` only in the CI script.
61
+
62
+ ## Formatting
63
+
64
+ `oxfmt` is Prettier-compatible in output and needs no config for the default style. It is
65
+ beta as of 2026, and it still delegates Markdown to Prettier internally — fine, but if a
66
+ project is mostly Markdown, or `oxfmt` mangles something, Prettier alone is the fallback.
67
+ **Never run both** — pick one and delete the other's config.
68
+
69
+ ## Scripts
70
+
71
+ ```json
72
+ {
73
+ "scripts": {
74
+ "typecheck": "tsc --noEmit",
75
+ "lint": "oxlint",
76
+ "lint:fix": "oxlint --fix",
77
+ "format": "oxfmt",
78
+ "format:check": "oxfmt --check",
79
+ "knip": "knip-bun",
80
+ "test": "bun test",
81
+ "check": "bun run typecheck && bun run lint && bun run format:check && bun run knip"
82
+ }
83
+ }
84
+ ```
85
+
86
+ `check` is the single command hooks, CI, and the user all run. Keep it that way — the moment
87
+ CI runs something the local `check` doesn't, the hooks stop being trustworthy.
88
+
89
+ ## Testing
90
+
91
+ `bun test` is the default: no dependency, no config, Jest-compatible API, and it runs
92
+ TypeScript directly. Reach for `vitest` only when you need what Bun's runner doesn't have —
93
+ component/DOM testing, browser mode, or a rich mocking surface. Adding `vitest` for a project
94
+ of pure logic tests is a dependency you'll maintain for nothing.
95
+
96
+ ## Libraries
97
+
98
+ Only when the package is published:
99
+
100
+ ```bash
101
+ bun add -D tsdown publint @arethetypeswrong/cli
102
+ ```
103
+
104
+ `tsdown` builds and emits declarations; `publint` checks the package manifest, and
105
+ `attw --pack .` checks that the types actually resolve for consumers under every module
106
+ resolution mode. Both belong in the `check` script for a library — a broken `exports` map is
107
+ invisible until someone else installs it.
108
+
109
+ ## Knip
110
+
111
+ The first `knip` run on an existing codebase reports a lot, and much of it is intentional
112
+ public API rather than dead code. Triage it: real removals go, deliberate entry points go in
113
+ `knip.json` under `entry`/`ignore` — with the reason in a comment. Don't wire it into a
114
+ blocking hook until it reports clean once.