lemmaly 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +238 -0
  3. package/cli/gen-agents-md.js +60 -0
  4. package/cli/gen-rule-docs.js +885 -0
  5. package/cli/lemmaly.js +162 -0
  6. package/commands/benchmark.md +40 -0
  7. package/commands/budget.md +53 -0
  8. package/commands/complexity.md +26 -0
  9. package/commands/cut.md +27 -0
  10. package/commands/hotpath.md +22 -0
  11. package/commands/invariant.md +22 -0
  12. package/commands/n-plus-one.md +20 -0
  13. package/commands/profile.md +34 -0
  14. package/commands/regress.md +43 -0
  15. package/commands/scale-check.md +37 -0
  16. package/commands/ship-check.md +26 -0
  17. package/package.json +48 -0
  18. package/rules/cpp.json +46 -0
  19. package/rules/csharp.json +38 -0
  20. package/rules/go.json +46 -0
  21. package/rules/java.json +38 -0
  22. package/rules/javascript.json +102 -0
  23. package/rules/php.json +38 -0
  24. package/rules/python.json +62 -0
  25. package/rules/ruby.json +38 -0
  26. package/rules/rust.json +38 -0
  27. package/rules/shell.json +38 -0
  28. package/rules/sql.json +54 -0
  29. package/skills/complexity-cuts/SKILL.md +259 -0
  30. package/skills/invariant-guard/SKILL.md +310 -0
  31. package/skills/lemmaly/AGENTS.md +1869 -0
  32. package/skills/lemmaly/SKILL.md +365 -0
  33. package/skills/lemmaly/references/async.md +135 -0
  34. package/skills/lemmaly/references/complexity.md +66 -0
  35. package/skills/lemmaly/references/hot-paths.md +87 -0
  36. package/skills/lemmaly/references/memory.md +118 -0
  37. package/skills/lemmaly/references/n-plus-one.md +139 -0
  38. package/skills/lemmaly/rules/cpp-map-double-lookup.md +38 -0
  39. package/skills/lemmaly/rules/cpp-range-loop-copy.md +33 -0
  40. package/skills/lemmaly/rules/cpp-raw-new.md +36 -0
  41. package/skills/lemmaly/rules/cpp-string-concat-in-loop.md +45 -0
  42. package/skills/lemmaly/rules/cpp-vector-push-no-reserve.md +40 -0
  43. package/skills/lemmaly/rules/cs-async-void.md +45 -0
  44. package/skills/lemmaly/rules/cs-disposable-no-using.md +32 -0
  45. package/skills/lemmaly/rules/cs-list-contains-in-loop.md +36 -0
  46. package/skills/lemmaly/rules/cs-string-concat-in-loop.md +42 -0
  47. package/skills/lemmaly/rules/go-defer-in-loop.md +39 -0
  48. package/skills/lemmaly/rules/go-err-not-checked.md +38 -0
  49. package/skills/lemmaly/rules/go-loop-var-capture.md +47 -0
  50. package/skills/lemmaly/rules/go-slice-append-no-cap.md +39 -0
  51. package/skills/lemmaly/rules/go-string-concat-in-loop.md +44 -0
  52. package/skills/lemmaly/rules/java-arraylist-remove-in-for-i.md +44 -0
  53. package/skills/lemmaly/rules/java-bare-catch-exception.md +42 -0
  54. package/skills/lemmaly/rules/java-list-contains-in-loop.md +40 -0
  55. package/skills/lemmaly/rules/java-string-concat-in-loop.md +42 -0
  56. package/skills/lemmaly/rules/js-anonymous-handler-jsx.md +31 -0
  57. package/skills/lemmaly/rules/js-array-key-index.md +29 -0
  58. package/skills/lemmaly/rules/js-async-in-foreach.md +43 -0
  59. package/skills/lemmaly/rules/js-await-in-for-loop.md +41 -0
  60. package/skills/lemmaly/rules/js-deep-clone-via-json.md +33 -0
  61. package/skills/lemmaly/rules/js-helper-call-in-iterator.md +41 -0
  62. package/skills/lemmaly/rules/js-includes-in-iterator.md +37 -0
  63. package/skills/lemmaly/rules/js-inline-object-jsx-prop.md +35 -0
  64. package/skills/lemmaly/rules/js-nested-for-loops.md +45 -0
  65. package/skills/lemmaly/rules/js-spread-in-reduce.md +38 -0
  66. package/skills/lemmaly/rules/js-unique-via-indexof.md +35 -0
  67. package/skills/lemmaly/rules/js-useeffect-missing-deps.md +33 -0
  68. package/skills/lemmaly/rules/php-count-in-for-condition.md +45 -0
  69. package/skills/lemmaly/rules/php-in-array-in-loop.md +42 -0
  70. package/skills/lemmaly/rules/php-loose-equality.md +35 -0
  71. package/skills/lemmaly/rules/php-query-in-loop.md +47 -0
  72. package/skills/lemmaly/rules/py-bare-except.md +39 -0
  73. package/skills/lemmaly/rules/py-django-loop-without-eager.md +42 -0
  74. package/skills/lemmaly/rules/py-in-list-literal.md +37 -0
  75. package/skills/lemmaly/rules/py-mutable-default-arg.md +39 -0
  76. package/skills/lemmaly/rules/py-open-without-with.md +33 -0
  77. package/skills/lemmaly/rules/py-range-len.md +35 -0
  78. package/skills/lemmaly/rules/py-string-concat-in-loop.md +43 -0
  79. package/skills/lemmaly/rules/rb-bare-rescue.md +41 -0
  80. package/skills/lemmaly/rules/rb-include-in-iterator.md +37 -0
  81. package/skills/lemmaly/rules/rb-n-plus-one-activerecord.md +39 -0
  82. package/skills/lemmaly/rules/rb-string-concat-in-loop.md +39 -0
  83. package/skills/lemmaly/rules/rs-clone-in-loop.md +38 -0
  84. package/skills/lemmaly/rules/rs-string-push-no-capacity.md +43 -0
  85. package/skills/lemmaly/rules/rs-unwrap-in-prod.md +36 -0
  86. package/skills/lemmaly/rules/rs-vec-push-no-capacity.md +42 -0
  87. package/skills/lemmaly/rules/sh-for-ls.md +41 -0
  88. package/skills/lemmaly/rules/sh-set-e-no-pipefail.md +37 -0
  89. package/skills/lemmaly/rules/sh-unquoted-var.md +35 -0
  90. package/skills/lemmaly/rules/sh-useless-cat-pipe.md +32 -0
  91. package/skills/lemmaly/rules/sql-leading-wildcard-like.md +34 -0
  92. package/skills/lemmaly/rules/sql-not-in-subquery.md +38 -0
  93. package/skills/lemmaly/rules/sql-or-in-where.md +35 -0
  94. package/skills/lemmaly/rules/sql-select-no-limit.md +37 -0
  95. package/skills/lemmaly/rules/sql-select-star.md +29 -0
  96. package/skills/lemmaly/rules/sql-update-no-where.md +35 -0
  97. package/skills/mathguard/SKILL.md +277 -0
