@nklisch/pi-agile-workflow 0.15.3
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/.claude-plugin/plugin.json +10 -0
- package/.codex-plugin/plugin.json +28 -0
- package/CHANGELOG.md +591 -0
- package/README.md +107 -0
- package/docs/ARCHITECTURE.md +716 -0
- package/docs/MIGRATION.md +419 -0
- package/docs/PRINCIPLES.md +464 -0
- package/docs/ROADMAP.md +95 -0
- package/docs/SPEC.md +855 -0
- package/docs/VISION.md +106 -0
- package/extensions/agile-workflow.test.ts +419 -0
- package/extensions/agile-workflow.ts +467 -0
- package/hooks/hooks.json +52 -0
- package/hooks/scripts/prompt-context.py +628 -0
- package/hooks/scripts/substrate-maintainer.py +359 -0
- package/hooks/scripts/test_prompt_context.py +757 -0
- package/package.json +30 -0
- package/scripts/install-work-view.sh +165 -0
- package/scripts/tests/agent-metadata.test.sh +75 -0
- package/scripts/tests/bump-version.test.sh +552 -0
- package/scripts/tests/channel-parity.test.sh +117 -0
- package/scripts/tests/convert-content-integrity.test.sh +246 -0
- package/scripts/tests/convert-install-routing.test.sh +159 -0
- package/scripts/tests/install-work-view.test.sh +411 -0
- package/scripts/tests/pi-package-metadata.test.sh +119 -0
- package/scripts/tests/work-board-shim.test.sh +239 -0
- package/scripts/tests/work-view-dist-version.test.sh +105 -0
- package/scripts/work-board.sh +64 -0
- package/scripts/work-view.sh +423 -0
- package/skills/autopilot/SKILL.md +330 -0
- package/skills/autopilot/agents/openai.yaml +6 -0
- package/skills/board/SKILL.md +62 -0
- package/skills/board/agents/openai.yaml +6 -0
- package/skills/bold-refactor/SKILL.md +244 -0
- package/skills/bold-refactor/agents/openai.yaml +6 -0
- package/skills/bug-scan/SKILL.md +378 -0
- package/skills/bug-scan/agents/openai.yaml +6 -0
- package/skills/bug-scan/references/async-promises.md +119 -0
- package/skills/bug-scan/references/concurrency-races.md +148 -0
- package/skills/bug-scan/references/data-layer.md +167 -0
- package/skills/bug-scan/references/error-handling.md +197 -0
- package/skills/bug-scan/references/gate-item-template.md +94 -0
- package/skills/bug-scan/references/language-footguns.md +219 -0
- package/skills/bug-scan/references/parked-item-template.md +103 -0
- package/skills/bug-scan/references/report-template.md +115 -0
- package/skills/bug-scan/references/resource-leaks.md +199 -0
- package/skills/bug-scan/references/state-closures.md +148 -0
- package/skills/bug-scan/references/time-numbers.md +158 -0
- package/skills/convert/SKILL.md +1463 -0
- package/skills/convert/agents/openai.yaml +6 -0
- package/skills/convert/references/legacy-overlap-migration.md +179 -0
- package/skills/deep-code-scan/SKILL.md +378 -0
- package/skills/deep-code-scan/agents/openai.yaml +6 -0
- package/skills/deep-code-scan/references/artifact-ledger.md +148 -0
- package/skills/deep-code-scan/references/consolidation.md +91 -0
- package/skills/deep-code-scan/references/decomposition.md +127 -0
- package/skills/deep-code-scan/references/item-templates.md +263 -0
- package/skills/deep-code-scan/references/lane-catalog.md +129 -0
- package/skills/deep-code-scan/references/review-gauntlet.md +108 -0
- package/skills/deep-code-scan/references/scanner-brief.md +152 -0
- package/skills/e2e-test-design/SKILL.md +517 -0
- package/skills/e2e-test-design/references/anti-tautology.md +261 -0
- package/skills/e2e-test-design/references/service-mocks.md +234 -0
- package/skills/e2e-test-design/references/test-taxonomy.md +211 -0
- package/skills/epic-design/SKILL.md +476 -0
- package/skills/epicize/SKILL.md +210 -0
- package/skills/epicize/agents/openai.yaml +6 -0
- package/skills/feature-design/SKILL.md +472 -0
- package/skills/fix/SKILL.md +173 -0
- package/skills/gate-cruft/SKILL.md +258 -0
- package/skills/gate-docs/SKILL.md +278 -0
- package/skills/gate-patterns/SKILL.md +389 -0
- package/skills/gate-refactor/SKILL.md +408 -0
- package/skills/gate-security/SKILL.md +275 -0
- package/skills/gate-tests/SKILL.md +367 -0
- package/skills/groom/SKILL.md +162 -0
- package/skills/groom/agents/openai.yaml +6 -0
- package/skills/ideate/SKILL.md +185 -0
- package/skills/ideate/agents/openai.yaml +6 -0
- package/skills/implement/SKILL.md +288 -0
- package/skills/implement-orchestrator/SKILL.md +604 -0
- package/skills/park/SKILL.md +111 -0
- package/skills/perf-design/SKILL.md +427 -0
- package/skills/perf-scout/SKILL.md +404 -0
- package/skills/perf-scout/agents/openai.yaml +6 -0
- package/skills/perf-scout/references/algorithmic-and-data-structures.md +126 -0
- package/skills/perf-scout/references/approximation-and-precomputation.md +136 -0
- package/skills/perf-scout/references/caching-and-memoization.md +123 -0
- package/skills/perf-scout/references/compiler-runtime-and-language.md +136 -0
- package/skills/perf-scout/references/database-and-storage-internals.md +144 -0
- package/skills/perf-scout/references/distributed-systems.md +150 -0
- package/skills/perf-scout/references/game-engine-and-realtime.md +136 -0
- package/skills/perf-scout/references/gpu-and-accelerators.md +130 -0
- package/skills/perf-scout/references/idea-ranking.md +75 -0
- package/skills/perf-scout/references/io-and-batching.md +135 -0
- package/skills/perf-scout/references/memory-and-data-locality.md +137 -0
- package/skills/perf-scout/references/parallelism-and-vectorization.md +126 -0
- package/skills/perf-scout/references/parked-item-template.md +117 -0
- package/skills/perf-scout/references/peer-review-pass.md +150 -0
- package/skills/perf-scout/references/report-template.md +148 -0
- package/skills/principles/SKILL.md +678 -0
- package/skills/principles/references/models.md +191 -0
- package/skills/principles/references/subagents.md +158 -0
- package/skills/prose-author/SKILL.md +185 -0
- package/skills/refactor-conventions-creator/SKILL.md +359 -0
- package/skills/refactor-conventions-creator/agents/openai.yaml +6 -0
- package/skills/refactor-conventions-creator/references/common-structures.md +91 -0
- package/skills/refactor-conventions-creator/references/common-styles.md +90 -0
- package/skills/refactor-design/SKILL.md +384 -0
- package/skills/release-deploy/SKILL.md +667 -0
- package/skills/release-deploy/agents/openai.yaml +6 -0
- package/skills/research/SKILL.md +166 -0
- package/skills/research/agents/openai.yaml +6 -0
- package/skills/review/SKILL.md +267 -0
- package/skills/review/references/deep-review.md +98 -0
- package/skills/review/references/review-lenses.md +68 -0
- package/skills/review/references/substrate-side-effects.md +161 -0
- package/skills/review/references/target-resolution.md +52 -0
- package/skills/scope/SKILL.md +486 -0
- package/work-view/Cargo.lock +486 -0
- package/work-view/Cargo.toml +9 -0
- package/work-view/crates/cli/.work-view-version +1 -0
- package/work-view/crates/cli/Cargo.toml +18 -0
- package/work-view/crates/cli/src/actionable.rs +500 -0
- package/work-view/crates/cli/src/args.rs +792 -0
- package/work-view/crates/cli/src/board/assets/board.css +855 -0
- package/work-view/crates/cli/src/board/assets/board.js +259 -0
- package/work-view/crates/cli/src/board/assets/card.js +139 -0
- package/work-view/crates/cli/src/board/assets/components.css +290 -0
- package/work-view/crates/cli/src/board/assets/dependency.js +1193 -0
- package/work-view/crates/cli/src/board/assets/detail.js +256 -0
- package/work-view/crates/cli/src/board/assets/filters.js +389 -0
- package/work-view/crates/cli/src/board/assets/index.html +77 -0
- package/work-view/crates/cli/src/board/assets/kanban.js +231 -0
- package/work-view/crates/cli/src/board/assets/markdown.js +260 -0
- package/work-view/crates/cli/src/board/assets/motion.css +131 -0
- package/work-view/crates/cli/src/board/assets/state.js +245 -0
- package/work-view/crates/cli/src/board/assets/table.js +304 -0
- package/work-view/crates/cli/src/board/assets/tokens.css +602 -0
- package/work-view/crates/cli/src/board/assets/views.js +41 -0
- package/work-view/crates/cli/src/board/assets.rs +162 -0
- package/work-view/crates/cli/src/board/feed.rs +204 -0
- package/work-view/crates/cli/src/board/mod.rs +246 -0
- package/work-view/crates/cli/src/board/open.rs +144 -0
- package/work-view/crates/cli/src/board/server.rs +407 -0
- package/work-view/crates/cli/src/main.rs +195 -0
- package/work-view/crates/cli/src/render.rs +354 -0
- package/work-view/crates/cli/src/scope.rs +157 -0
- package/work-view/crates/cli/src/stale.rs +581 -0
- package/work-view/crates/cli/tests/board-js/dependency-table.test.mjs +64 -0
- package/work-view/crates/cli/tests/board-js/expanded-browsing.test.mjs +79 -0
- package/work-view/crates/cli/tests/board-js/harness-smoke.test.mjs +11 -0
- package/work-view/crates/cli/tests/board-js/harness.mjs +437 -0
- package/work-view/crates/cli/tests/board-js/kanban-detail.test.mjs +75 -0
- package/work-view/crates/cli/tests/board-js/markdown-filter.test.mjs +60 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/CONVENTIONS.md +3 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/active/epics/epic-alpha.md +16 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/active/features/feat-a.md +16 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/active/features/feat-b.md +16 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/active/stories/story-alpha-1.md +16 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/active/stories/story-research-1.md +19 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/archive/feat-done.md +16 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/archive/feat-shipped.md +18 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/backlog/idea-backlog.md +7 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/releases/v1.0/v1.0.md +16 -0
- package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/CONVENTIONS.md +6 -0
- package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-dep-done.md +17 -0
- package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-design-ready.md +17 -0
- package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-impl-ready.md +17 -0
- package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/stories/story-review-ready.md +17 -0
- package/work-view/crates/cli/tests/fixtures/with-malformed/.work/CONVENTIONS.md +4 -0
- package/work-view/crates/cli/tests/fixtures/with-malformed/.work/active/epics/malformed-no-closing-fence.md +8 -0
- package/work-view/crates/cli/tests/fixtures/with-malformed/.work/active/features/good-item.md +16 -0
- package/work-view/crates/cli/tests/integration.rs +2833 -0
- package/work-view/crates/core/Cargo.toml +13 -0
- package/work-view/crates/core/src/error.rs +61 -0
- package/work-view/crates/core/src/filter.rs +804 -0
- package/work-view/crates/core/src/graph.rs +324 -0
- package/work-view/crates/core/src/index.rs +590 -0
- package/work-view/crates/core/src/lib.rs +54 -0
- package/work-view/crates/core/src/model.rs +202 -0
- package/work-view/crates/core/src/parse.rs +494 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/CONVENTIONS.md +15 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/active/epics/epic-core.md +16 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/active/features/feat-parser.md +16 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/active/features/feat-query.md +16 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/malformed-no-id.md +15 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/story-parse-unit.md +16 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/story-security-gate.md +16 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/archive/feat-old.md +16 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/archive/feat-parser.md +17 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/backlog/idea-future-board.md +7 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/releases/v1.0/v1.0.md +16 -0
- package/work-view/crates/core/tests/fixtures/precedence/.work/CONVENTIONS.md +10 -0
- package/work-view/crates/core/tests/fixtures/precedence/.work/archive/dup-item.md +18 -0
- package/work-view/crates/core/tests/fixtures/precedence/.work/releases/v2.0/dup-item.md +18 -0
- package/work-view/crates/core/tests/integration.rs +541 -0
- package/work-view/dist/.gitattributes +1 -0
- package/work-view/dist/README.md +42 -0
- package/work-view/dist/aarch64-apple-darwin/work-view +0 -0
- package/work-view/dist/aarch64-unknown-linux-musl/work-view +0 -0
- package/work-view/dist/x86_64-apple-darwin/work-view +0 -0
- package/work-view/dist/x86_64-unknown-linux-musl/work-view +0 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Language-Specific Footguns — Bug Reference
|
|
2
|
+
|
|
3
|
+
> **When to load this reference**: scanning code in a specific language for footguns that aren't
|
|
4
|
+
> captured by the cross-cutting bug references. Load the section that matches the detected language.
|
|
5
|
+
|
|
6
|
+
## How to use
|
|
7
|
+
- Scanner reads only the section(s) matching the codebase's languages.
|
|
8
|
+
- Each pattern is "watch for this in this language" — pure language-level surprises. Concurrency, async, state, resources, time/numbers, errors, and data/distributed footguns live in sibling references.
|
|
9
|
+
- Format per pattern: signature → why hard to find → fix direction.
|
|
10
|
+
|
|
11
|
+
## JavaScript / TypeScript
|
|
12
|
+
*scanner: load if `.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs` present.*
|
|
13
|
+
|
|
14
|
+
### 1. Loose equality and `null`/`undefined` coercion
|
|
15
|
+
- `value == 0`, `"0" == false`, `null == undefined` is `true`. Coercion makes wrong matches look intentional.
|
|
16
|
+
- Fix: `===`/`!==`; explicit `x === null || x === undefined`.
|
|
17
|
+
|
|
18
|
+
### 2. `JSON.stringify` drops fields / throws on BigInt
|
|
19
|
+
- `JSON.stringify({fn: () => 1, big: 1n, u: undefined})` — functions/`undefined` vanish silently; `BigInt` throws on the offending payload only.
|
|
20
|
+
- Fix: pre-validate shape; custom replacer for BigInt; never serialize class instances directly.
|
|
21
|
+
|
|
22
|
+
### 3. Object spread is shallow
|
|
23
|
+
- `{...state, nested: state.nested}` then mutating `next.nested.x`. State updates look immutable but share inner refs.
|
|
24
|
+
- Fix: `structuredClone`, recursive copy, or spread each nested level explicitly.
|
|
25
|
+
|
|
26
|
+
### 4. `Array.sort()` lexicographic on numbers
|
|
27
|
+
- `[10, 2, 1].sort()` → `[1, 10, 2]`. Tests with small data pass; production sees ordering bugs.
|
|
28
|
+
- Fix: always pass `(a, b) => a - b`.
|
|
29
|
+
|
|
30
|
+
### 5. `this` rebinding when methods become callbacks
|
|
31
|
+
- `setTimeout(obj.method, 0)`, `arr.map(this.handler)`. Works when called directly; only callback paths break.
|
|
32
|
+
- Fix: arrow functions, `.bind(this)`, or arrow class fields.
|
|
33
|
+
|
|
34
|
+
### 6. TS `as` assertion / `any` contagion
|
|
35
|
+
- `value as User`, `JSON.parse(s) as Config`, `as unknown as T`. Compiler trusts you; runtime mismatches surface as `undefined` accesses later. `any` poisons every expression it touches.
|
|
36
|
+
- Fix: runtime validators (zod, io-ts); prefer `unknown` + type guards over `any`.
|
|
37
|
+
|
|
38
|
+
### 7. Optional chaining swallowing required calls
|
|
39
|
+
- `obj?.callback()` when callback was supposed to fire. Silent no-op masks misconfiguration.
|
|
40
|
+
- Fix: assert presence at the boundary; reserve `?.` for genuinely-optional reads.
|
|
41
|
+
|
|
42
|
+
### 8. `for...in` on arrays, integer-key promotion in objects
|
|
43
|
+
- `for (const k in arr)` yields string keys (incl. inherited); `{2:'b', 1:'a'}` reorders integer-like keys first.
|
|
44
|
+
- Fix: `for...of`; `Map` when insertion order matters.
|
|
45
|
+
|
|
46
|
+
## Python
|
|
47
|
+
*scanner: load if `.py`, `.pyi`, `pyproject.toml`, `requirements*.txt` present.*
|
|
48
|
+
|
|
49
|
+
### 1. Mutable default arguments
|
|
50
|
+
- `def f(x=[]):` — default created once at def-time and shared across calls.
|
|
51
|
+
- Fix: `def f(x=None): x = [] if x is None else x`.
|
|
52
|
+
|
|
53
|
+
### 2. Late binding in closures over loop vars
|
|
54
|
+
- `[lambda: i for i in range(3)]` — all return `2`. Lambdas look independent; only invocation reveals shared binding.
|
|
55
|
+
- Fix: default-arg capture `lambda i=i:` or `functools.partial`.
|
|
56
|
+
|
|
57
|
+
### 3. `is` vs `==` interning lies
|
|
58
|
+
- `x is 256` works, `x is 257` may not. Small-int and short-string interning makes `is` appear correct in tests.
|
|
59
|
+
- Fix: `==` for value compare; `is` only for `None`, `True`, `False`, sentinels.
|
|
60
|
+
|
|
61
|
+
### 4. `bool` is an `int`
|
|
62
|
+
- `isinstance(True, int)` true; `sum([True, True])` is `2`; `dict[True]` and `dict[1]` collide.
|
|
63
|
+
- Fix: check `bool` before `int` in isinstance chains; coerce explicitly.
|
|
64
|
+
|
|
65
|
+
### 5. Shared class attribute mistaken for instance
|
|
66
|
+
- `class C: items = []` then `self.items.append(x)` mutates the class-level list across all instances.
|
|
67
|
+
- Fix: assign in `__init__`; `field(default_factory=list)` for dataclasses.
|
|
68
|
+
|
|
69
|
+
### 6. `[[0]*3]*3` shared inner lists
|
|
70
|
+
- `grid = [[0]*3]*3; grid[0][0] = 1` flips column 0 across all rows.
|
|
71
|
+
- Fix: `[[0]*3 for _ in range(3)]`.
|
|
72
|
+
|
|
73
|
+
### 7. `lru_cache` on instance methods leaks `self`
|
|
74
|
+
- `@lru_cache` on `def method(self, ...)` holds strong refs to every `self`; memory grows with instance count.
|
|
75
|
+
- Fix: `cached_property` per-instance; or module-level cache keyed on hashable inputs only.
|
|
76
|
+
|
|
77
|
+
### 8. Naive `datetime.utcnow()` (deprecated 3.12+)
|
|
78
|
+
- `datetime.utcnow()`, `datetime.utcfromtimestamp(t)` return naive datetimes that look like UTC but compare wrong against aware ones.
|
|
79
|
+
- Fix: `datetime.now(datetime.UTC)` (3.11+) or `datetime.now(timezone.utc)`.
|
|
80
|
+
|
|
81
|
+
### 9. Pickle of untrusted data → arbitrary code execution
|
|
82
|
+
- `pickle.loads(network_bytes)`, `joblib.load(user_file)`. Any deserialization from network/uploads/queues.
|
|
83
|
+
- Fix: JSON / msgpack / protobuf at untrusted boundaries; pickle only inside trusted process boundaries.
|
|
84
|
+
|
|
85
|
+
## Go
|
|
86
|
+
*scanner: load if `.go`, `go.mod`, `go.sum` present.*
|
|
87
|
+
|
|
88
|
+
### 1. Loop variable capture (pre-1.22 modules)
|
|
89
|
+
- `for _, v := range items { go func() { use(v) }() }` in a module declaring `go 1.21` or older. Go 1.22+ fixed semantics, but only for modules that declare `go 1.22`+.
|
|
90
|
+
- Fix: bump `go.mod`; or shadow with `v := v` inside loop.
|
|
91
|
+
|
|
92
|
+
### 2. Typed-nil interface
|
|
93
|
+
- `var e *MyErr = nil; var err error = e; err != nil` → `true`. Interface has non-nil type tag with nil pointer; `err == nil` checks fail.
|
|
94
|
+
- Fix: return untyped `nil` when there's no error; never assign typed-nil to an interface variable.
|
|
95
|
+
|
|
96
|
+
### 3. `defer` in a long-running loop
|
|
97
|
+
- `for _, f := range files { fp, _ := os.Open(f); defer fp.Close() }` — defers don't fire until the function returns; FDs accumulate.
|
|
98
|
+
- Fix: extract loop body into a function so `defer` scopes correctly; or close explicitly.
|
|
99
|
+
|
|
100
|
+
### 4. Slice append aliasing
|
|
101
|
+
- `b := append(a, x)` may mutate `a`'s tail if `cap(a) > len(a)`. Behavior depends on capacity, not visible code.
|
|
102
|
+
- Fix: `slices.Clone(a)` before append; or pre-copy with `append([]T(nil), a...)`.
|
|
103
|
+
|
|
104
|
+
### 5. `range` over map is randomized
|
|
105
|
+
- `for k, v := range m` produces a different order each run. Tests asserting order pass intermittently.
|
|
106
|
+
- Fix: collect keys, sort, iterate.
|
|
107
|
+
|
|
108
|
+
### 6. Receive from closed channel returns zero value
|
|
109
|
+
- `v := <-ch` after `close(ch)` returns `0`/empty silently — looks like a real message.
|
|
110
|
+
- Fix: `v, ok := <-ch` or `for v := range ch`.
|
|
111
|
+
|
|
112
|
+
### 7. Send to nil channel blocks forever
|
|
113
|
+
- `var ch chan int; ch <- 1`. Lazily-initialized channels or unset struct fields are common sources.
|
|
114
|
+
- Fix: initialize before use; use nil channels deliberately only inside `select` to disable a case.
|
|
115
|
+
|
|
116
|
+
### 8. Mixed pointer/value receivers on the same type
|
|
117
|
+
- Half the methods `func (s Foo)`, half `func (s *Foo)`. Method sets differ for `Foo` vs `*Foo`; interface satisfaction quietly fails for one but not the other.
|
|
118
|
+
- Fix: pick one receiver kind per type and stick to it.
|
|
119
|
+
|
|
120
|
+
## Rust
|
|
121
|
+
*scanner: load if `.rs`, `Cargo.toml`, `Cargo.lock` present.*
|
|
122
|
+
|
|
123
|
+
### 1. `unwrap()` / `expect()` in production paths
|
|
124
|
+
- `.unwrap()` outside tests / one-shot CLI prelude. Code compiles cleanly; panics surface only on the unhappy path. Library crates especially.
|
|
125
|
+
- Fix: propagate with `?`; convert to typed errors; reserve `expect` for invariants with explanatory message.
|
|
126
|
+
|
|
127
|
+
### 2. `clone()` to silence the borrow checker
|
|
128
|
+
- `.clone()` chains on `Vec`/`String`/`Arc` in hot loops. Code correct but copies large data; only profilers reveal allocator pressure.
|
|
129
|
+
- Fix: borrow with `&`; use `Cow<'_, T>`; restructure ownership; `Arc::clone` is cheap but still atomic.
|
|
130
|
+
|
|
131
|
+
### 3. `move` closures capturing more than expected
|
|
132
|
+
- `move || { use(&x); use(&y) }` where only `x` was meant to move. Compiles; consumes both, "use of moved value" elsewhere.
|
|
133
|
+
- Fix: clone before move; capture only what's needed (`let x = x.clone(); move || ...`).
|
|
134
|
+
|
|
135
|
+
### 4. `RefCell` / re-entrant `Mutex` runtime conflicts
|
|
136
|
+
- `cell.borrow_mut()` while a `borrow()` is alive; nested `lock()` same thread. Type system can't catch interior-mutability conflicts; panic/deadlock under specific call sequences.
|
|
137
|
+
- Fix: shorten borrow scopes; restructure to avoid reentrancy; `try_borrow_mut`.
|
|
138
|
+
|
|
139
|
+
### 5. `?` coercing through `From` to wrong error type
|
|
140
|
+
- `value?` where multiple `From<E>` impls exist; the wrong one wins.
|
|
141
|
+
- Fix: explicit `.map_err`; narrower error enums; `thiserror` with disambiguated `#[from]`.
|
|
142
|
+
|
|
143
|
+
### 6. `if let` temporary lifetimes past `else` (pre-2024 edition)
|
|
144
|
+
- `if let Some(x) = mutex.lock()?.get() { ... } else { mutex.lock(); /* deadlock */ }` — pre-2024 editions keep the temporary guard alive until after `else`.
|
|
145
|
+
- Fix: bind temporary to `let` and drop explicitly; or move to Rust 2024 edition.
|
|
146
|
+
|
|
147
|
+
### 7. `unsafe { ... }` scoping more than the unsafe call
|
|
148
|
+
- Large `unsafe` blocks wrapping mostly-safe code. Whole block is reviewer-blessed; only the actually-unsafe op needs scrutiny.
|
|
149
|
+
- Fix: shrink unsafe scope to the minimal expression; document invariants per call.
|
|
150
|
+
|
|
151
|
+
## Java / Kotlin
|
|
152
|
+
*scanner: load if `.java`, `.kt`, `.kts`, `pom.xml`, `build.gradle*` present.*
|
|
153
|
+
|
|
154
|
+
### 1. `equals` without matching `hashCode`
|
|
155
|
+
- Overridden `equals` with default `hashCode`, or only one. Works in `List.contains`; breaks in `HashMap`/`HashSet`.
|
|
156
|
+
- Fix: always override both; use records, `@AutoValue`, or Kotlin `data class` (without inheritance).
|
|
157
|
+
|
|
158
|
+
### 2. `==` on boxed numerics (Java); reference equality on objects
|
|
159
|
+
- `Integer a = 200, b = 200; a == b` → `false` (outside `-128..127` cache).
|
|
160
|
+
- Fix: `.equals()` for boxed types; primitives where possible. (Kotlin `==` calls `equals`; `===` is reference.)
|
|
161
|
+
|
|
162
|
+
### 3. `ConcurrentModificationException` from iterator + mutation
|
|
163
|
+
- `for (X x : list) { list.remove(x); }`.
|
|
164
|
+
- Fix: `Iterator.remove()`, `removeIf`, copy-to-new-list, or `CopyOnWriteArrayList`.
|
|
165
|
+
|
|
166
|
+
### 4. `Optional.get()` without `isPresent`
|
|
167
|
+
- `opt.get()` reachable without prior presence check; chains using `Optional` as if nullable.
|
|
168
|
+
- Fix: `orElse`, `orElseThrow`, `ifPresent`, `map`/`flatMap`.
|
|
169
|
+
|
|
170
|
+
### 5. `compareTo` overflow / inconsistent with equals
|
|
171
|
+
- Comparators using subtraction (`a.x - b.x`) overflow; non-antisymmetric implementations.
|
|
172
|
+
- Fix: `Integer.compare(a, b)`; `Comparator.comparing(...)`.
|
|
173
|
+
|
|
174
|
+
### 6. Kotlin platform types — silent NPE
|
|
175
|
+
- `val name: String = javaObj.getName()` where Java return is unannotated (`String!`). Compiler can't enforce nullability across the Java boundary.
|
|
176
|
+
- Fix: declare result as `String?` and check; wrap Java calls with explicit null handling.
|
|
177
|
+
|
|
178
|
+
### 7. Kotlin `lateinit` uninitialized access
|
|
179
|
+
- `lateinit var foo: Foo` accessed before assignment → `UninitializedPropertyAccessException`. DI-like patterns and tests without framework are common sources.
|
|
180
|
+
- Fix: constructor injection; `by lazy`; `::foo.isInitialized` only when justified.
|
|
181
|
+
|
|
182
|
+
### 8. Kotlin `data class` inheritance / mutable hash keys
|
|
183
|
+
- `data class Sub(val extra: Int) : Base(...)` ignores Base fields in generated equals/hashCode; `data class` with `var` mutable in `HashMap`.
|
|
184
|
+
- Fix: avoid inheritance from data classes; keep fields `val`.
|
|
185
|
+
|
|
186
|
+
## C / C++
|
|
187
|
+
*scanner: load if `.c`, `.cc`, `.cpp`, `.cxx`, `.h`, `.hpp`, `CMakeLists.txt`, `Makefile` present.*
|
|
188
|
+
|
|
189
|
+
### 1. Returning pointer / reference to local
|
|
190
|
+
- `T* foo() { T x; return &x; }`, `const T& bar() { return T{}; }`, `string_view` over a local. Works "by accident" until stack frame is reused or optimizer changes.
|
|
191
|
+
- Fix: return by value (NRVO), smart pointer, or output buffer parameter.
|
|
192
|
+
|
|
193
|
+
### 2. Use-after-move
|
|
194
|
+
- `f(std::move(x)); g(x);` — `x` is valid-but-unspecified. Type system permits the second use.
|
|
195
|
+
- Fix: scope variables tightly; only assign or destroy after move; clang-tidy `bugprone-use-after-move`.
|
|
196
|
+
|
|
197
|
+
### 3. Iterator / pointer invalidation on container mutation
|
|
198
|
+
- Loop over `vector` that `push_back`s; cached `.data()` across reallocation. Works while capacity holds; reallocation silently invalidates.
|
|
199
|
+
- Fix: collect changes then apply; reserve capacity; index-based loops with care.
|
|
200
|
+
|
|
201
|
+
### 4. Implicit narrowing conversions
|
|
202
|
+
- `int i = 1.9;`, `uint32_t x = -1;`, `int16_t y = some_int;`. Brace-init `{}` warns; assignment doesn't.
|
|
203
|
+
- Fix: prefer `{}` initialization to surface narrowing; explicit `static_cast`; `-Wconversion`.
|
|
204
|
+
|
|
205
|
+
### 5. Signed/unsigned mixing
|
|
206
|
+
- `for (size_t i = 0; i < v.size() - 1; ++i)` when `v.size() == 0` underflows to huge value.
|
|
207
|
+
- Fix: pick one signedness per arithmetic chain; `std::ssize`; bounds-check before subtraction.
|
|
208
|
+
|
|
209
|
+
### 6. Missing virtual destructor on polymorphic base
|
|
210
|
+
- Base with virtual functions but no virtual dtor; `delete` via base pointer. Derived destructor doesn't run → leaks, partial destruction UB.
|
|
211
|
+
- Fix: add `virtual ~Base() = default;`, or protect dtor and restrict deletion to derived.
|
|
212
|
+
|
|
213
|
+
### 7. `auto` deducing value when reference intended
|
|
214
|
+
- `for (auto x : container)` copies; `auto y = obj.large_member();` copies large/non-copyable types.
|
|
215
|
+
- Fix: `auto&` / `const auto&` for non-mutating loops; `decltype(auto)` when forwarding return type.
|
|
216
|
+
|
|
217
|
+
### 8. Strict-aliasing / type-punning via cast
|
|
218
|
+
- `float f = *reinterpret_cast<float*>(&someInt);`. Works at `-O0`; UB under `-O2`; optimizer assumes types don't alias.
|
|
219
|
+
- Fix: `std::memcpy` for type punning; `std::bit_cast` (C++20); unions are NOT portable for this in C++.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Parked Bug-Scan Item Template
|
|
2
|
+
|
|
3
|
+
> Used by `bug-scan` in **standalone mode** to park each finding into `.work/backlog/<id>.md`.
|
|
4
|
+
> Lighter than the gate-mode item template — backlog items have no `stage:` field and minimal
|
|
5
|
+
> structure. The user elevates anything they want to action via `/agile-workflow:scope`.
|
|
6
|
+
|
|
7
|
+
## File location
|
|
8
|
+
|
|
9
|
+
All standalone findings (Critical → Low) go to `.work/backlog/<id>.md`.
|
|
10
|
+
|
|
11
|
+
> **Why all severities go to backlog (even Critical):** standalone is exploratory ("scan and
|
|
12
|
+
> see what's there"). The user decides what to action. For release-bound, must-fix-before-ship
|
|
13
|
+
> Criticals, use **gate mode** (`bug-scan --release <version>`) which writes to
|
|
14
|
+
> `.work/active/stories/` instead.
|
|
15
|
+
|
|
16
|
+
## ID convention
|
|
17
|
+
|
|
18
|
+
`bug-scan-<short-slug>` — slug describes the finding briefly (kebab-case, ≤ 40 chars after prefix).
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
- `bug-scan-stale-closure-cart-effect`
|
|
22
|
+
- `bug-scan-tx-not-rolled-back-on-error`
|
|
23
|
+
- `bug-scan-await-in-foreach-callback`
|
|
24
|
+
- `bug-scan-goroutine-leak-blocked-send`
|
|
25
|
+
|
|
26
|
+
On collision (same slug exists), suffix with `-2`, `-3`, etc.
|
|
27
|
+
|
|
28
|
+
## Frontmatter + body template
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
---
|
|
32
|
+
id: bug-scan-<short-slug>
|
|
33
|
+
created: YYYY-MM-DD # today's local date
|
|
34
|
+
tags: [bug, <domain-tag>] # plus optional severity tag, see below
|
|
35
|
+
bug_origin: scan # how this entered the backlog
|
|
36
|
+
bug_severity: critical | high | medium | low
|
|
37
|
+
bug_domain: concurrency | async | state | resource-leak | time-numbers | error-handling | data-layer | language-footgun
|
|
38
|
+
bug_location: <file>:<line> # canonical location for idempotency
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
# <one-line title>
|
|
42
|
+
|
|
43
|
+
**Location**: `<file>:<line>` · **Severity**: <severity> · **Pattern**: <named pattern or "new">
|
|
44
|
+
|
|
45
|
+
<one-paragraph capture: what the bug is, why it's a bug, and a one-sentence remediation direction>
|
|
46
|
+
|
|
47
|
+
```<lang>
|
|
48
|
+
<1-5 lines of offending code as evidence>
|
|
49
|
+
```
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Domain tag mapping
|
|
53
|
+
|
|
54
|
+
Same as the gate-item template:
|
|
55
|
+
|
|
56
|
+
| Domain | Tag |
|
|
57
|
+
|---|---|
|
|
58
|
+
| Concurrency & races | `concurrency` |
|
|
59
|
+
| Async / promises | `async` |
|
|
60
|
+
| State & closures | `state` |
|
|
61
|
+
| Resource leaks | `resource-leak` |
|
|
62
|
+
| Time & numbers | `time-numbers` |
|
|
63
|
+
| Error handling | `error-handling` |
|
|
64
|
+
| Data layer | `data-layer` |
|
|
65
|
+
| Language footguns | `language-footgun` |
|
|
66
|
+
|
|
67
|
+
All carry the umbrella `bug` tag. Optionally add `critical` / `high` to `tags:` for the
|
|
68
|
+
two highest severities so `grep -l "critical" .work/backlog/` surfaces them fast.
|
|
69
|
+
|
|
70
|
+
## Idempotency (re-run protection)
|
|
71
|
+
|
|
72
|
+
Before parking, scan existing backlog files:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
grep -rl "^bug_origin: scan$" .work/backlog/ 2>/dev/null | while read -r f; do
|
|
76
|
+
loc=$(grep -m1 '^bug_location:' "$f" | awk '{print $2}')
|
|
77
|
+
[ -n "$loc" ] && echo "$loc $f"
|
|
78
|
+
done > /tmp/bugscan-existing.txt
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
For each new finding, check the (file, line, pattern) triple against
|
|
82
|
+
`/tmp/bugscan-existing.txt`. If present, **skip** — do not re-park. Tally the skip count and
|
|
83
|
+
include it in the report's summary.
|
|
84
|
+
|
|
85
|
+
If the existing file is older than 30 days and the finding still applies, refresh its
|
|
86
|
+
`created` to today and append a "(still applies as of YYYY-MM-DD)" line to the body.
|
|
87
|
+
Otherwise leave it alone.
|
|
88
|
+
|
|
89
|
+
## Rules
|
|
90
|
+
|
|
91
|
+
- One finding = one parked file. No bundling.
|
|
92
|
+
- `bug_origin: scan` is required — distinguishes standalone-parked items from gate-produced
|
|
93
|
+
items (`gate_origin: bugs`) and from user-parked ideas (no `bug_origin` field).
|
|
94
|
+
- `bug_location` is required — drives idempotency.
|
|
95
|
+
- `created` is today's ISO date.
|
|
96
|
+
- Keep the body to one paragraph + the code snippet. Detailed analysis belongs in the report,
|
|
97
|
+
not in every backlog file (backlog files get skimmed; the report gets read).
|
|
98
|
+
- After parking all items, commit in a single commit: `bug-scan: park <N> findings (<scope>)`.
|
|
99
|
+
|
|
100
|
+
## Opt-out
|
|
101
|
+
|
|
102
|
+
If the user invokes with `--no-park`, skip this entire phase. Write only the report. Note
|
|
103
|
+
the skipped count in the report's summary so it's transparent the substrate was bypassed.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Bug-Scan Report Template
|
|
2
|
+
|
|
3
|
+
> Used by `bug-scan` in **standalone mode** to write `bug-scan-report.md` at repo root.
|
|
4
|
+
> Fill the placeholders. Drop sections with zero entries.
|
|
5
|
+
|
|
6
|
+
```markdown
|
|
7
|
+
# Bug-Scan Report
|
|
8
|
+
|
|
9
|
+
**Generated**: <ISO-date>
|
|
10
|
+
**Scope**: <"whole repo" OR path glob>
|
|
11
|
+
**Files scanned**: <N>
|
|
12
|
+
**Overall score**: **<X.X/10>**
|
|
13
|
+
|
|
14
|
+
## Stack profile
|
|
15
|
+
|
|
16
|
+
- Languages: <list>
|
|
17
|
+
- Frameworks: <list>
|
|
18
|
+
- Data layer: <list>
|
|
19
|
+
- Concurrency primitives: <list>
|
|
20
|
+
- Notable entry points: <list>
|
|
21
|
+
|
|
22
|
+
## Domain scores
|
|
23
|
+
|
|
24
|
+
| Domain | Relevance | Score | Findings (C/H/M/L) |
|
|
25
|
+
|---|---|---|---|
|
|
26
|
+
| Concurrency & races | most | X/10 | 0/1/2/3 |
|
|
27
|
+
| Async / promises | most | X/10 | 0/0/1/2 |
|
|
28
|
+
| State & closures | relevant | X/10 | 0/0/0/1 |
|
|
29
|
+
| Resource leaks | most | X/10 | 1/0/1/0 |
|
|
30
|
+
| Time & numbers | relevant | X/10 | 0/0/0/2 |
|
|
31
|
+
| Error handling | most | X/10 | 0/2/1/0 |
|
|
32
|
+
| Data layer | most | X/10 | 0/1/3/1 |
|
|
33
|
+
| Language footguns | relevant | X/10 | 0/0/2/4 |
|
|
34
|
+
|
|
35
|
+
**Overall**: <X.X/10> — weighted average (most-relevant 2x, relevant 1x).
|
|
36
|
+
|
|
37
|
+
## Top 3 critical findings
|
|
38
|
+
|
|
39
|
+
1. **<title>** — `<file:line>` (<domain>, <severity>)
|
|
40
|
+
2. **<title>** — `<file:line>` (<domain>, <severity>)
|
|
41
|
+
3. **<title>** — `<file:line>` (<domain>, <severity>)
|
|
42
|
+
|
|
43
|
+
## Findings by severity
|
|
44
|
+
|
|
45
|
+
### Critical (<n>)
|
|
46
|
+
|
|
47
|
+
#### <title>
|
|
48
|
+
- **Domain**: <domain>
|
|
49
|
+
- **Pattern**: <pattern name from reference, or "new">
|
|
50
|
+
- **Location**: `<file:line>`
|
|
51
|
+
- **Parked**: `bug-scan-<slug>` *(or `not parked: --no-park` / `not parked: no substrate` /
|
|
52
|
+
`duplicate of bug-scan-<slug>` if skipped via idempotency)*
|
|
53
|
+
- **Evidence**:
|
|
54
|
+
```<lang>
|
|
55
|
+
<1-5 lines of offending code>
|
|
56
|
+
```
|
|
57
|
+
- **Why it's a bug**: <1-2 sentences — the specific failure mode>
|
|
58
|
+
- **Remediation direction**: <direction, not finished fix>
|
|
59
|
+
- *Also flagged by*: <other domain, if duplicate>
|
|
60
|
+
|
|
61
|
+
<repeat for each Critical>
|
|
62
|
+
|
|
63
|
+
### High (<n>)
|
|
64
|
+
|
|
65
|
+
<same shape>
|
|
66
|
+
|
|
67
|
+
### Medium (<n>)
|
|
68
|
+
|
|
69
|
+
<same shape>
|
|
70
|
+
|
|
71
|
+
### Low (<n>)
|
|
72
|
+
|
|
73
|
+
<same shape — can be compressed to one-liners if many>
|
|
74
|
+
|
|
75
|
+
## Domains skipped
|
|
76
|
+
|
|
77
|
+
| Domain | Why |
|
|
78
|
+
|---|---|
|
|
79
|
+
| <domain> | <e.g. "no async code in scope", "no DB usage detected"> |
|
|
80
|
+
|
|
81
|
+
## Backlog parking summary
|
|
82
|
+
|
|
83
|
+
| Metric | Count |
|
|
84
|
+
|---|---|
|
|
85
|
+
| Findings parked this run | <n> |
|
|
86
|
+
| Duplicates skipped (already in backlog) | <n> |
|
|
87
|
+
| Refreshed (existing item, still applies, `created` updated to today) | <n> |
|
|
88
|
+
| Opt-out (`--no-park`) | true / false |
|
|
89
|
+
| Substrate present | true / false |
|
|
90
|
+
|
|
91
|
+
> All parked items live at `.work/backlog/bug-scan-*.md` with `bug_origin: scan`. Elevate any
|
|
92
|
+
> of them to active stories via `/agile-workflow:scope <id>`. Find them all with:
|
|
93
|
+
> `grep -l '^bug_origin: scan$' .work/backlog/*.md`.
|
|
94
|
+
|
|
95
|
+
## Scanner gaps
|
|
96
|
+
|
|
97
|
+
> If any scanner returned an error or inconclusive results, list it here.
|
|
98
|
+
> The user can re-run that domain alone if they want.
|
|
99
|
+
|
|
100
|
+
- <domain>: <what went wrong, or "—" if all clean>
|
|
101
|
+
|
|
102
|
+
## Next steps
|
|
103
|
+
|
|
104
|
+
- Hand the Criticals to `/agile-workflow:fix <id>` for immediate repair.
|
|
105
|
+
- `/agile-workflow:scope` to promote findings into tracked items.
|
|
106
|
+
- Re-run after fixes to verify resolution.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Rules
|
|
110
|
+
|
|
111
|
+
- Always include the stack profile even if short.
|
|
112
|
+
- Show the domain table even when most rows are zero — empty rows signal coverage.
|
|
113
|
+
- Group findings by severity, then by domain within each severity.
|
|
114
|
+
- Sort within a severity group by file path (predictable diff-friendly).
|
|
115
|
+
- Don't repeat the same finding under multiple severities — pick the highest and cross-link.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Resource Leaks — Bug Reference
|
|
2
|
+
|
|
3
|
+
> **When to load this reference**: when scanning code that opens files, connects to anything, subscribes/observes, registers listeners, schedules timers, spawns threads/processes, acquires locks/semaphores, or otherwise consumes a finite resource pool.
|
|
4
|
+
|
|
5
|
+
## Detection signals
|
|
6
|
+
|
|
7
|
+
grep heuristics (combine with negative-lookahead for matching cleanup):
|
|
8
|
+
|
|
9
|
+
- `open\(` / `fopen` / `os.Open` without nearby `close`/`with`/`defer`/`use`
|
|
10
|
+
- `connect\(` / `Dial\(` / `new WebSocket` / `EventSource\(` / `createConnection`
|
|
11
|
+
- `subscribe\(` / `\.on\(` / `addEventListener\(` (especially with inline `=> {}`)
|
|
12
|
+
- `setInterval\(` / `setTimeout\(` / `requestAnimationFrame` without paired `clear*`
|
|
13
|
+
- `new (Worker|Thread|ResizeObserver|IntersectionObserver|MutationObserver|AbortController)`
|
|
14
|
+
- `go func\(` / `Thread\.start` / `tokio::spawn` / `asyncio\.create_task` / `spawn\(` / `fork\(` / `Popen\(`
|
|
15
|
+
- `pool\.(acquire|get|checkout)` / `Semaphore.*acquire` / `Lock\.lock`
|
|
16
|
+
- `\.begin\(\)` / `startTransaction` / `tx, _ :=` without paired `Commit|Rollback`
|
|
17
|
+
- Missing `defer .*\.Close\(\)` after handle acquisition (Go); JS/TS `useEffect` body opens a resource but `return` is absent
|
|
18
|
+
|
|
19
|
+
## Patterns
|
|
20
|
+
|
|
21
|
+
### 1. File opened without RAII / context manager / defer
|
|
22
|
+
- **Signature**: handle acquired then used without language-idiomatic auto-close.
|
|
23
|
+
- **Why hard to find**: works fine until exception path or high throughput exhausts FDs.
|
|
24
|
+
- **Where to look**: report generators, log writers, CSV/JSON loaders, image pipelines.
|
|
25
|
+
- **Example**: `f = open(p); data = f.read(); parse(data); f.close()` — exception in `parse` leaks `f`.
|
|
26
|
+
- **Fix direction**: `with open(p) as f:` (Py), try-with-resources (Java), `defer f.Close()` (Go), `using`/`await using` (C#, TS 5.2+), RAII (Rust/C++), `try/finally` (JS); Swift `defer { try? f.close() }`; C `goto cleanup`; Node streams need `'error'`+`'close'` handlers.
|
|
27
|
+
|
|
28
|
+
### 2. DB connection acquired, early return before release
|
|
29
|
+
- **Signature**: `conn = pool.acquire(); ...; pool.release(conn)` with branches in between.
|
|
30
|
+
- **Why hard to find**: pool exhaustion appears only under sustained error rate.
|
|
31
|
+
- **Where to look**: repository layers, request handlers, retry loops.
|
|
32
|
+
- **Example**: `conn = await pool.connect(); res = await conn.query(sql); pool.release(conn)` — `query` throws → leak.
|
|
33
|
+
- **Fix direction**: try/finally; `async with pool.acquire()`; `defer conn.Release()`; scoped helpers (`pool.withConnection(fn)`); Go `sql.DB` auto-manages but `Rows` needs `defer rows.Close()`; HikariCP needs try-with-resources.
|
|
34
|
+
|
|
35
|
+
### 3. Transaction begun but neither committed nor rolled back on early return
|
|
36
|
+
- **Signature**: `tx = db.begin(); ...; tx.commit()` with no `tx.rollback()` in catch / defer.
|
|
37
|
+
- **Why hard to find**: lock contention and idle-in-transaction grow slowly; only surface under load.
|
|
38
|
+
- **Where to look**: multi-step writes, saga steps, validation between writes.
|
|
39
|
+
- **Example**: `tx = await db.begin(); await tx.insert(x); validate(x); await tx.commit()` — `validate` throws → idle tx holds row locks.
|
|
40
|
+
- **Fix direction**: `try { ...; await tx.commit() } catch { await tx.rollback(); throw }`; Go `defer tx.Rollback()` after Begin (Commit makes Rollback a no-op).
|
|
41
|
+
|
|
42
|
+
### 4. HTTP client / Axios / Session recreated per request
|
|
43
|
+
- **Signature**: `new HttpClient()` / `axios.create()` / `requests.Session()` inside hot loop or handler.
|
|
44
|
+
- **Why hard to find**: leaks ephemeral ports + sockets in TIME_WAIT; manifests as ENOBUFS / EADDRNOTAVAIL.
|
|
45
|
+
- **Where to look**: utility functions called repeatedly, lambdas, per-request middleware.
|
|
46
|
+
- **Example**: handler does `await axios.get(url)` (creates new agent) instead of reusing a module-level instance.
|
|
47
|
+
- **Fix direction**: hoist client to module scope; reuse `http.Client` (Go), `HttpClient` (.NET — never per-request), pooled `requests.Session` (Py).
|
|
48
|
+
|
|
49
|
+
### 5. WebSocket / SSE / EventSource not closed on unmount
|
|
50
|
+
|
|
51
|
+
- **Signature**: `new WebSocket(url)` inside component / route handler without paired `.close()`.
|
|
52
|
+
- **Why hard to find**: SPA navigation leaks connections per route visit; backend FD pressure.
|
|
53
|
+
- **Where to look**: React/Vue/Svelte effects, hooks, route components.
|
|
54
|
+
- **Example**: `useEffect(() => { const ws = new WebSocket(u); ws.onmessage = ... }, [])` — no cleanup.
|
|
55
|
+
- **Fix direction**: `return () => ws.close()` from effect; Vue `onUnmounted`; Svelte `onDestroy`; Angular `takeUntilDestroyed`.
|
|
56
|
+
|
|
57
|
+
### 6. addEventListener without removeEventListener (and anonymous fn)
|
|
58
|
+
|
|
59
|
+
- **Signature**: `el.addEventListener('x', () => {...})` — anonymous arrow, no removal handle.
|
|
60
|
+
- **Why hard to find**: GC keeps target reachable via listener closure; multiplies per mount.
|
|
61
|
+
- **Where to look**: window/document listeners from components, custom hooks, third-party widget integrations.
|
|
62
|
+
- **Example**: `window.addEventListener('resize', () => setW(window.innerWidth))` — anon arrow, can't remove.
|
|
63
|
+
- **Fix direction**: name the handler, `return () => window.removeEventListener('resize', handler)`; or use `AbortController` `signal` + abort on cleanup.
|
|
64
|
+
|
|
65
|
+
### 7. setInterval / setTimeout / requestAnimationFrame not cleared
|
|
66
|
+
|
|
67
|
+
- **Signature**: `setInterval(fn, ms)` / `setTimeout(fn, ms)` without `clearInterval` / `clearTimeout`.
|
|
68
|
+
- **Why hard to find**: long-lived timers reference component state, prevent GC, fire after unmount.
|
|
69
|
+
- **Where to look**: polling, debouncing, animations, retry-with-backoff.
|
|
70
|
+
- **Example**: `useEffect(() => { setInterval(poll, 1000) }, [])` — id discarded, never cleared.
|
|
71
|
+
- **Fix direction**: capture id; `return () => clearInterval(id)`; for RAF, `cancelAnimationFrame(raf)`.
|
|
72
|
+
|
|
73
|
+
### 8. Subscription teardown discarded (RxJS / Observable / EventEmitter)
|
|
74
|
+
|
|
75
|
+
- **Signature**: `obs.subscribe(fn)` without assigning to a variable; `emitter.on(...)` without paired `off`.
|
|
76
|
+
- **Why hard to find**: subscription persists across component lifetime; closure roots a tree of state.
|
|
77
|
+
- **Where to look**: Angular components, Redux observable epics, EventEmitter wiring.
|
|
78
|
+
- **Example**: `this.user$.subscribe(u => this.user = u)` in component — no `unsubscribe` in `ngOnDestroy`.
|
|
79
|
+
- **Fix direction**: `takeUntilDestroyed()` (Angular 16+); store `Subscription` and unsubscribe; Node `emitter.once` or `emitter.off(name, handler)`.
|
|
80
|
+
|
|
81
|
+
### 9. Async generator never returned / closed
|
|
82
|
+
|
|
83
|
+
- **Signature**: `for await (const x of gen())` exited via break/throw without `return()`/`finally`.
|
|
84
|
+
- **Why hard to find**: driver task / pending I/O lingers; Python warns `coroutine was never awaited`.
|
|
85
|
+
- **Where to look**: streaming endpoints, paginated iterators, server-sent generators.
|
|
86
|
+
- **Example**: `async function *stream() { const r = await openReader(); yield ... }` — caller breaks; reader never closed.
|
|
87
|
+
- **Fix direction**: `try { yield ... } finally { reader.close() }`; Python `async with aclosing(gen)`; explicit `await gen.return()`.
|
|
88
|
+
|
|
89
|
+
### 10. Goroutine blocked on send to unbuffered channel
|
|
90
|
+
|
|
91
|
+
- **Signature**: `go func(){ ch <- result }()` where parent may return before receiving.
|
|
92
|
+
- **Why hard to find**: leak is per-request; only visible via runtime/goroutine count, pprof, or `uber/goleak`.
|
|
93
|
+
- **Where to look**: timeout patterns, first-result-wins races, fan-out with early return.
|
|
94
|
+
- **Example**: launch goroutine that writes to result channel; parent times out via `select` → sender blocks forever.
|
|
95
|
+
- **Fix direction**: buffer the channel (`make(chan T, 1)`); pass a `context.Context` with cancel; use `errgroup` or `sync.WaitGroup`.
|
|
96
|
+
|
|
97
|
+
### 11. Python tempfile created with delete=False, never unlinked
|
|
98
|
+
|
|
99
|
+
- **Signature**: `NamedTemporaryFile(delete=False)`, `mkstemp()`, `mkdtemp()` without `os.unlink` / `shutil.rmtree`.
|
|
100
|
+
- **Why hard to find**: `/tmp` slowly fills; only noticed when disk full.
|
|
101
|
+
- **Where to look**: file-upload handlers, ML preprocessing, PDF/image conversion.
|
|
102
|
+
- **Example**: `f = NamedTemporaryFile(delete=False); f.write(data); process(f.name)` — no cleanup on success or error.
|
|
103
|
+
- **Fix direction**: `try/finally: os.unlink(f.name)`; prefer `delete=True` with `with`; use `tempfile.TemporaryDirectory()`.
|
|
104
|
+
|
|
105
|
+
### 12. child_process / subprocess never killed on parent shutdown
|
|
106
|
+
|
|
107
|
+
- **Signature**: `spawn` / `Popen` / `exec.Command(...).Start()` without `.kill()` on shutdown.
|
|
108
|
+
- **Why hard to find**: orphan processes survive parent; accumulate over restarts.
|
|
109
|
+
- **Where to look**: dev servers, FFmpeg/imagemagick wrappers, language-server hosts.
|
|
110
|
+
- **Example**: `const proc = spawn('ffmpeg', args)` — parent crashes; ffmpeg lingers.
|
|
111
|
+
- **Fix direction**: SIGTERM/SIGINT handlers that kill children; process-group kill; Python `atexit` + `proc.terminate()`; Go `Cmd.SysProcAttr.Setpgid` then kill group.
|
|
112
|
+
|
|
113
|
+
### 13. Worker thread / Web Worker never terminated
|
|
114
|
+
|
|
115
|
+
- **Signature**: `new Worker(...)` / `new Thread(...)` without `terminate()` / `join()` on tear-down.
|
|
116
|
+
- **Why hard to find**: workers hold their own heap; per-tab and per-component leaks multiply.
|
|
117
|
+
- **Where to look**: heavy computation offloaded from UI, parallel pipelines.
|
|
118
|
+
- **Example**: `useEffect(() => { const w = new Worker(url); w.postMessage(x) }, [])` — no `w.terminate()`.
|
|
119
|
+
- **Fix direction**: return cleanup that calls `terminate()`/`close()`; Node `worker_threads` should be pooled and reused.
|
|
120
|
+
|
|
121
|
+
### 14. Native handle held via finalizer-only cleanup
|
|
122
|
+
|
|
123
|
+
- **Signature**: relies on GC / `__del__` / finalizer to release FDs, sockets, mmaps.
|
|
124
|
+
- **Why hard to find**: works in tests; under load GC lag exhausts handles before finalizers run.
|
|
125
|
+
- **Where to look**: wrappers around C libraries, `ctypes`/JNI/N-API bindings.
|
|
126
|
+
- **Example**: Python class wraps `os.open(fd)` and closes in `__del__` — never called in ref-cycle.
|
|
127
|
+
- **Fix direction**: explicit `close()` / `Dispose()` / `Drop`; context-manager protocol; never depend on finalizer for finite resources.
|
|
128
|
+
|
|
129
|
+
### 15. Lock acquired in one branch, released only in another
|
|
130
|
+
|
|
131
|
+
- **Signature**: conditional `lock.acquire()` not mirrored by guaranteed release.
|
|
132
|
+
- **Why hard to find**: deadlock only on specific branch with exception.
|
|
133
|
+
- **Where to look**: cache fill paths, double-checked initialization, retry logic.
|
|
134
|
+
- **Example**: `if (stale) { lock.acquire(); fetch(); }; lock.release()` — release runs even when not acquired (or vice versa).
|
|
135
|
+
- **Fix direction**: scope guard (`with lock:`, `defer mu.Unlock()`, `std::lock_guard`, Java try-finally); don't branch on acquire.
|
|
136
|
+
|
|
137
|
+
### 16. Semaphore permit acquired, exception before release
|
|
138
|
+
|
|
139
|
+
- **Signature**: `sem.acquire()` then risky operation then `sem.release()` without finally.
|
|
140
|
+
- **Why hard to find**: permits monotonically deplete; eventually all callers block.
|
|
141
|
+
- **Where to look**: rate limiters, bounded concurrency wrappers, connection guards.
|
|
142
|
+
- **Example**: `await sem.acquire(); await callApi(); sem.release()` — `callApi` throws → permit lost.
|
|
143
|
+
- **Fix direction**: `try { ... } finally { sem.release() }`; Python `async with sem:`; Java `Semaphore` in try/finally.
|
|
144
|
+
|
|
145
|
+
### 17. AbortController created per render, not aborted
|
|
146
|
+
|
|
147
|
+
- **Signature**: `const ctrl = new AbortController()` inside render/effect without `ctrl.abort()` in cleanup.
|
|
148
|
+
- **Why hard to find**: each render registers more listeners on the signal; closures pin state.
|
|
149
|
+
- **Where to look**: data-fetching hooks, search-as-you-type, infinite scroll.
|
|
150
|
+
- **Example**: `useEffect(() => { fetch(url, { signal: new AbortController().signal }) }, [url])` — orphaned controllers.
|
|
151
|
+
- **Fix direction**: `const ctrl = new AbortController(); ...; return () => ctrl.abort()`; one controller per effect run.
|
|
152
|
+
|
|
153
|
+
### 18. Cache without eviction, keyed by user input
|
|
154
|
+
|
|
155
|
+
- **Signature**: `Map`/`dict` populated by user-supplied keys without TTL, LRU bound, or invalidation.
|
|
156
|
+
- **Why hard to find**: looks like a perf optimization; only OOMs at scale or with adversarial input.
|
|
157
|
+
- **Where to look**: memoization helpers, request-deduplication caches, dataloader-ish patterns at module scope.
|
|
158
|
+
- **Example**: `const cache = new Map(); function get(key){ if(!cache.has(key)) cache.set(key, compute(key)); return cache.get(key) }`.
|
|
159
|
+
- **Fix direction**: bounded LRU (`lru-cache`, `functools.lru_cache(maxsize=N)`, Caffeine, Guava), `WeakMap` keyed by lifetime, TTL eviction.
|
|
160
|
+
|
|
161
|
+
### 19. Listener on long-lived global from short-lived component
|
|
162
|
+
|
|
163
|
+
- **Signature**: short-lived object registers callback on a singleton/global without dropping it on destroy.
|
|
164
|
+
- **Why hard to find**: global retains tree of component state; classic "detached DOM" pattern.
|
|
165
|
+
- **Where to look**: stores/buses, document/window, EventBus singletons, third-party SDK globals (analytics, posthog, sockets).
|
|
166
|
+
- **Example**: modal subscribes `bus.on('refresh', this.refresh)` — bus outlives modal, `this` rooted forever.
|
|
167
|
+
- **Fix direction**: pair every `on` with `off` in destroy; use `WeakRef` or weak listener; expose `dispose()` API.
|
|
168
|
+
|
|
169
|
+
### 20. ResizeObserver / IntersectionObserver / MutationObserver not disconnected
|
|
170
|
+
|
|
171
|
+
- **Signature**: `new ResizeObserver(cb)` / `new IntersectionObserver(...)` without `.disconnect()`.
|
|
172
|
+
- **Why hard to find**: observer keeps target node + callback closure alive even after DOM removal.
|
|
173
|
+
- **Where to look**: virtualized lists, layout-aware components, lazy-image loaders.
|
|
174
|
+
- **Example**: `const ro = new ResizeObserver(...); ro.observe(el)` in effect, no cleanup.
|
|
175
|
+
- **Fix direction**: `return () => ro.disconnect()`; or `unobserve(el)` if observer is shared; ESLint `no-leaked-resize-observer`.
|
|
176
|
+
|
|
177
|
+
### 21. IndexedDB transaction goes async without being awaited
|
|
178
|
+
|
|
179
|
+
- **Signature**: `db.transaction(...).objectStore(...).put(...)` followed by `await unrelatedPromise` mid-tx.
|
|
180
|
+
- **Why hard to find**: IDB auto-commits a tx that becomes idle; subsequent ops in same tx throw `TransactionInactiveError`.
|
|
181
|
+
- **Where to look**: offline-first apps, sync engines, PWA storage layers.
|
|
182
|
+
- **Example**: `const tx = db.transaction('s', 'rw'); await fetch(...); tx.objectStore('s').put(x)` — tx closed before put.
|
|
183
|
+
- **Fix direction**: complete all IDB work synchronously after the request; chain via `tx.complete`/`tx.oncomplete`; or use `idb` library's `tx.done`.
|
|
184
|
+
|
|
185
|
+
### 22. ORM/ODM cursor / streaming iterator not closed
|
|
186
|
+
|
|
187
|
+
- **Signature**: `cursor = collection.find(...)` / `db.Query(...)` / `session.stream(...)` without explicit close.
|
|
188
|
+
- **Why hard to find**: holds server-side cursor, connection, and snapshot until timeout; replica lag spikes.
|
|
189
|
+
- **Where to look**: export endpoints, batch jobs, MongoDB streams, Postgres `stream()`/`cursor()`.
|
|
190
|
+
- **Example**: Go `rows, _ := db.Query(...)` — caller forgets `defer rows.Close()`; connection never returns to pool.
|
|
191
|
+
- **Fix direction**: `defer rows.Close()` (Go), `with cursor:` / `for x in cursor: ... ; cursor.close()` (Py), `try-with-resources` (JDBC), `await cursor.close()` (Mongo Node driver).
|
|
192
|
+
|
|
193
|
+
### 23. Streams: readable not drained / writable not ended
|
|
194
|
+
|
|
195
|
+
- **Signature**: `stream.on('data', ...)` without `'end'`/`'error'`, or `writable.write(x)` without `.end()`.
|
|
196
|
+
- **Why hard to find**: backpressure stalls pipelines; FDs retained until process exit.
|
|
197
|
+
- **Where to look**: file copies, HTTP proxies, compression pipelines, S3 uploads.
|
|
198
|
+
- **Example**: `fs.createReadStream(p).pipe(transform)` — `transform` never piped to a sink; FD held.
|
|
199
|
+
- **Fix direction**: `pipeline(readable, transform, writable, cb)` (Node `stream/promises`); handle `'error'` to trigger destroy; explicit `writable.end()`.
|