ts-reviewer 1.0.0 → 1.2.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.
- package/README.md +48 -16
- package/package.json +1 -1
- package/ts-reviewer/SKILL.md +124 -36
- package/ts-reviewer/references/architecture.md +158 -0
- package/ts-reviewer/references/async-patterns.md +50 -8
- package/ts-reviewer/references/boundary-validation.md +56 -0
- package/ts-reviewer/references/code-quality.md +45 -7
- package/ts-reviewer/references/dependency-hygiene.md +47 -0
- package/ts-reviewer/references/error-handling.md +49 -0
- package/ts-reviewer/references/fix-workflow.md +45 -5
- package/ts-reviewer/references/modernization.md +80 -27
- package/ts-reviewer/references/security.md +45 -2
- package/ts-reviewer/references/tsconfig.md +35 -2
- package/ts-reviewer/references/type-safety.md +48 -1
package/README.md
CHANGED
|
@@ -14,16 +14,17 @@ Three modes, one skill:
|
|
|
14
14
|
| **fix** | Reads the report and applies fixes file-by-file with tsc/lint/test verification |
|
|
15
15
|
| **auto** | Runs scan, asks you to confirm, fixes everything, deletes the report if clean |
|
|
16
16
|
|
|
17
|
-
The review covers six domains, each with its own detailed checklist:
|
|
18
|
-
|
|
19
|
-
| Domain | Examples |
|
|
20
|
-
|
|
21
|
-
| **Type Safety** | `any` abuse, unsafe casts, non-null assertions, missing exhaustive checks |
|
|
22
|
-
| **Security** | Injection, prototype pollution, ReDoS, path traversal, hardcoded secrets |
|
|
23
|
-
| **Async Patterns** | Floating promises, race conditions, missing error propagation, `forEach(async...)` |
|
|
24
|
-
| **Modernization** | `enum` → `as const`, missing `satisfies`, `using` keyword, `import type` |
|
|
25
|
-
| **Code Quality** | Dead code, complexity, duplication, hacky patterns, error handling |
|
|
26
|
-
| **Config** | tsconfig.json strict flags, module resolution, deprecated options |
|
|
17
|
+
The review covers six domains by default, each with its own detailed checklist. Add `--arch` or `--full` to include architecture analysis:
|
|
18
|
+
|
|
19
|
+
| Domain | Examples | Default |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| **Type Safety** | `any` abuse, unsafe casts, non-null assertions, missing exhaustive checks | ✓ |
|
|
22
|
+
| **Security** | Injection, prototype pollution, ReDoS, path traversal, hardcoded secrets | ✓ |
|
|
23
|
+
| **Async Patterns** | Floating promises, race conditions, missing error propagation, `forEach(async...)` | ✓ |
|
|
24
|
+
| **Modernization** | `enum` → `as const`, missing `satisfies`, `using` keyword, `import type` | ✓ |
|
|
25
|
+
| **Code Quality** | Dead code, complexity, duplication, hacky patterns, error handling | ✓ |
|
|
26
|
+
| **Config** | tsconfig.json strict flags, module resolution, deprecated options | ✓ |
|
|
27
|
+
| **Architecture** | Shallow modules, scattered concepts, tight coupling, dependency seams, testability | `--arch` / `--full` |
|
|
27
28
|
|
|
28
29
|
## Installation
|
|
29
30
|
|
|
@@ -75,7 +76,29 @@ Find issues in this project
|
|
|
75
76
|
Audit the codebase for security and type safety problems
|
|
76
77
|
```
|
|
77
78
|
|
|
78
|
-
Claude will analyze the project and write a report to `code-smells.md` in the project root
|
|
79
|
+
Claude will analyze the project and write a report to `code-smells.md` in the project root.
|
|
80
|
+
|
|
81
|
+
#### Domain flags
|
|
82
|
+
|
|
83
|
+
By default, only the six core domains run. Use flags to control which domains are active:
|
|
84
|
+
|
|
85
|
+
| Flag | What runs |
|
|
86
|
+
|---|---|
|
|
87
|
+
| *(none)* | Type Safety, Security, Async, Modernization, Code Quality, Config |
|
|
88
|
+
| `--arch` | Architecture only (shallow modules, coupling, dependency seams) |
|
|
89
|
+
| `--full` | All seven domains |
|
|
90
|
+
|
|
91
|
+
Examples:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
Review my TypeScript code --arch
|
|
95
|
+
```
|
|
96
|
+
```
|
|
97
|
+
Full audit --full
|
|
98
|
+
```
|
|
99
|
+
```
|
|
100
|
+
Review architecture of this project
|
|
101
|
+
```
|
|
79
102
|
|
|
80
103
|
### Fix — apply fixes from the report
|
|
81
104
|
|
|
@@ -136,6 +159,14 @@ Issues on unchanged lines are listed separately as pre-existing tech debt — in
|
|
|
136
159
|
| **Medium** | Tech debt — clean up when you're already editing that file |
|
|
137
160
|
| **Low** | Style and conventions — improve when convenient |
|
|
138
161
|
|
|
162
|
+
Architecture findings use the same scale. Each candidate also carries a **Fixability** tag:
|
|
163
|
+
|
|
164
|
+
| Fixability | Meaning |
|
|
165
|
+
|---|---|
|
|
166
|
+
| `auto` | Applied automatically during fix mode |
|
|
167
|
+
| `needs-confirm` | Shown to you first — only applied after explicit approval |
|
|
168
|
+
| `report-only` | Left as documentation — never auto-applied |
|
|
169
|
+
|
|
139
170
|
## Project Structure
|
|
140
171
|
|
|
141
172
|
```
|
|
@@ -153,21 +184,22 @@ ts-reviewer/
|
|
|
153
184
|
├── modernization.md # Checklist: TS 5.9+ idioms, satisfies, using, as const
|
|
154
185
|
├── code-quality.md # Checklist: complexity, dead code, naming, duplication
|
|
155
186
|
├── tsconfig.md # Checklist: strict flags, module resolution, deprecated
|
|
187
|
+
├── architecture.md # Checklist: shallow modules, coupling, seams, deepening
|
|
156
188
|
└── fix-workflow.md # Complete fix protocol: tests, verification, rollback
|
|
157
189
|
```
|
|
158
190
|
|
|
159
|
-
**SKILL.md**
|
|
191
|
+
**SKILL.md** is the orchestrator — it routes between scan/fix/auto modes, detects domain flags (`--arch`, `--full`), defines scope detection, severity scale, and report format.
|
|
160
192
|
|
|
161
|
-
**Reference files** contain the detailed checklists and protocols. Each analysis agent reads only the reference file relevant to its domain, keeping context focused.
|
|
193
|
+
**Reference files** contain the detailed checklists and protocols. Each analysis agent reads only the reference file relevant to its domain, keeping context focused. Architecture analysis is opt-in and loaded only when the domain is active.
|
|
162
194
|
|
|
163
195
|
## How It Works Under the Hood
|
|
164
196
|
|
|
165
197
|
### Scan mode
|
|
166
198
|
|
|
167
|
-
1. **Discovery** — maps the project, reads tsconfig.json, detects linter and test runner
|
|
199
|
+
1. **Discovery** — detects domain flags, maps the project, reads tsconfig.json, detects linter and test runner. If architecture is active, also maps module relationships and checks for `docs/adr/`.
|
|
168
200
|
2. **Diagnostics** — runs `tsc --noEmit`, linter, and LSP diagnostics (if available)
|
|
169
|
-
3. **Analysis** —
|
|
170
|
-
4. **Report** — deduplicates, applies severity boost (scoped modes), consolidates recurring patterns, writes `code-smells.md`
|
|
201
|
+
3. **Analysis** — specialized passes for each active domain (sub-agents in Claude Code, sequential in Claude.ai), each with its own checklist
|
|
202
|
+
4. **Report** — deduplicates, applies severity boost (scoped modes), consolidates recurring patterns, writes `code-smells.md`. Architecture findings appear in a separate `## Architecture Opportunities` section at the end.
|
|
171
203
|
|
|
172
204
|
### Fix mode
|
|
173
205
|
|
package/package.json
CHANGED
package/ts-reviewer/SKILL.md
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ts-reviewer
|
|
3
3
|
description: >
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"find problems", "find bugs", "fix issues", "fix code smells", "auto-fix",
|
|
11
|
-
"review and fix", "clean up code", or "review code quality" in a TypeScript project.
|
|
12
|
-
Also trigger when the user mentions "tech debt", "code health", "refactor candidates",
|
|
13
|
-
"security audit", "modernize", "review my changes", "review my PR", "check what I changed",
|
|
14
|
-
"review last commit", or "review uncommitted" in a TypeScript context. Works with pure
|
|
15
|
-
TypeScript 5.9+ codebases — no framework-specific checks (React, Vue, Angular, etc.).
|
|
4
|
+
TypeScript code review and auto-fix. Modes: scan, fix, auto. Scopes: full codebase,
|
|
5
|
+
uncommitted, branch diff, last N commits. Trigger on: review, audit, check, lint,
|
|
6
|
+
find issues, find bugs, fix issues, fix code smells, auto-fix, review and fix,
|
|
7
|
+
clean up code, tech debt, code health, security audit, modernize, review my changes,
|
|
8
|
+
review my PR, review last commit. Architecture review: --arch, --full, review architecture,
|
|
9
|
+
find refactoring opportunities, full audit. Pure TypeScript 5.9+ only.
|
|
16
10
|
---
|
|
17
11
|
|
|
18
12
|
# TypeScript Code Reviewer
|
|
@@ -33,6 +27,22 @@ This skill operates in three modes. Detect the mode from the user's request:
|
|
|
33
27
|
**`fix`** — Read `code-smells.md` and apply fixes with verification.
|
|
34
28
|
**`auto`** — Run scan, then fix, then re-scan to verify. Delete report if clean.
|
|
35
29
|
|
|
30
|
+
## Domain Set
|
|
31
|
+
|
|
32
|
+
Controls which review domains are active. Detected from flags first, then natural-language phrases.
|
|
33
|
+
|
|
34
|
+
| Flag / phrase | Active domains |
|
|
35
|
+
|---|---|
|
|
36
|
+
| (none / default) | Type Safety, Security, Async Patterns, Modernization, Code Quality, Config, Boundary Validation, Error Handling, Dependency Hygiene |
|
|
37
|
+
| `--arch` / "review architecture" / "find refactoring opportunities" / "deepening review" | Architecture only |
|
|
38
|
+
| `--full` / "full audit" / "full review" / "review everything" | All 10 domains (default 9 + Architecture) |
|
|
39
|
+
|
|
40
|
+
Parse order: check for explicit `--arch` / `--full` / `--no-arch` flags in the user's message first.
|
|
41
|
+
Fall back to phrase detection. If no flag or phrase matches → default domain set (no architecture).
|
|
42
|
+
|
|
43
|
+
Architecture domain is **off** in default scans. It loads `references/architecture.md` and adds
|
|
44
|
+
an `## Architecture Opportunities` section to `code-smells.md` only when active.
|
|
45
|
+
|
|
36
46
|
## Report File Location
|
|
37
47
|
|
|
38
48
|
- Always write to `code-smells.md` in the project root (not in `.claude/`)
|
|
@@ -94,30 +104,34 @@ If not specified, default to **full codebase**.
|
|
|
94
104
|
|
|
95
105
|
### Git commands per scope
|
|
96
106
|
|
|
97
|
-
**`full`** — all
|
|
107
|
+
**`full`** — all TypeScript files:
|
|
98
108
|
```bash
|
|
99
|
-
npx glob '**/*.ts' --ignore '**/node_modules/**'
|
|
100
|
-
# or: git ls-files '*.ts'
|
|
109
|
+
npx glob '**/*.{ts,mts,cts}' --ignore '**/node_modules/**'
|
|
110
|
+
# or: git ls-files '*.ts' '*.mts' '*.cts'
|
|
101
111
|
```
|
|
102
112
|
|
|
103
113
|
**`uncommitted`** — staged + unstaged + untracked:
|
|
104
114
|
```bash
|
|
105
|
-
git diff --name-only HEAD -- '*.ts'
|
|
106
|
-
git ls-files --others --exclude-standard -- '*.ts'
|
|
115
|
+
git diff --name-only HEAD -- '*.ts' '*.mts' '*.cts'
|
|
116
|
+
git ls-files --others --exclude-standard -- '*.ts' '*.mts' '*.cts'
|
|
107
117
|
```
|
|
108
118
|
|
|
109
119
|
**`branch`** — current branch vs base:
|
|
110
120
|
```bash
|
|
111
121
|
BASE=$(git rev-parse --verify main 2>/dev/null && echo main || echo master)
|
|
112
|
-
git diff --name-only "$BASE"...HEAD -- '*.ts'
|
|
113
|
-
git diff --name-only HEAD -- '*.ts'
|
|
122
|
+
git diff --name-only "$BASE"...HEAD -- '*.ts' '*.mts' '*.cts'
|
|
123
|
+
git diff --name-only HEAD -- '*.ts' '*.mts' '*.cts'
|
|
114
124
|
```
|
|
115
125
|
|
|
116
126
|
**`commits:N`** — last N commits:
|
|
117
127
|
```bash
|
|
118
|
-
git diff --name-only HEAD~N..HEAD -- '*.ts'
|
|
128
|
+
git diff --name-only HEAD~N..HEAD -- '*.ts' '*.mts' '*.cts'
|
|
119
129
|
```
|
|
120
130
|
|
|
131
|
+
**File type policy:** `.mts`/`.cts` are reviewed like `.ts`. `.d.ts` files are reviewed
|
|
132
|
+
only by the Type Safety and Config domains (declarations have no runtime behavior).
|
|
133
|
+
`.tsx` is out of scope — pure TypeScript only, no frameworks.
|
|
134
|
+
|
|
121
135
|
### Context files (scoped reviews)
|
|
122
136
|
|
|
123
137
|
Analysis scope = diff file list. Reading scope = wider. Always include as read-only:
|
|
@@ -133,7 +147,7 @@ Context files are NOT analyzed for issues.
|
|
|
133
147
|
|
|
134
148
|
Collect changed hunks:
|
|
135
149
|
```bash
|
|
136
|
-
git diff -U0 [appropriate range] -- '*.ts' | grep '^@@'
|
|
150
|
+
git diff -U0 [appropriate range] -- '*.ts' '*.mts' '*.cts' | grep '^@@'
|
|
137
151
|
```
|
|
138
152
|
|
|
139
153
|
- Issue on **new/modified line**: boost severity +1 (Low→Medium, Medium→High, High→Highest)
|
|
@@ -145,21 +159,31 @@ git diff -U0 [appropriate range] -- '*.ts' | grep '^@@'
|
|
|
145
159
|
|
|
146
160
|
## Phase 1 — Discovery (scan mode)
|
|
147
161
|
|
|
148
|
-
1. **Detect
|
|
162
|
+
1. **Detect domain set** from flags and phrases (see Domain Set section above).
|
|
163
|
+
|
|
164
|
+
2. **Detect scope mode** from user's request. Build file list.
|
|
149
165
|
If scoped mode yields 0 files, ask whether to fall back to full.
|
|
150
166
|
|
|
151
|
-
|
|
167
|
+
3. **Map project tree** — full structure regardless of scope.
|
|
152
168
|
|
|
153
|
-
|
|
169
|
+
4. **Read `tsconfig.json`**. Load `references/tsconfig.md` and audit config flags.
|
|
154
170
|
In scoped modes: only flag config if `tsconfig.json` is in diff or if full review.
|
|
171
|
+
Monorepos: detect workspaces (`workspaces` in package.json, `pnpm-workspace.yaml`) and
|
|
172
|
+
multiple tsconfigs (`tsconfig.build.json`, per-package configs, `references`). Audit the
|
|
173
|
+
config that actually governs the files in scope; note which config applies in the summary.
|
|
155
174
|
|
|
156
|
-
|
|
175
|
+
5. **Check for linter configs** — `eslint.config.*`, `.eslintrc.*`, `biome.json`, `deno.json`.
|
|
157
176
|
|
|
158
|
-
|
|
177
|
+
6. **Read `package.json`** — TS version, dependencies, module type.
|
|
159
178
|
|
|
160
|
-
|
|
179
|
+
7. **Identify entry points** — `index.ts`, `main.ts`, exports in `package.json`.
|
|
161
180
|
|
|
162
|
-
|
|
181
|
+
8. **Collect context files** (scoped modes only).
|
|
182
|
+
|
|
183
|
+
9. **Architecture discovery** (only if Architecture is in active domain set):
|
|
184
|
+
- Map module relationships: note circular imports, deep relative imports, barrel-file cycles, feature slices, public entry points.
|
|
185
|
+
- Check if `docs/adr/` exists — if so, scan it for architectural decisions before proposing changes. Skip silently if absent.
|
|
186
|
+
- Do NOT require `CONTEXT.md` or any domain-doc files.
|
|
163
187
|
|
|
164
188
|
Discovery summary:
|
|
165
189
|
```
|
|
@@ -183,7 +207,11 @@ Files in scope: <N> .ts files (+ <M> context files)
|
|
|
183
207
|
npx tsc --noEmit 2>&1 | head -200
|
|
184
208
|
```
|
|
185
209
|
|
|
186
|
-
|
|
210
|
+
All output lines are errors (tsc emits no warnings). Triage each error:
|
|
211
|
+
- Indicates a runtime hazard (null/undefined access, wrong argument shape,
|
|
212
|
+
missing property) -> **Highest**
|
|
213
|
+
- Hygiene errors (unused locals, unreachable code, implicit any on internals) -> **High**
|
|
214
|
+
|
|
187
215
|
In scoped modes: run on full project, report only errors in scoped files.
|
|
188
216
|
|
|
189
217
|
### 2b. Linter
|
|
@@ -191,7 +219,11 @@ In scoped modes: run on full project, report only errors in scoped files.
|
|
|
191
219
|
ESLint: `npx eslint [files] --format json 2>/dev/null | head -500`
|
|
192
220
|
Biome: `npx biome check [files] --reporter json 2>/dev/null | head -500`
|
|
193
221
|
|
|
194
|
-
Map
|
|
222
|
+
Map linter severities conservatively — projects configure stylistic rules as `error`:
|
|
223
|
+
- Rule matches a checklist item in a reference file -> use the checklist severity
|
|
224
|
+
- Correctness-class rule (`no-floating-promises`, `no-misused-promises`,
|
|
225
|
+
`no-unsafe-*`) -> **High**
|
|
226
|
+
- Anything else: `error` -> **Medium**, `warning` -> **Low**, `info` -> **Low**
|
|
195
227
|
|
|
196
228
|
### 2c. LSP diagnostics (if available)
|
|
197
229
|
|
|
@@ -203,14 +235,20 @@ Query TypeScript LSP via MCP if accessible. Merge with compiler output, deduplic
|
|
|
203
235
|
|
|
204
236
|
Read the corresponding reference file before each analysis pass.
|
|
205
237
|
|
|
238
|
+
Run only the agents whose domain is in the active domain set (see Domain Set section).
|
|
239
|
+
|
|
206
240
|
| Agent | Reference file | Focus |
|
|
207
241
|
|---|---|---|
|
|
208
242
|
| Type Safety | `references/type-safety.md` | `any`, casts, `!`, exhaustiveness, generics |
|
|
209
243
|
| Security | `references/security.md` | Injection, prototype pollution, ReDoS, path traversal |
|
|
210
244
|
| Async Patterns | `references/async-patterns.md` | Floating promises, race conditions, error propagation |
|
|
211
245
|
| Modernization | `references/modernization.md` | Outdated patterns vs TS 5.9+ idioms |
|
|
212
|
-
| Code Quality | `references/code-quality.md` | Complexity, duplication, naming, dead code |
|
|
246
|
+
| Code Quality | `references/code-quality.md` | Complexity, duplication, naming, dead code, testability |
|
|
213
247
|
| Config | `references/tsconfig.md` | tsconfig.json flags and module setup |
|
|
248
|
+
| Boundary Validation | `references/boundary-validation.md` | Runtime validation at system edges, DTO/domain separation |
|
|
249
|
+
| Error Handling | `references/error-handling.md` | Silent failures, throw hygiene, failure design |
|
|
250
|
+
| Dependency Hygiene | `references/dependency-hygiene.md` | package.json, versions, lockfiles, supply chain |
|
|
251
|
+
| Architecture | `references/architecture.md` | Shallow modules, scattered concepts, coupling, dependency seams, layering |
|
|
214
252
|
|
|
215
253
|
### Sub-agent template (Claude Code)
|
|
216
254
|
|
|
@@ -233,7 +271,7 @@ Output JSONL, one object per line:
|
|
|
233
271
|
"fix": "Concrete recommendation with code example",
|
|
234
272
|
"auto_fixable": true|false,
|
|
235
273
|
"in_diff": true|false,
|
|
236
|
-
"reference": "
|
|
274
|
+
"reference": "optional — omit unless allowed by the Evidence Protocol"
|
|
237
275
|
}
|
|
238
276
|
```
|
|
239
277
|
|
|
@@ -254,8 +292,11 @@ Go through each domain one at a time. Same JSON structure.
|
|
|
254
292
|
|
|
255
293
|
1. Apply severity boost (scoped modes only): `in_diff: true` -> boost +1 level.
|
|
256
294
|
2. Deduplicate: same file + line + issue -> keep one.
|
|
257
|
-
3. Merge overlapping: keep higher severity, note overlap.
|
|
295
|
+
3. Merge overlapping: keep higher severity, note overlap. If two domains flag the same
|
|
296
|
+
file + line, merge into one entry attributing both categories — never emit twice.
|
|
258
297
|
4. Consolidate patterns: 3+ identical issues -> one "Recurring Pattern" entry.
|
|
298
|
+
5. Noise budget: if a single domain produces > 25 Medium/Low findings, keep its top 15
|
|
299
|
+
by severity and impact, consolidate the rest into Recurring Pattern entries with counts.
|
|
259
300
|
|
|
260
301
|
### Report structure
|
|
261
302
|
|
|
@@ -269,7 +310,7 @@ Write to `code-smells.md`:
|
|
|
269
310
|
**TypeScript version:** <version>
|
|
270
311
|
**Scope:** Full / Uncommitted / Branch `x` vs `y` / Last N commits
|
|
271
312
|
**Files analyzed:** N (+ M context)
|
|
272
|
-
**Total issues:** N (X
|
|
313
|
+
**Total issues:** N (X highest, Y high, Z medium, W low)
|
|
273
314
|
**Severity-boosted:** N (scoped modes only)
|
|
274
315
|
|
|
275
316
|
## Summary
|
|
@@ -297,6 +338,23 @@ Write to `code-smells.md`:
|
|
|
297
338
|
## Recurring Patterns
|
|
298
339
|
## Config Issues
|
|
299
340
|
## Pre-existing Issues (scoped modes only)
|
|
341
|
+
|
|
342
|
+
## Architecture Opportunities
|
|
343
|
+
(only when Architecture is in active domain set AND at least one candidate found — omit entirely otherwise)
|
|
344
|
+
|
|
345
|
+
### TITLE — Severity
|
|
346
|
+
|
|
347
|
+
- **Files:** relative/path/a.ts, relative/path/b.ts
|
|
348
|
+
- **Problem:** why this causes friction now
|
|
349
|
+
- **Proposed deepening:** what would change
|
|
350
|
+
- **Interface shape:** rough sketch of new interface
|
|
351
|
+
- **Dependency category:** in-process | local-substitutable | remote-owned | true-external
|
|
352
|
+
- **Test strategy:** how tests improve
|
|
353
|
+
- **Benefits:** locality, leverage, test impact
|
|
354
|
+
- **Trade-offs:** what gets harder
|
|
355
|
+
- **Fixability:** auto | needs-confirm | report-only
|
|
356
|
+
|
|
357
|
+
---
|
|
300
358
|
````
|
|
301
359
|
|
|
302
360
|
### Sorting
|
|
@@ -337,13 +395,43 @@ Key principles:
|
|
|
337
395
|
|
|
338
396
|
In scoped modes: these are base severities before diff-aware boost.
|
|
339
397
|
|
|
398
|
+
Architecture findings use the same severity scale with domain-specific criteria.
|
|
399
|
+
See `references/architecture.md` → Severity Mapping for Architecture.
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## Evidence Protocol
|
|
404
|
+
|
|
405
|
+
Every finding must survive verification before it enters the report:
|
|
406
|
+
|
|
407
|
+
1. **Re-read before reporting.** After drafting a finding, re-read the exact lines in the
|
|
408
|
+
current file state. The `snippet` must appear verbatim at the stated `line` (±2 lines).
|
|
409
|
+
If it doesn't, re-locate or drop the finding.
|
|
410
|
+
2. **Check the surrounding context.** If a guard, validation, type narrowing, or comment
|
|
411
|
+
within the enclosing function/module already handles the case, the finding is a false
|
|
412
|
+
positive — drop it, don't downgrade it.
|
|
413
|
+
3. **Confidence gate.** Report only findings you can defend with the code in front of you.
|
|
414
|
+
If a finding depends on runtime behavior you cannot see (external input actually reaching
|
|
415
|
+
a sink, concurrent callers actually existing), either verify the data flow by reading the
|
|
416
|
+
callers, or mark the finding's problem statement with "if <condition>" and cap severity
|
|
417
|
+
at Medium.
|
|
418
|
+
4. **References must be real.** The `reference` field is optional. Only cite
|
|
419
|
+
typescriptlang.org, developer.mozilla.org, nodejs.org, or a file path inside this skill.
|
|
420
|
+
Never construct URLs from memory for anything else — omit the field instead.
|
|
421
|
+
5. **Convention override.** If a flagged non-High pattern appears consistently 5+ times
|
|
422
|
+
across the codebase, treat it as an intentional convention: downgrade one severity level
|
|
423
|
+
and report it once as a Recurring Pattern, not per occurrence.
|
|
424
|
+
6. **Version gate.** Every finding that recommends newer syntax or APIs must state the
|
|
425
|
+
minimum TS/runtime version required, verified against the project's actual TS version,
|
|
426
|
+
`target`/`lib`, and runtime. Never recommend a feature the project cannot use.
|
|
427
|
+
|
|
340
428
|
---
|
|
341
429
|
|
|
342
430
|
## Important Guidelines
|
|
343
431
|
|
|
344
432
|
- **No framework checks.** Pure TypeScript only.
|
|
345
|
-
- **No false positives from intentional patterns.**
|
|
346
|
-
|
|
433
|
+
- **No false positives from intentional patterns.** Suppression-directive severities are
|
|
434
|
+
owned by `references/type-safety.md` (Suppression Directives) — follow it, don't improvise.
|
|
347
435
|
- **Respect project conventions.** Don't flag consistent patterns unless harmful.
|
|
348
436
|
- **Be concrete.** Every issue needs a snippet and a fix recommendation.
|
|
349
437
|
"Consider refactoring" is not a valid fix.
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Architecture Review Checklist
|
|
2
|
+
|
|
3
|
+
Loaded only when architecture review is active (`--arch` or `--full` flag).
|
|
4
|
+
|
|
5
|
+
## Glossary
|
|
6
|
+
|
|
7
|
+
Use these terms in all architecture findings. Don't substitute "component", "service", "API", "boundary".
|
|
8
|
+
|
|
9
|
+
- **Module** — anything with an interface and an implementation: function, class, package, feature slice.
|
|
10
|
+
- **Interface** — everything a caller must know: types, invariants, error modes, ordering, config. Not just the TypeScript `interface` keyword.
|
|
11
|
+
- **Implementation** — the code inside the module.
|
|
12
|
+
- **Depth** — leverage at the interface. Deep = large behaviour behind small interface. Shallow = interface nearly as complex as implementation.
|
|
13
|
+
- **Seam** — where the interface lives; a place behaviour can be altered without editing in place. Prefer over "boundary".
|
|
14
|
+
- **Adapter** — a concrete thing satisfying an interface at a seam.
|
|
15
|
+
- **Leverage** — what callers get from depth: more capability per unit of interface they must learn.
|
|
16
|
+
- **Locality** — what maintainers get from depth: change, bugs, and knowledge concentrated in one place.
|
|
17
|
+
|
|
18
|
+
## Principles
|
|
19
|
+
|
|
20
|
+
- **Depth is a property of the interface, not the implementation.** A deep module can be internally complex — what matters is how small its interface is relative to what it hides.
|
|
21
|
+
- **Deletion test.** Imagine deleting the module. If complexity vanishes → it was a pass-through. If complexity reappears across callers → it was earning its keep.
|
|
22
|
+
- **The interface is the test surface.** Callers and tests cross the same seam. If tests must reach past the interface into internals, the module is the wrong shape.
|
|
23
|
+
- **One adapter = hypothetical seam. Two adapters = real seam.** Don't introduce a port unless at least two adapters are justified (production + test at minimum). Single-adapter seams are just indirection.
|
|
24
|
+
|
|
25
|
+
## Smell Checklist
|
|
26
|
+
|
|
27
|
+
### Shallow Modules
|
|
28
|
+
|
|
29
|
+
- Understanding one concept requires bouncing across many tiny modules. Severity: **Medium**.
|
|
30
|
+
- Module interface is nearly as complex as its implementation (shallow). Severity: **Medium**.
|
|
31
|
+
- Pass-through wrapper / manager / helper / service that adds no hidden complexity. Severity: **Medium**. Apply deletion test.
|
|
32
|
+
- Pure functions extracted only for testability, but real bugs hide in orchestration — no locality. Severity: **Medium**.
|
|
33
|
+
|
|
34
|
+
### Coupling and Boundaries
|
|
35
|
+
|
|
36
|
+
- Tight coupling leaks across module interfaces (callers must know implementation details). Severity: **High**.
|
|
37
|
+
- Tests must reach into module internals or mock too many neighbors to test one thing. Severity: **High**.
|
|
38
|
+
- Public API exports implementation details, config, ordering constraints, or internal error modes that callers should not need to know. Severity: **Medium**.
|
|
39
|
+
|
|
40
|
+
### Import and Module Structure
|
|
41
|
+
|
|
42
|
+
*Note: circular imports and deep relative imports are also flagged by the Code Quality domain in all scan modes. Architecture domain provides deeper refactoring guidance when Code Quality flags them.*
|
|
43
|
+
|
|
44
|
+
- Barrel-file cycles or circular import chains that can't be resolved by reordering. Severity: **High**.
|
|
45
|
+
- Deep relative imports (`../../../`) indicating modules not co-located with what they depend on. Severity: **Low**. Suggest path aliases or co-location.
|
|
46
|
+
|
|
47
|
+
### Locality
|
|
48
|
+
|
|
49
|
+
- Shared utility module mixes unrelated domain concepts (a grab-bag util). Severity: **Medium**.
|
|
50
|
+
- Feature logic split by technical layer (e.g. controller / service / repo per feature) in a way that destroys locality — change to one feature requires touching 4+ files. Severity: **Medium**.
|
|
51
|
+
|
|
52
|
+
### Under-Engineering
|
|
53
|
+
|
|
54
|
+
Depth cuts both ways — flag missing abstraction where it already hurts:
|
|
55
|
+
|
|
56
|
+
- Identical business rule (same constants/branching) implemented in 2+ modules.
|
|
57
|
+
Severity: **Medium**. Under-abstraction; deepen one module to own the rule.
|
|
58
|
+
- One module accreting unrelated concerns (imports from many unrelated domains, exports
|
|
59
|
+
serving disjoint caller groups). Severity: **Medium**. Split along caller groups.
|
|
60
|
+
- Event/string-keyed indirection between two modules that only ever talk to each other —
|
|
61
|
+
a direct typed call would be simpler and checkable. Severity: **Low**.
|
|
62
|
+
|
|
63
|
+
## Dependency Direction and Layering
|
|
64
|
+
|
|
65
|
+
Direction matters more than layer count. Detect and flag:
|
|
66
|
+
|
|
67
|
+
- Domain/computation modules importing IO directly (`node:fs`, `node:http`, DB clients,
|
|
68
|
+
`fetch`) when the dependency classification says the IO belongs behind a seam.
|
|
69
|
+
Severity: **Medium**. The domain owns logic; transport/storage is injected or
|
|
70
|
+
isolated at the edge.
|
|
71
|
+
- Shared/leaf modules (`utils/`, `types/`, `core/`) importing from feature modules —
|
|
72
|
+
inverted direction; the "shared" code now depends on a specific feature.
|
|
73
|
+
Severity: **High** — this is how import cycles start.
|
|
74
|
+
- Wire/DTO types from an external API imported deep into domain modules rather than
|
|
75
|
+
mapped to domain types at the boundary. Severity: **Medium**.
|
|
76
|
+
Cross-reference: `references/boundary-validation.md`.
|
|
77
|
+
|
|
78
|
+
## Detection Heuristics
|
|
79
|
+
|
|
80
|
+
Run these — don't guess from file names:
|
|
81
|
+
|
|
82
|
+
- Grep feature-module imports inside `shared|common|core|utils` directories
|
|
83
|
+
(inverted dependency direction).
|
|
84
|
+
- For each module, list its importers. A module whose every export has exactly one
|
|
85
|
+
importer is a pass-through candidate — apply the deletion test.
|
|
86
|
+
- An interface/type re-exported through 3+ files unchanged marks a pass-through chain.
|
|
87
|
+
- A utility file imported by the majority of modules is a grab-bag candidate — check
|
|
88
|
+
whether its exports share a domain concept.
|
|
89
|
+
- Change-set locality: in `git log --name-only`, do commits touching one feature
|
|
90
|
+
consistently touch 4+ directories? That feature's logic is scattered.
|
|
91
|
+
|
|
92
|
+
## Dependency Classification
|
|
93
|
+
|
|
94
|
+
When proposing a deepening fix, classify the dependency to determine test strategy:
|
|
95
|
+
|
|
96
|
+
**1. In-process** — pure computation or in-memory state, no I/O.
|
|
97
|
+
- Strategy: merge modules, test through the deepened interface directly. No adapter needed.
|
|
98
|
+
|
|
99
|
+
**2. Local-substitutable** — DB, filesystem, etc. with usable local stand-ins (PGLite, in-memory filesystem).
|
|
100
|
+
- Strategy: deepen using the stand-in in tests. The seam is internal; no port at the external interface.
|
|
101
|
+
|
|
102
|
+
**3. Remote but owned** — internal services across a network boundary.
|
|
103
|
+
- Strategy: define a port (TypeScript interface) at the seam. Production adapter + in-memory adapter for tests.
|
|
104
|
+
- The deep module owns the logic; only the transport is injected.
|
|
105
|
+
|
|
106
|
+
**4. True external** — third-party services (Stripe, Twilio, etc.) you don't control.
|
|
107
|
+
- Strategy: inject a port; provide a mock adapter for tests.
|
|
108
|
+
|
|
109
|
+
**Rule:** don't introduce a port unless at least two adapters are justified. A single-adapter seam is indirection without benefit.
|
|
110
|
+
|
|
111
|
+
## Candidate Report Format
|
|
112
|
+
|
|
113
|
+
Each architecture finding uses this format in the `## Architecture Opportunities` section:
|
|
114
|
+
|
|
115
|
+
```md
|
|
116
|
+
### TITLE — Severity
|
|
117
|
+
|
|
118
|
+
- **Files:** relative/path/a.ts, relative/path/b.ts
|
|
119
|
+
- **Problem:** why this causes friction now (not just a pattern name)
|
|
120
|
+
- **Proposed deepening:** plain-English description of what would change
|
|
121
|
+
- **Interface shape:** rough sketch of the new interface (types, methods, key invariants)
|
|
122
|
+
- **Dependency category:** in-process | local-substitutable | remote-owned | true-external
|
|
123
|
+
- **Test strategy:** how tests would improve (what tests survive, what gets deleted, what's new)
|
|
124
|
+
- **Benefits:** locality gained, leverage gained, test impact
|
|
125
|
+
- **Trade-offs:** what gets harder, what is genuinely uncertain
|
|
126
|
+
- **Fixability:** auto | needs-confirm | report-only
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Severity Mapping for Architecture
|
|
130
|
+
|
|
131
|
+
| Level | Architecture criteria |
|
|
132
|
+
|---|---|
|
|
133
|
+
| **Highest** | Architecture issue directly causing a security vulnerability, data loss, or production correctness bug |
|
|
134
|
+
| **High** | Circular imports or cross-module coupling that blocks reliable tests or causes recurring defects |
|
|
135
|
+
| **Medium** | Shallow modules, scattered domain logic, pass-through abstractions, hard-to-test orchestration |
|
|
136
|
+
| **Low** | Small interface leaks, minor naming drift between module interface and what it exposes |
|
|
137
|
+
|
|
138
|
+
## Fixability Mapping
|
|
139
|
+
|
|
140
|
+
| Fixability | Meaning | Fix mode behavior |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| `auto` | Local change: import path cleanup, narrow circular-import break, local module merge with co-located tests | Applied in normal fix loop |
|
|
143
|
+
| `needs-confirm` | Interface change, dependency inversion, test replacement, feature-slice reorganization | Shown to user, NOT applied automatically |
|
|
144
|
+
| `report-only` | Broad migration, ambiguous domain model, change requiring product/domain decision | Never applied, left as documentation |
|
|
145
|
+
|
|
146
|
+
## Naming Guidance
|
|
147
|
+
|
|
148
|
+
Use names that already exist in the codebase (function names, type names, file names, package names). Don't invent domain terms. If a deepened module needs a name, prefer a name already used in adjacent code.
|
|
149
|
+
|
|
150
|
+
## Architecture Fix Rules
|
|
151
|
+
|
|
152
|
+
When applying `auto` fixability architecture findings:
|
|
153
|
+
|
|
154
|
+
- Write new tests at the deepened module's interface. Old unit tests on shallow modules that are now merged become redundant — delete them.
|
|
155
|
+
- Tests assert observable behavior through the interface, not internal state.
|
|
156
|
+
- Don't expose internal seams through the interface just because tests need them.
|
|
157
|
+
- Tests that have to change when the implementation changes are testing past the interface.
|
|
158
|
+
- For large architecture changes classified `needs-confirm`, show a diff/migration plan and wait for explicit go-ahead before touching code.
|
|
@@ -13,19 +13,59 @@
|
|
|
13
13
|
|
|
14
14
|
## Error Handling
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
- `catch` that only logs without rethrowing. Severity: **Medium**.
|
|
20
|
-
- `Promise.allSettled()` ignoring `'rejected'` entries. Severity: **Medium**.
|
|
16
|
+
All catch/throw/failure-design checks live in `references/error-handling.md` — do not
|
|
17
|
+
duplicate them here. This domain only flags errors *lost to the async machinery*
|
|
18
|
+
(floating promises above, allSettled below).
|
|
21
19
|
|
|
22
20
|
## Race Conditions
|
|
23
21
|
|
|
24
22
|
- Multiple async ops modifying shared state without coordination.
|
|
25
|
-
Severity: **High**.
|
|
23
|
+
Severity: **High**. Fix: serialize via a promise chain / async mutex, or make the
|
|
24
|
+
operations idempotent so ordering doesn't matter.
|
|
26
25
|
- `Promise.race()` where losing promise's side effects still execute.
|
|
27
26
|
Severity: **Medium**.
|
|
28
27
|
- Read-modify-write with `await` in the middle. Severity: **High** in concurrent contexts.
|
|
28
|
+
- Overlapping requests writing to the same variable/state where the earlier response
|
|
29
|
+
can arrive last (last-write-wins clobbering). Severity: **High** in concurrent contexts.
|
|
30
|
+
Fix: request-sequence token check before applying the result, or abort the previous
|
|
31
|
+
request via AbortController.
|
|
32
|
+
- Lazily-initialized async singleton where concurrent first callers each run the
|
|
33
|
+
initializer. Severity: **Medium**. Fix: memoize the *promise*, not the resolved value:
|
|
34
|
+
`init ??= doInit(); return init;`
|
|
35
|
+
|
|
36
|
+
## Timeouts
|
|
37
|
+
|
|
38
|
+
- `await` on network/IO (fetch, DB call, queue op) with no timeout and no AbortSignal.
|
|
39
|
+
Severity: **Medium** (internal tools), **High** (request-handling / server paths —
|
|
40
|
+
a hung dependency hangs every caller).
|
|
41
|
+
Fix: `fetch(url, { signal: AbortSignal.timeout(5000) })`, or `Promise.race` with a
|
|
42
|
+
timer for APIs without signal support (clear the timer in `finally`).
|
|
43
|
+
- Timeout implemented with `Promise.race` but the losing operation keeps running and
|
|
44
|
+
holds resources (sockets, locks). Severity: **Medium**. Fix: abort the operation,
|
|
45
|
+
don't just abandon the promise.
|
|
46
|
+
|
|
47
|
+
## Retries
|
|
48
|
+
|
|
49
|
+
- Retry loop without a max-attempt cap. Severity: **High** (infinite loop under
|
|
50
|
+
persistent failure).
|
|
51
|
+
- Retries without backoff (tight loop hammering a failing dependency). Severity: **Medium**.
|
|
52
|
+
Fix: exponential backoff with jitter.
|
|
53
|
+
- Retrying a non-idempotent operation (payment, email send, resource creation).
|
|
54
|
+
Severity: **Highest** — duplicate side effects. Fix: idempotency key or check-then-act
|
|
55
|
+
on the server side; otherwise don't retry.
|
|
56
|
+
|
|
57
|
+
## Concurrency Limits
|
|
58
|
+
|
|
59
|
+
- `Promise.all(items.map(asyncFn))` where `items` is unbounded (user data, file lists,
|
|
60
|
+
API pages) and `asyncFn` does network/disk work. Severity: **Medium**.
|
|
61
|
+
Fix: process in chunks or use a concurrency limiter. Do NOT recommend converting
|
|
62
|
+
sequential awaits to unbounded `Promise.all` — recommend it only with an explicit
|
|
63
|
+
concurrency cap when the collection can exceed ~10 items.
|
|
64
|
+
- `Promise.all` where one rejection should not discard sibling results, or where
|
|
65
|
+
siblings' later rejections become unhandled. Severity: **Medium**.
|
|
66
|
+
Fix: `Promise.allSettled` + explicit handling of `rejected` entries.
|
|
67
|
+
- `Promise.allSettled()` results used without checking `status === 'rejected'` entries.
|
|
68
|
+
Severity: **Medium**.
|
|
29
69
|
|
|
30
70
|
## Cancellation
|
|
31
71
|
|
|
@@ -47,8 +87,10 @@
|
|
|
47
87
|
- `async function() { return await bar(); }` — unnecessary await (except in try/catch).
|
|
48
88
|
Severity: **Low**.
|
|
49
89
|
- Mixing `await` and `.then()` chains in same function. Severity: **Low**.
|
|
50
|
-
- Sequential `await` in loop when iterations are independent
|
|
51
|
-
Severity: **Medium**. Fix: `Promise.all(items.map(...))
|
|
90
|
+
- Sequential `await` in loop when iterations are independent AND the collection is small
|
|
91
|
+
and bounded (< ~10 known items). Severity: **Medium**. Fix: `Promise.all(items.map(...))`.
|
|
92
|
+
If the collection is unbounded or does network/disk work, require a concurrency cap
|
|
93
|
+
instead — see Concurrency Limits.
|
|
52
94
|
|
|
53
95
|
## Async Iterators
|
|
54
96
|
|