@@ -0,0 +1,38 @@
1
+ ---
2
+ id: rs-clone-in-loop
3
+ title: .clone() inside iterator — avoid if a borrow works
4
+ severity: info
5
+ impact: MEDIUM
6
+ impactDescription: Suboptimal; flag when n is large or on a hot path
7
+ language: rust
8
+ tags: rust, info, complexity-cuts
9
+ ---
10
+
11
+ # .clone() inside iterator — avoid if a borrow works
12
+
13
+ A `.clone()` inside an iterator allocates a fresh copy per element. If a borrow (`&x`) or `Rc::clone` (cheap atomic increment for shared ownership) would do, the deep clone is wasted work.
14
+
15
+ ## Fix
16
+
17
+ Borrow with &, use Rc/Arc for shared ownership, or move once outside the loop.
18
+
19
+ ## Incorrect
20
+
21
+ ```rust
22
+ // Deep clones every element
23
+ let names: Vec<String> = users.iter().map(|u| u.name.clone()).collect();
24
+ ```
25
+
26
+ ## Correct
27
+
28
+ ```rust
29
+ // Borrow when possible
30
+ let names: Vec<&str> = users.iter().map(|u| u.name.as_str()).collect();
31
+
32
+ // Cheap reference-counted clone when shared ownership is needed
33
+ let shared: Vec<Rc<String>> = users.iter().map(|u| Rc::clone(&u.name)).collect();
34
+ ```
35
+
36
+ ## Escalate to
37
+
38
+ If this pattern is widespread in the codebase, load **complexity-cuts** for the corrective workflow.
@@ -0,0 +1,43 @@
1
+ ---
2
+ id: rs-string-push-no-capacity
3
+ title: String::new() + push_str in loop — repeated reallocation
4
+ severity: info
5
+ impact: MEDIUM
6
+ impactDescription: Suboptimal; flag when n is large or on a hot path
7
+ language: rust
8
+ tags: rust, info, complexity-cuts
9
+ ---
10
+
11
+ # String::new() + push_str in loop — repeated reallocation
12
+
13
+ `String::new()` starts at capacity 0. Each `push_str` past capacity reallocates. `with_capacity` or `join` avoids it.
14
+
15
+ ## Fix
16
+
17
+ Preallocate: `String::with_capacity(n)`, or `parts.join(sep)` for known parts.
18
+
19
+ ## Incorrect
20
+
21
+ ```rust
22
+ let mut s = String::new();
23
+ for part in parts.iter() {
24
+ s.push_str(part); // reallocates as it grows
25
+ }
26
+ ```
27
+
28
+ ## Correct
29
+
30
+ ```rust
31
+ let total: usize = parts.iter().map(|p| p.len()).sum();
32
+ let mut s = String::with_capacity(total);
33
+ for part in parts.iter() {
34
+ s.push_str(part);
35
+ }
36
+
37
+ // Or, simplest:
38
+ let s = parts.join("");
39
+ ```
40
+
41
+ ## Escalate to
42
+
43
+ If this pattern is widespread in the codebase, load **complexity-cuts** for the corrective workflow.
@@ -0,0 +1,36 @@
1
+ ---
2
+ id: rs-unwrap-in-prod
3
+ title: .unwrap() / .expect() panics on None/Err — surface the error
4
+ severity: warning
5
+ impact: HIGH
6
+ impactDescription: Hot-path or correctness risk at realistic n
7
+ language: rust
8
+ tags: rust, warning, invariant-guard
9
+ ---
10
+
11
+ # .unwrap() / .expect() panics on None/Err — surface the error
12
+
13
+ `.unwrap()` and `.expect()` panic on `None`/`Err`. In production code they crash the process and lose the structured error. Rust gives you `?`, `match`, and `ok_or` to surface the error to the caller.
14
+
15
+ ## Fix
16
+
17
+ Use `?`, `match`, `unwrap_or`, `unwrap_or_else`, or `ok_or(...)?`. Reserve unwrap for tests and provably infallible paths.
18
+
19
+ ## Incorrect
20
+
21
+ ```rust
22
+ let value = map.get(&key).unwrap(); // panics if key missing
23
+ ```
24
+
25
+ ## Correct
26
+
27
+ ```rust
28
+ let value = map.get(&key).ok_or(Error::MissingKey)?;
29
+
30
+ // Or, when None has a meaningful default:
31
+ let value = map.get(&key).copied().unwrap_or_default();
32
+ ```
33
+
34
+ ## Escalate to
35
+
36
+ If this pattern is widespread in the codebase, load **invariant-guard** for the corrective workflow.
@@ -0,0 +1,42 @@
1
+ ---
2
+ id: rs-vec-push-no-capacity
3
+ title: Vec::new() + push in loop — repeated reallocation
4
+ severity: info
5
+ impact: MEDIUM
6
+ impactDescription: Suboptimal; flag when n is large or on a hot path
7
+ language: rust
8
+ tags: rust, info, complexity-cuts
9
+ ---
10
+
11
+ # Vec::new() + push in loop — repeated reallocation
12
+
13
+ `Vec::new()` starts with capacity 0; each `push` past the current capacity reallocates and copies. Preallocating with `Vec::with_capacity(n)` does one allocation.
14
+
15
+ ## Fix
16
+
17
+ Preallocate: `Vec::with_capacity(n)`, or use `.collect::<Vec<_>>()` over an iterator that has a size hint.
18
+
19
+ ## Incorrect
20
+
21
+ ```rust
22
+ let mut out = Vec::new();
23
+ for x in input.iter() {
24
+ out.push(transform(x)); // grows; reallocates log(n) times
25
+ }
26
+ ```
27
+
28
+ ## Correct
29
+
30
+ ```rust
31
+ let mut out = Vec::with_capacity(input.len());
32
+ for x in input.iter() {
33
+ out.push(transform(x));
34
+ }
35
+
36
+ // Or, when transform is pure:
37
+ let out: Vec<_> = input.iter().map(transform).collect();
38
+ ```
39
+
40
+ ## Escalate to
41
+
42
+ If this pattern is widespread in the codebase, load **complexity-cuts** for the corrective workflow.
@@ -0,0 +1,41 @@
1
+ ---
2
+ id: sh-for-ls
3
+ title: for f in $(ls ...) — breaks on filenames with spaces / newlines
4
+ severity: warning
5
+ impact: HIGH
6
+ impactDescription: Hot-path or correctness risk at realistic n
7
+ language: shell
8
+ tags: shell, warning, invariant-guard
9
+ ---
10
+
11
+ # for f in $(ls ...) — breaks on filenames with spaces / newlines
12
+
13
+ `for f in $(ls ...)` breaks on filenames with spaces, tabs, newlines, or globs. The output of `ls` is meant for humans, not programs. Use a glob or `find -print0 | xargs -0`.
14
+
15
+ ## Fix
16
+
17
+ Use glob `for f in *.txt` or `find ... -print0 | xargs -0`. Never parse `ls`.
18
+
19
+ ## Incorrect
20
+
21
+ ```shell
22
+ for f in $(ls *.txt); do
23
+ process "$f" # breaks on "my file.txt"
24
+ done
25
+ ```
26
+
27
+ ## Correct
28
+
29
+ ```shell
30
+ # Glob directly
31
+ for f in *.txt; do
32
+ process "$f"
33
+ done
34
+
35
+ # Or, when find is necessary
36
+ find . -name '*.txt' -print0 | xargs -0 -n1 process
37
+ ```
38
+
39
+ ## Escalate to
40
+
41
+ If this pattern is widespread in the codebase, load **invariant-guard** for the corrective workflow.
@@ -0,0 +1,37 @@
1
+ ---
2
+ id: sh-set-e-no-pipefail
3
+ title: set -e without set -o pipefail — failures inside pipes are masked
4
+ severity: warning
5
+ impact: HIGH
6
+ impactDescription: Hot-path or correctness risk at realistic n
7
+ language: shell
8
+ tags: shell, warning, invariant-guard
9
+ ---
10
+
11
+ # set -e without set -o pipefail — failures inside pipes are masked
12
+
13
+ `set -e` exits on a failed command, but a failure in the middle of a pipe is masked — only the *last* command's exit status counts. `set -o pipefail` fixes it. `set -u` catches unset variables.
14
+
15
+ ## Fix
16
+
17
+ Use `set -euo pipefail` so the script also fails when an earlier stage in a pipe fails.
18
+
19
+ ## Incorrect
20
+
21
+ ```shell
22
+ #!/bin/bash
23
+ set -e
24
+ some_failing_step | grep needle # if some_failing_step fails, script keeps going
25
+ ```
26
+
27
+ ## Correct
28
+
29
+ ```shell
30
+ #!/bin/bash
31
+ set -euo pipefail
32
+ some_failing_step | grep needle # any failure in the pipe aborts the script
33
+ ```
34
+
35
+ ## Escalate to
36
+
37
+ If this pattern is widespread in the codebase, load **invariant-guard** for the corrective workflow.
@@ -0,0 +1,35 @@
1
+ ---
2
+ id: sh-unquoted-var
3
+ title: Unquoted $var — word splitting and glob expansion
4
+ severity: warning
5
+ impact: HIGH
6
+ impactDescription: Hot-path or correctness risk at realistic n
7
+ language: shell
8
+ tags: shell, warning, invariant-guard
9
+ ---
10
+
11
+ # Unquoted $var — word splitting and glob expansion
12
+
13
+ An unquoted `$var` is subject to word splitting (on `$IFS`) and glob expansion. A path with a space, a tab, or a `*` will silently do the wrong thing.
14
+
15
+ ## Fix
16
+
17
+ Quote: `"$var"`. Use `"${arr[@]}"` for arrays. Required even for paths you trust.
18
+
19
+ ## Incorrect
20
+
21
+ ```shell
22
+ if [ -d $dir ]; then echo yes; fi
23
+ # If $dir = "/tmp/with space", expands to: [ -d /tmp/with space ] — syntax error
24
+ ```
25
+
26
+ ## Correct
27
+
28
+ ```shell
29
+ if [ -d "$dir" ]; then echo yes; fi
30
+ # Always quote. For arrays: "${arr[@]}".
31
+ ```
32
+
33
+ ## Escalate to
34
+
35
+ If this pattern is widespread in the codebase, load **invariant-guard** for the corrective workflow.
@@ -0,0 +1,32 @@
1
+ ---
2
+ id: sh-useless-cat-pipe
3
+ title: cat file | cmd — useless use of cat (UUOC)
4
+ severity: info
5
+ impact: MEDIUM
6
+ impactDescription: Suboptimal; flag when n is large or on a hot path
7
+ language: shell
8
+ tags: shell, info
9
+ ---
10
+
11
+ # cat file | cmd — useless use of cat (UUOC)
12
+
13
+ `cat file | cmd` reads the file and pipes through `cat` just to feed `cmd`. Every command that takes a file argument can read it directly — one fewer process, clearer intent.
14
+
15
+ ## Fix
16
+
17
+ Run the command on the file directly: `grep ... file` instead of `cat file | grep ...`.
18
+
19
+ ## Incorrect
20
+
21
+ ```shell
22
+ cat access.log | grep "500"
23
+ ```
24
+
25
+ ## Correct
26
+
27
+ ```shell
28
+ grep "500" access.log
29
+
30
+ # Or stdin redirection if the command takes only stdin:
31
+ cmd < access.log
32
+ ```
@@ -0,0 +1,34 @@
1
+ ---
2
+ id: sql-leading-wildcard-like
3
+ title: LIKE with leading wildcard — cannot use a B-tree index
4
+ severity: warning
5
+ impact: HIGH
6
+ impactDescription: Hot-path or correctness risk at realistic n
7
+ language: sql
8
+ tags: sql, warning
9
+ ---
10
+
11
+ # LIKE with leading wildcard — cannot use a B-tree index
12
+
13
+ A B-tree index sorts by prefix. `LIKE '%foo'` cannot use it — the query scans the table. Use a trigram index (Postgres `pg_trgm`), reverse the column for suffix search, or a full-text search index.
14
+
15
+ ## Fix
16
+
17
+ Use a trigram index (pg_trgm), full-text search, or a reversed-column index.
18
+
19
+ ## Incorrect
20
+
21
+ ```sql
22
+ SELECT * FROM products WHERE name LIKE '%phone';
23
+ ```
24
+
25
+ ## Correct
26
+
27
+ ```sql
28
+ -- Postgres: GIN index on trigrams
29
+ CREATE INDEX products_name_trgm ON products USING gin (name gin_trgm_ops);
30
+ SELECT * FROM products WHERE name ILIKE '%phone%';
31
+
32
+ -- Or full-text:
33
+ SELECT * FROM products WHERE to_tsvector(name) @@ to_tsquery('phone');
34
+ ```
@@ -0,0 +1,38 @@
1
+ ---
2
+ id: sql-not-in-subquery
3
+ title: NOT IN (subquery) — null-unsafe; usually slower than NOT EXISTS
4
+ severity: warning
5
+ impact: HIGH
6
+ impactDescription: Hot-path or correctness risk at realistic n
7
+ language: sql
8
+ tags: sql, warning, invariant-guard
9
+ ---
10
+
11
+ # NOT IN (subquery) — null-unsafe; usually slower than NOT EXISTS
12
+
13
+ `NOT IN (subquery)` is null-unsafe: if any row in the subquery is NULL, the whole predicate is NULL (not TRUE), and the outer row is dropped. `NOT EXISTS` is null-safe and usually has a better plan.
14
+
15
+ ## Fix
16
+
17
+ Rewrite as `WHERE NOT EXISTS (SELECT 1 FROM ... WHERE ...)`.
18
+
19
+ ## Incorrect
20
+
21
+ ```sql
22
+ SELECT * FROM orders
23
+ WHERE user_id NOT IN (SELECT id FROM banned_users);
24
+ -- One NULL in banned_users.id → returns zero rows
25
+ ```
26
+
27
+ ## Correct
28
+
29
+ ```sql
30
+ SELECT o.* FROM orders o
31
+ WHERE NOT EXISTS (
32
+ SELECT 1 FROM banned_users b WHERE b.id = o.user_id
33
+ );
34
+ ```
35
+
36
+ ## Escalate to
37
+
38
+ If this pattern is widespread in the codebase, load **invariant-guard** for the corrective workflow.
@@ -0,0 +1,35 @@
1
+ ---
2
+ id: sql-or-in-where
3
+ title: OR in WHERE — can prevent index use
4
+ severity: info
5
+ impact: MEDIUM
6
+ impactDescription: Suboptimal; flag when n is large or on a hot path
7
+ language: sql
8
+ tags: sql, info
9
+ ---
10
+
11
+ # OR in WHERE — can prevent index use
12
+
13
+ A query planner can use an index on one side of an `OR` but often not both, falling back to a sequential scan. `UNION ALL` or `IN (...)` (when both sides are equality on the same column) usually wins.
14
+
15
+ ## Fix
16
+
17
+ Equality on same column → `WHERE col IN (...)`. Otherwise consider UNION ALL.
18
+
19
+ ## Incorrect
20
+
21
+ ```sql
22
+ SELECT * FROM events
23
+ WHERE user_id = $1 OR account_id = $1;
24
+ ```
25
+
26
+ ## Correct
27
+
28
+ ```sql
29
+ SELECT * FROM events WHERE user_id = $1
30
+ UNION ALL
31
+ SELECT * FROM events WHERE account_id = $1;
32
+
33
+ -- Or, when both sides are the same column:
34
+ SELECT * FROM events WHERE user_id IN ($1, $2, $3);
35
+ ```
@@ -0,0 +1,37 @@
1
+ ---
2
+ id: sql-select-no-limit
3
+ title: SELECT without LIMIT — unbounded result set
4
+ severity: info
5
+ impact: MEDIUM
6
+ impactDescription: Suboptimal; flag when n is large or on a hot path
7
+ language: sql
8
+ tags: sql, info
9
+ ---
10
+
11
+ # SELECT without LIMIT — unbounded result set
12
+
13
+ A query with no `LIMIT` returns however many rows match. On a small table this is fine; on a growing one it eventually OOMs the client or the page. Add a bound, or paginate.
14
+
15
+ ## Fix
16
+
17
+ Add LIMIT, or paginate by id range. Unbounded reads OOM under growth.
18
+
19
+ ## Incorrect
20
+
21
+ ```sql
22
+ SELECT id, email FROM users ORDER BY created_at DESC;
23
+ ```
24
+
25
+ ## Correct
26
+
27
+ ```sql
28
+ SELECT id, email FROM users
29
+ ORDER BY created_at DESC
30
+ LIMIT 100;
31
+
32
+ -- Keyset pagination for next page:
33
+ SELECT id, email FROM users
34
+ WHERE created_at < $1
35
+ ORDER BY created_at DESC
36
+ LIMIT 100;
37
+ ```
@@ -0,0 +1,29 @@
1
+ ---
2
+ id: sql-select-star
3
+ title: SELECT * — fetches unused columns; blocks index-only scans
4
+ severity: warning
5
+ impact: HIGH
6
+ impactDescription: Hot-path or correctness risk at realistic n
7
+ language: sql
8
+ tags: sql, warning
9
+ ---
10
+
11
+ # SELECT * — fetches unused columns; blocks index-only scans
12
+
13
+ `SELECT *` fetches every column, defeating index-only scans, inflating wire traffic, and breaking downstream code when a column is added/renamed. Project only what you need.
14
+
15
+ ## Fix
16
+
17
+ Name the columns. Smaller payload and more covering-index opportunities.
18
+
19
+ ## Incorrect
20
+
21
+ ```sql
22
+ SELECT * FROM users WHERE id = $1;
23
+ ```
24
+
25
+ ## Correct
26
+
27
+ ```sql
28
+ SELECT id, email, created_at FROM users WHERE id = $1;
29
+ ```
@@ -0,0 +1,35 @@
1
+ ---
2
+ id: sql-update-no-where
3
+ title: UPDATE / DELETE without WHERE — touches every row
4
+ severity: error
5
+ impact: CRITICAL
6
+ impactDescription: Will break or scale-fail in production
7
+ language: sql
8
+ tags: sql, error, invariant-guard
9
+ ---
10
+
11
+ # UPDATE / DELETE without WHERE — touches every row
12
+
13
+ An `UPDATE` or `DELETE` without a `WHERE` clause rewrites every row in the table. In production this is an incident, not a bug.
14
+
15
+ ## Fix
16
+
17
+ Add a WHERE clause, or confirm in a comment that touching all rows is intentional.
18
+
19
+ ## Incorrect
20
+
21
+ ```sql
22
+ UPDATE users SET active = false;
23
+ DELETE FROM sessions;
24
+ ```
25
+
26
+ ## Correct
27
+
28
+ ```sql
29
+ UPDATE users SET active = false WHERE last_seen_at < NOW() - INTERVAL '90 days';
30
+ DELETE FROM sessions WHERE expires_at < NOW();
31
+ ```
32
+
33
+ ## Escalate to
34
+
35
+ If this pattern is widespread in the codebase, load **invariant-guard** for the corrective workflow.