pi-lens 3.7.1 → 3.8.2
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/CHANGELOG.md +217 -0
- package/README.md +706 -619
- package/clients/architect-client.ts +7 -2
- package/clients/ast-grep-client.ts +7 -1
- package/clients/dispatch/plan.ts +10 -4
- package/clients/dispatch/runners/architect.ts +20 -7
- package/clients/dispatch/runners/ast-grep-napi.ts +5 -2
- package/clients/dispatch/runners/ast-grep.ts +29 -18
- package/clients/dispatch/runners/biome.ts +4 -4
- package/clients/dispatch/runners/python-slop.ts +17 -7
- package/clients/dispatch/runners/ruff.ts +4 -4
- package/clients/dispatch/runners/tree-sitter.ts +30 -19
- package/clients/dispatch/runners/ts-slop.ts +17 -7
- package/clients/dispatch/runners/utils/runner-helpers.ts +76 -8
- package/clients/dispatch/utils/format-utils.ts +2 -1
- package/clients/fix-scanners.ts +8 -8
- package/clients/installer/index.ts +19 -1
- package/clients/lsp/index.ts +0 -40
- package/clients/lsp/launch.ts +5 -2
- package/clients/package-root.ts +44 -0
- package/clients/pipeline.ts +179 -8
- package/clients/scan-utils.ts +20 -32
- package/clients/sg-runner.ts +7 -5
- package/clients/source-filter.ts +222 -0
- package/clients/startup-scan.ts +142 -0
- package/clients/todo-scanner.ts +44 -55
- package/clients/tree-sitter-cache.ts +315 -0
- package/clients/tree-sitter-client.ts +208 -52
- package/clients/tree-sitter-fixer.ts +217 -0
- package/clients/tree-sitter-navigator.ts +329 -0
- package/clients/tree-sitter-query-loader.ts +55 -32
- package/commands/booboo.ts +47 -35
- package/default-architect.yaml +76 -87
- package/docs/ARCHITECTURE.md +74 -0
- package/docs/AST_GREP_RULES.md +266 -0
- package/docs/COMPLEXITY_METRICS.md +120 -0
- package/docs/EXCLUSIONS.md +83 -0
- package/docs/LSP_CONFIG.md +240 -0
- package/docs/TREE_SITTER_RULES.md +340 -0
- package/docs/WRITING_NEW_AST_GREP_RULES.md +200 -0
- package/index.ts +209 -86
- package/package.json +13 -4
- package/rules/ast-grep-rules/rules/array-callback-return-js.yml +33 -0
- package/rules/ast-grep-rules/rules/array-callback-return.yml +1 -1
- package/rules/ast-grep-rules/rules/constructor-super-js.yml +22 -0
- package/rules/ast-grep-rules/rules/empty-catch-js.yml +45 -0
- package/rules/ast-grep-rules/rules/empty-catch.yml +1 -1
- package/rules/ast-grep-rules/rules/getter-return-js.yml +59 -0
- package/rules/ast-grep-rules/rules/getter-return.yml +1 -1
- package/rules/ast-grep-rules/rules/hardcoded-url-js.yml +12 -0
- package/rules/ast-grep-rules/rules/jsx-boolean-short-circuit.yml +1 -1
- package/rules/ast-grep-rules/rules/jwt-no-verify-js.yml +14 -0
- package/rules/ast-grep-rules/rules/missed-concurrency-js.yml +25 -0
- package/rules/ast-grep-rules/rules/nested-ternary-js.yml +10 -0
- package/rules/ast-grep-rules/rules/no-alert-js.yml +6 -0
- package/rules/ast-grep-rules/rules/no-architecture-violation.yml +21 -18
- package/rules/ast-grep-rules/rules/no-array-constructor-js.yml +10 -0
- package/rules/ast-grep-rules/rules/no-async-promise-executor-js.yml +15 -0
- package/rules/ast-grep-rules/rules/no-async-promise-executor.yml +1 -1
- package/rules/ast-grep-rules/rules/no-await-in-loop-js.yml +30 -0
- package/rules/ast-grep-rules/rules/no-await-in-promise-all-js.yml +20 -0
- package/rules/ast-grep-rules/rules/no-await-in-promise-all.yml +1 -1
- package/rules/ast-grep-rules/rules/no-bare-except.yml +1 -1
- package/rules/ast-grep-rules/rules/no-case-declarations-js.yml +16 -0
- package/rules/ast-grep-rules/rules/no-compare-neg-zero-js.yml +13 -0
- package/rules/ast-grep-rules/rules/no-compare-neg-zero.yml +1 -1
- package/rules/ast-grep-rules/rules/no-comparison-to-none.yml +1 -1
- package/rules/ast-grep-rules/rules/no-cond-assign-js.yml +36 -0
- package/rules/ast-grep-rules/rules/no-cond-assign.yml +1 -1
- package/rules/ast-grep-rules/rules/no-constant-condition-js.yml +25 -0
- package/rules/ast-grep-rules/rules/no-constant-condition.yml +1 -1
- package/rules/ast-grep-rules/rules/no-constructor-return-js.yml +28 -0
- package/rules/ast-grep-rules/rules/no-constructor-return.yml +1 -1
- package/rules/ast-grep-rules/rules/no-discarded-error-js.yml +25 -0
- package/rules/ast-grep-rules/rules/no-discarded-error.yml +25 -0
- package/rules/ast-grep-rules/rules/no-dupe-args-js.yml +15 -0
- package/rules/ast-grep-rules/rules/no-dupe-keys-js.yml +73 -0
- package/rules/ast-grep-rules/rules/no-extra-boolean-cast-js.yml +25 -0
- package/rules/ast-grep-rules/rules/no-hardcoded-secrets-js.yml +17 -0
- package/rules/ast-grep-rules/rules/no-implied-eval-js.yml +15 -0
- package/rules/ast-grep-rules/rules/no-inner-html-js.yml +13 -0
- package/rules/ast-grep-rules/rules/no-insecure-randomness-js.yml +20 -0
- package/rules/ast-grep-rules/rules/no-insecure-randomness.yml +1 -1
- package/rules/ast-grep-rules/rules/no-javascript-url-js.yml +11 -0
- package/rules/ast-grep-rules/rules/no-nan-comparison-js.yml +22 -0
- package/rules/ast-grep-rules/rules/no-nan-comparison.yml +22 -0
- package/rules/ast-grep-rules/rules/no-new-symbol-js.yml +8 -0
- package/rules/ast-grep-rules/rules/no-new-wrappers-js.yml +13 -0
- package/rules/ast-grep-rules/rules/no-open-redirect-js.yml +15 -0
- package/rules/ast-grep-rules/rules/no-prototype-builtins-js.yml +15 -0
- package/rules/ast-grep-rules/rules/no-prototype-builtins.yml +1 -1
- package/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +13 -0
- package/rules/ast-grep-rules/rules/no-sql-in-code.yml +1 -1
- package/rules/ast-grep-rules/rules/no-throw-string-js.yml +12 -0
- package/rules/ast-grep-rules/rules/no-throw-string.yml +1 -1
- package/rules/ast-grep-rules/rules/strict-equality-js.yml +10 -0
- package/rules/ast-grep-rules/rules/strict-inequality-js.yml +10 -0
- package/rules/ast-grep-rules/rules/toctou-js.yml +112 -0
- package/rules/ast-grep-rules/rules/toctou.yml +1 -1
- package/rules/ast-grep-rules/rules/unchecked-sync-fs-js.yml +44 -0
- package/rules/ast-grep-rules/rules/unchecked-sync-fs.yml +44 -0
- package/rules/ast-grep-rules/rules/unchecked-throwing-call-js.yml +31 -0
- package/rules/ast-grep-rules/rules/unchecked-throwing-call-python.yml +48 -0
- package/rules/ast-grep-rules/rules/unchecked-throwing-call-ruby.yml +47 -0
- package/rules/ast-grep-rules/rules/unchecked-throwing-call.yml +31 -0
- package/rules/ast-grep-rules/rules/weak-rsa-key-js.yml +15 -0
- package/rules/tree-sitter-queries/go/go-bare-error.yml +47 -0
- package/rules/tree-sitter-queries/go/go-defer-in-loop.yml +47 -0
- package/rules/tree-sitter-queries/go/go-hardcoded-secrets.yml +54 -0
- package/rules/tree-sitter-queries/python/is-vs-equals.yml +1 -1
- package/rules/tree-sitter-queries/python/python-debugger.yml +46 -0
- package/rules/tree-sitter-queries/python/python-empty-except.yml +48 -0
- package/rules/tree-sitter-queries/python/python-hardcoded-secrets.yml +44 -0
- package/rules/tree-sitter-queries/python/python-mutable-class-attr.yml +57 -0
- package/rules/tree-sitter-queries/python/python-print-statement.yml +53 -0
- package/rules/tree-sitter-queries/python/python-raise-string.yml +38 -0
- package/rules/tree-sitter-queries/python/python-unsafe-regex.yml +58 -0
- package/rules/tree-sitter-queries/ruby/ruby-debugger.yml +44 -0
- package/rules/tree-sitter-queries/ruby/ruby-empty-rescue.yml +47 -0
- package/rules/tree-sitter-queries/ruby/ruby-eval.yml +43 -0
- package/rules/tree-sitter-queries/ruby/ruby-hardcoded-secrets.yml +40 -0
- package/rules/tree-sitter-queries/ruby/ruby-open-struct.yml +48 -0
- package/rules/tree-sitter-queries/ruby/ruby-puts-statement.yml +52 -0
- package/rules/tree-sitter-queries/ruby/ruby-rescue-exception.yml +51 -0
- package/rules/tree-sitter-queries/ruby/ruby-unsafe-regex.yml +49 -0
- package/rules/tree-sitter-queries/rust/rust-clone-in-loop.yml +49 -0
- package/rules/tree-sitter-queries/rust/rust-unwrap.yml +45 -0
- package/rules/tree-sitter-queries/typescript/console-statement.yml +3 -3
- package/rules/tree-sitter-queries/typescript/hardcoded-secrets.yml +13 -27
- package/rules/tree-sitter-queries/typescript/injections.scm +40 -0
- package/rules/tree-sitter-queries/typescript/no-console-in-tests.yml +52 -0
- package/rules/tree-sitter-queries/typescript/sql-injection.yml +55 -0
- package/rules/tree-sitter-queries/typescript/unsafe-regex.yml +71 -0
- package/rules/tree-sitter-queries/typescript/variable-shadowing.yml +51 -0
- package/scripts/download-grammars.ts +78 -0
package/commands/booboo.ts
CHANGED
|
@@ -24,6 +24,10 @@ import {
|
|
|
24
24
|
import { RunnerTracker } from "../clients/runner-tracker.js";
|
|
25
25
|
import { safeSpawn } from "../clients/safe-spawn.js";
|
|
26
26
|
import { getSourceFiles } from "../clients/scan-utils.js";
|
|
27
|
+
import {
|
|
28
|
+
collectSourceFiles,
|
|
29
|
+
getFilterStats,
|
|
30
|
+
} from "../clients/source-filter.js";
|
|
27
31
|
import { calculateSimilarity } from "../clients/state-matrix.js";
|
|
28
32
|
import type { TodoScanner } from "../clients/todo-scanner.js";
|
|
29
33
|
import { TreeSitterClient } from "../clients/tree-sitter-client.js";
|
|
@@ -89,6 +93,35 @@ export async function handleBooboo(
|
|
|
89
93
|
// Detect project type once for all runners
|
|
90
94
|
const isTsProject = nodeFs.existsSync(path.join(targetPath, "tsconfig.json"));
|
|
91
95
|
|
|
96
|
+
// Collect source files once with unified artifact filtering
|
|
97
|
+
// This ensures all scanners work on the same deduplicated file set
|
|
98
|
+
const sourceFiles = collectSourceFiles(targetPath);
|
|
99
|
+
const allFiles = collectSourceFiles(targetPath, {
|
|
100
|
+
extensions: [
|
|
101
|
+
".ts",
|
|
102
|
+
".tsx",
|
|
103
|
+
".js",
|
|
104
|
+
".jsx",
|
|
105
|
+
".mjs",
|
|
106
|
+
".cjs",
|
|
107
|
+
".py",
|
|
108
|
+
".go",
|
|
109
|
+
".rs",
|
|
110
|
+
".rb",
|
|
111
|
+
],
|
|
112
|
+
});
|
|
113
|
+
const filterStats = getFilterStats(allFiles, sourceFiles);
|
|
114
|
+
|
|
115
|
+
if (filterStats.skipped > 0) {
|
|
116
|
+
const byTypeStr = Object.entries(filterStats.byType)
|
|
117
|
+
.map(([ext, count]) => `${count} ${ext}`)
|
|
118
|
+
.join(", ");
|
|
119
|
+
// biome-ignore lint/suspicious/noConsole: CLI output
|
|
120
|
+
console.log(
|
|
121
|
+
`[lens-booboo] Filtered ${filterStats.skipped} build artifacts (${byTypeStr}), scanning ${filterStats.kept} source files`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
92
125
|
// Get available commands for the project
|
|
93
126
|
const availableCommands = getAvailableCommands(projectMeta);
|
|
94
127
|
|
|
@@ -390,9 +423,8 @@ export async function handleBooboo(
|
|
|
390
423
|
const results: import("../clients/complexity-client.js").FileComplexity[] =
|
|
391
424
|
[];
|
|
392
425
|
const aiSlopIssues: string[] = [];
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
);
|
|
426
|
+
// Use pre-collected sourceFiles (already filtered for artifacts)
|
|
427
|
+
const files = sourceFiles.filter(shouldIncludeFile);
|
|
396
428
|
|
|
397
429
|
for (const fullPath of files) {
|
|
398
430
|
if (clients.complexity.isSupportedFile(fullPath)) {
|
|
@@ -937,40 +969,20 @@ export async function handleBooboo(
|
|
|
937
969
|
return { findings: 0, status: "skipped" };
|
|
938
970
|
}
|
|
939
971
|
|
|
940
|
-
// Detect TypeScript project - skip .js files in TS projects (compiled artifacts)
|
|
941
|
-
const isTsProject = nodeFs.existsSync(
|
|
942
|
-
path.join(targetPath, "tsconfig.json"),
|
|
943
|
-
);
|
|
944
|
-
|
|
945
972
|
const archViolations: Array<{ file: string; message: string }> = [];
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
if (
|
|
956
|
-
isTsProject &&
|
|
957
|
-
/\.(js|jsx)$/.test(entry.name) &&
|
|
958
|
-
nodeFs.existsSync(full.replace(/\.(js|jsx)$/, ".ts"))
|
|
959
|
-
)
|
|
960
|
-
continue;
|
|
961
|
-
const relPath = path.relative(targetPath, full).replace(/\\/g, "/");
|
|
962
|
-
const content = nodeFs.readFileSync(full, "utf-8");
|
|
963
|
-
const lineCount = content.split("\n").length;
|
|
964
|
-
for (const v of clients.architect.checkFile(relPath, content)) {
|
|
965
|
-
archViolations.push({ file: relPath, message: v.message });
|
|
966
|
-
}
|
|
967
|
-
const sizeV = clients.architect.checkFileSize(relPath, lineCount);
|
|
968
|
-
if (sizeV)
|
|
969
|
-
archViolations.push({ file: relPath, message: sizeV.message });
|
|
970
|
-
}
|
|
973
|
+
|
|
974
|
+
// Use pre-collected sourceFiles (already filtered for artifacts and exclusions)
|
|
975
|
+
for (const fullPath of sourceFiles) {
|
|
976
|
+
if (isTestFile(fullPath)) continue;
|
|
977
|
+
const relPath = path.relative(targetPath, fullPath).replace(/\\/g, "/");
|
|
978
|
+
const content = nodeFs.readFileSync(fullPath, "utf-8");
|
|
979
|
+
const lineCount = content.split("\n").length;
|
|
980
|
+
for (const v of clients.architect.checkFile(relPath, content)) {
|
|
981
|
+
archViolations.push({ file: relPath, message: v.message });
|
|
971
982
|
}
|
|
972
|
-
|
|
973
|
-
|
|
983
|
+
const sizeV = clients.architect.checkFileSize(relPath, lineCount);
|
|
984
|
+
if (sizeV) archViolations.push({ file: relPath, message: sizeV.message });
|
|
985
|
+
}
|
|
974
986
|
|
|
975
987
|
if (archViolations.length > 0) {
|
|
976
988
|
summaryItems.push({
|
package/default-architect.yaml
CHANGED
|
@@ -1,141 +1,130 @@
|
|
|
1
1
|
# .pi-lens/architect.yaml
|
|
2
|
-
# Architectural rules for your project
|
|
3
|
-
# Copy to your project root as .pi-lens/architect.yaml and
|
|
2
|
+
# Architectural rules for your project.
|
|
3
|
+
# Copy to your project root as .pi-lens/architect.yaml and customise.
|
|
4
4
|
#
|
|
5
|
-
#
|
|
5
|
+
# Patterns use JavaScript regex syntax (multiline mode enabled).
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
7
|
+
# NOTE: Rules here are intentionally non-overlapping with pi-lens built-in runners:
|
|
8
|
+
# - Empty catch blocks -> tree-sitter + ast-grep handle these
|
|
9
|
+
# - Hardcoded secrets -> secrets scanner handles these
|
|
10
|
+
# - as any / :any -> ast-grep no-any-type/no-as-any handle these
|
|
9
11
|
|
|
10
12
|
version: "1.2"
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
#
|
|
14
|
+
rules:
|
|
15
|
+
|
|
16
|
+
# ===========================================================================
|
|
17
|
+
# FILE SIZE LIMITS
|
|
18
|
+
# ===========================================================================
|
|
15
19
|
|
|
16
20
|
# Services: focused, single-purpose modules
|
|
17
21
|
- pattern: "**/services/**/*.ts"
|
|
18
22
|
max_lines: 500
|
|
19
23
|
must_not:
|
|
20
|
-
# TUNED: Was (8+ spaces, 5 lines), now (12+ spaces, 10 lines)
|
|
21
24
|
- pattern: '(?:\s{12,}.*\n){10,}'
|
|
22
|
-
message: "Extreme nesting
|
|
25
|
+
message: "Extreme nesting (6+ levels) — refactor into smaller functions."
|
|
23
26
|
|
|
24
|
-
# Clients: can be larger but still bounded
|
|
27
|
+
# Clients/commands: can be larger but still bounded
|
|
25
28
|
- pattern: "**/clients/**/*.ts|**/commands/**/*.ts"
|
|
26
29
|
max_lines: 1000
|
|
27
30
|
must_not:
|
|
28
|
-
# TUNED: Was (8+ spaces, 5 lines), now (12+ spaces, 10 lines)
|
|
29
31
|
- pattern: '(?:\s{12,}.*\n){10,}'
|
|
30
|
-
message: "Extreme nesting
|
|
32
|
+
message: "Extreme nesting (6+ levels) — refactor into smaller functions."
|
|
31
33
|
|
|
32
|
-
# General
|
|
34
|
+
# General source files
|
|
33
35
|
- pattern: "**/*.{ts,tsx,js,jsx,py,go,rs}"
|
|
34
36
|
max_lines: 3000
|
|
35
37
|
must_not:
|
|
36
|
-
# TUNED: Was (8+ spaces, 5 lines), now (12+ spaces, 10 lines) for extreme nesting only
|
|
37
38
|
- pattern: '(?:\s{12,}.*\n){10,}'
|
|
38
|
-
message: "Extreme nesting
|
|
39
|
+
message: "Extreme nesting (6+ levels) — refactor into smaller functions."
|
|
39
40
|
|
|
40
|
-
# Test files: more relaxed
|
|
41
|
+
# Test files: more relaxed
|
|
41
42
|
- pattern: "**/*.test.ts|**/*.test.js|**/*.spec.ts|**/*.spec.js"
|
|
42
43
|
max_lines: 5000
|
|
43
44
|
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
45
|
+
# ===========================================================================
|
|
46
|
+
# UNIVERSAL RULES
|
|
47
|
+
# ===========================================================================
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
# --- CI & Environment Safety ---
|
|
49
|
+
# CI & cross-platform safety
|
|
50
50
|
- pattern: "**/*.{ts,tsx,js,jsx,py,go,rs,java,cpp}"
|
|
51
51
|
must_not:
|
|
52
52
|
- pattern: '[a-zA-Z]:\\(?:Users|Program Files|Windows|Temp)\\[^\\]*'
|
|
53
53
|
message: "No absolute Windows paths — breaks CI and cross-platform builds."
|
|
54
54
|
fix: "Use path.join(__dirname, 'relative/path') or process.cwd()"
|
|
55
55
|
- pattern: '/(?:home|Users|usr|etc|var)/[a-zA-Z0-9_-]+/'
|
|
56
|
-
message: "
|
|
56
|
+
message: "Absolute Unix path — use relative paths or path.join()."
|
|
57
57
|
fix: "Use path.join(process.cwd(), 'relative/path') or path.resolve()"
|
|
58
58
|
- pattern: 'https?://localhost:[0-9]+'
|
|
59
|
-
message: "No hardcoded localhost URLs — use environment
|
|
59
|
+
message: "No hardcoded localhost URLs — use an environment variable."
|
|
60
60
|
fix: "Use process.env.API_URL || 'http://localhost:3000'"
|
|
61
61
|
|
|
62
|
-
#
|
|
62
|
+
# Technical debt
|
|
63
63
|
- pattern: "**/*.{ts,tsx,js,jsx,py,go,rs}"
|
|
64
64
|
must_not:
|
|
65
65
|
- pattern: '(?:(?://|#).*\n){10,}'
|
|
66
|
-
message: "Large
|
|
67
|
-
fix: "Delete the commented code. Git
|
|
66
|
+
message: "Large commented-out code block — delete it and rely on Git history."
|
|
67
|
+
fix: "Delete the commented code. Git preserves it if needed."
|
|
68
68
|
|
|
69
|
-
#
|
|
70
|
-
-
|
|
71
|
-
|
|
72
|
-
- pattern: '(?:catch|except)\s*\(?.*?\)?\s*\{\s*\}'
|
|
73
|
-
message: "No empty catch/except blocks. Swallowing errors makes debugging impossible — at least log the error."
|
|
74
|
-
fix: |
|
|
75
|
-
console.error(`[context] Operation failed:`, err);
|
|
76
|
-
// or: throw err;
|
|
77
|
-
// or: return null;
|
|
78
|
-
- pattern: '\b(?:password|secret|api_?key|token|private_?key)\b\s*[:=]\s*(?:"|'')[^''"]{8,}(?:"|'')'
|
|
79
|
-
message: "No hardcoded secrets — use environment variables or a secrets manager."
|
|
80
|
-
fix: "Use process.env.SECRET_NAME and add to .env.example (not .env!)"
|
|
81
|
-
|
|
82
|
-
# =============================================================================
|
|
83
|
-
# JS/TS-SPECIFIC RULES (Node.js & Frontend)
|
|
84
|
-
# =============================================================================
|
|
85
|
-
|
|
86
|
-
# --- Type Safety ---
|
|
87
|
-
- pattern: "**/*.{ts,tsx}"
|
|
88
|
-
must_not:
|
|
89
|
-
- pattern: ':\s*any\b|as\s+any\b'
|
|
90
|
-
message: "No 'any' types — use 'unknown' or define a proper interface to maintain type safety."
|
|
91
|
-
fix: |
|
|
92
|
-
// Instead of: x as any
|
|
93
|
-
x as unknown as SpecificType
|
|
94
|
-
// Or define proper interface and use it
|
|
95
|
-
must:
|
|
96
|
-
- "Use strict TypeScript mode"
|
|
97
|
-
|
|
98
|
-
# --- Configuration & IO ---
|
|
99
|
-
# REMOVED: no process.env rule - extensions and CLI tools need env access
|
|
100
|
-
# If you want this rule for your project, add it to your .pi-lens/architect.yaml:
|
|
101
|
-
# - pattern: "**/services/**/*.ts|**/domain/**/*.ts"
|
|
102
|
-
# must_not:
|
|
103
|
-
# - pattern: 'process\.env'
|
|
104
|
-
# message: "Domain/Service logic must not read env vars directly — inject config."
|
|
69
|
+
# ===========================================================================
|
|
70
|
+
# JS/TS-SPECIFIC RULES
|
|
71
|
+
# ===========================================================================
|
|
105
72
|
|
|
106
|
-
#
|
|
107
|
-
# DISABLED: .then() rule was too aggressive - flagged all promise usage
|
|
108
|
-
# Only flag in specific contexts (3+ level chains) via tree-sitter instead
|
|
109
|
-
# - pattern: "**/*.{ts,tsx,js,jsx}"
|
|
110
|
-
# must_not:
|
|
111
|
-
# - pattern: '\.then\('
|
|
112
|
-
# message: "Prefer async/await over .then() chains for better readability."
|
|
113
|
-
|
|
114
|
-
# --- Grep-ability & Agent Search ---
|
|
115
|
-
# Note: 'export default' is acceptable for entry points (index.ts)
|
|
73
|
+
# Deep relative imports hurt readability and agent code search
|
|
116
74
|
- pattern: "**/*.{ts,tsx}"
|
|
117
75
|
must_not:
|
|
118
|
-
- pattern: "from\\s+['\"]
|
|
119
|
-
message: "
|
|
76
|
+
- pattern: "from\\s+['\"](\\.\\./){3,}"
|
|
77
|
+
message: "Deep relative import (3+ levels) — use path aliases instead."
|
|
78
|
+
fix: "Configure tsconfig paths: { '@app/*': ['src/*'] }"
|
|
120
79
|
|
|
121
|
-
#
|
|
122
|
-
# PYTHON-SPECIFIC RULES
|
|
123
|
-
#
|
|
80
|
+
# ===========================================================================
|
|
81
|
+
# PYTHON-SPECIFIC RULES
|
|
82
|
+
# ===========================================================================
|
|
124
83
|
|
|
125
|
-
# --- Clean Python ---
|
|
126
84
|
- pattern: "**/*.py"
|
|
127
85
|
must_not:
|
|
128
86
|
- pattern: 'print\('
|
|
129
|
-
message: "No print()
|
|
87
|
+
message: "No print() in production code — use the 'logging' module."
|
|
130
88
|
- pattern: 'global\s+[a-zA-Z_]'
|
|
131
|
-
message: "No 'global' keyword — global state makes code untestable
|
|
89
|
+
message: "No 'global' keyword — global state makes code untestable."
|
|
132
90
|
- pattern: 'def\s+[a-zA-Z0-9_]+\([^)]*=\s*\[\]'
|
|
133
|
-
message: "
|
|
91
|
+
message: "Mutable default argument (x=[]) — use x=None and initialise inside."
|
|
92
|
+
- pattern: 'eval\(|exec\('
|
|
93
|
+
message: "No eval()/exec() — security risk and architectural anti-pattern."
|
|
94
|
+
|
|
95
|
+
# ===========================================================================
|
|
96
|
+
# AGENT-SPECIFIC ANTI-PATTERNS
|
|
97
|
+
# Catches common failure modes where agents scaffold but forget to implement.
|
|
98
|
+
# Test files are excluded by the architect runner (skipTestFiles: true).
|
|
99
|
+
# ===========================================================================
|
|
100
|
+
|
|
101
|
+
- pattern: "**/*.{ts,tsx,js,jsx}"
|
|
102
|
+
must_not:
|
|
103
|
+
- pattern: 'throw new Error\((?:"(?:not implemented|TODO|todo|Not implemented)"|''(?:not implemented|TODO|todo|Not implemented)'')\)'
|
|
104
|
+
message: "Stub not implemented — function body was scaffolded but never completed."
|
|
105
|
+
fix: "Implement the function body or remove the stub."
|
|
134
106
|
|
|
135
|
-
# --- Python Reliability ---
|
|
136
107
|
- pattern: "**/*.py"
|
|
137
108
|
must_not:
|
|
138
|
-
- pattern: '
|
|
139
|
-
message: "
|
|
140
|
-
|
|
141
|
-
|
|
109
|
+
- pattern: 'raise NotImplementedError'
|
|
110
|
+
message: "NotImplementedError stub — function was scaffolded but never completed."
|
|
111
|
+
fix: "Implement the function body."
|
|
112
|
+
- pattern: '^\s*\.\.\.\s*$'
|
|
113
|
+
message: "Bare ellipsis body — function was scaffolded but never completed."
|
|
114
|
+
fix: "Implement the function body."
|
|
115
|
+
|
|
116
|
+
# ===========================================================================
|
|
117
|
+
# PROJECT-SPECIFIC TEMPLATES (uncomment and customise)
|
|
118
|
+
# ===========================================================================
|
|
119
|
+
|
|
120
|
+
# Layer boundary enforcement — adjust paths to match your project:
|
|
121
|
+
# - pattern: "**/domain/**/*.ts|**/services/**/*.ts"
|
|
122
|
+
# must_not:
|
|
123
|
+
# - pattern: "from\\s+['\"].*/(controllers|routes|api)/"
|
|
124
|
+
# message: "Domain/service layer must not import from API layer."
|
|
125
|
+
#
|
|
126
|
+
# No direct env access in domain layer:
|
|
127
|
+
# - pattern: "**/domain/**/*.ts"
|
|
128
|
+
# must_not:
|
|
129
|
+
# - pattern: 'process\.env'
|
|
130
|
+
# message: "Domain logic must not read env vars — inject config via constructor."
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Caching Architecture
|
|
2
|
+
|
|
3
|
+
pi-lens uses a multi-layer caching strategy to avoid redundant work across sessions.
|
|
4
|
+
|
|
5
|
+
## Cache Layers
|
|
6
|
+
|
|
7
|
+
### 1. Tool Availability Cache
|
|
8
|
+
|
|
9
|
+
**Location:** `clients/tool-availability.ts`
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Map<toolName, {available, version}>
|
|
13
|
+
• Persisted for session lifetime
|
|
14
|
+
• Refreshed on extension restart
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Avoids repeated `which`/`where` calls for tools like `biome`, `ruff`, `pyright`.
|
|
18
|
+
|
|
19
|
+
### 2. Dispatch Baselines (Delta Mode)
|
|
20
|
+
|
|
21
|
+
**Location:** `clients/dispatch/dispatcher.ts`
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
Map<filePath, Diagnostic[]>
|
|
25
|
+
• Cleared at turn start
|
|
26
|
+
• Updated after each runner execution
|
|
27
|
+
• Filters: only NEW issues shown
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
First edit shows all issues; subsequent edits only show issues that weren't there before.
|
|
31
|
+
|
|
32
|
+
### 3. Client-Level Caches
|
|
33
|
+
|
|
34
|
+
| Client | Cache | TTL | Purpose |
|
|
35
|
+
|--------|-------|-----|---------|
|
|
36
|
+
| **Knip** | `clients/cache-manager.ts` | 5 min | Dead code analysis |
|
|
37
|
+
| **jscpd** | `clients/cache-manager.ts` | 5 min | Duplicate detection |
|
|
38
|
+
| **Type Coverage** | In-memory | Session | `any` type percentage |
|
|
39
|
+
| **Complexity** | In-memory | File-level | MI, cognitive complexity |
|
|
40
|
+
|
|
41
|
+
### 4. Session Turn State
|
|
42
|
+
|
|
43
|
+
**Location:** `clients/cache-manager.ts`
|
|
44
|
+
|
|
45
|
+
Tracks per-turn state:
|
|
46
|
+
- Modified files this turn
|
|
47
|
+
- Modified line ranges per file
|
|
48
|
+
- Import changes detected
|
|
49
|
+
- Turn cycle counter (max 10)
|
|
50
|
+
|
|
51
|
+
Used by:
|
|
52
|
+
- jscpd: Only re-scan modified files
|
|
53
|
+
- Madge: Only check deps if imports changed
|
|
54
|
+
- Cycle detection: Prevents infinite fix loops
|
|
55
|
+
|
|
56
|
+
### 5. Tree-sitter Caches
|
|
57
|
+
|
|
58
|
+
| Component | Location | Strategy | Details |
|
|
59
|
+
|-----------|----------|----------|---------|
|
|
60
|
+
| **TreeCache** | `clients/tree-sitter-cache.ts` | SHA-256 content hash + mtime | Parsed ASTs cached by file content; 50-file LRU; mtime check for invalidation |
|
|
61
|
+
| **Query Cache** | `clients/tree-sitter-client.ts` | In-memory Map | Compiled tree-sitter queries cached per language |
|
|
62
|
+
| **Navigator** | `clients/tree-sitter-navigator.ts` | Runtime scope detection | Parent/sibling traversal, test block detection, try-catch detection |
|
|
63
|
+
|
|
64
|
+
**TreeCache implementation:**
|
|
65
|
+
- SHA-256 hashing of file content for cache keys
|
|
66
|
+
- mtime tracking for fast invalidation checks
|
|
67
|
+
- LRU eviction when cache exceeds 50 files
|
|
68
|
+
- `incrementalUpdate()` API ready for full incremental parsing when old content is tracked
|
|
69
|
+
|
|
70
|
+
## Cache Invalidation
|
|
71
|
+
|
|
72
|
+
- **Tool caches:** Refreshed on extension restart
|
|
73
|
+
- **File caches:** Invalidated by mtime change
|
|
74
|
+
- **Turn state:** Reset at each turn start
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# AST-Grep Rules Reference
|
|
2
|
+
|
|
3
|
+
pi-lens uses **ast-grep** for fast structural pattern matching across multiple languages. This document describes all 112 rules (56 unique patterns × 2 languages: TypeScript and JavaScript).
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
**What ast-grep catches:**
|
|
8
|
+
- **Security vulnerabilities** — Hardcoded secrets, SQL injection, unsafe regex, JWT without verification
|
|
9
|
+
- **Runtime errors** — NaN comparison, discarded errors, unchecked throwing calls, TOCTOU
|
|
10
|
+
- **Code quality** — Empty catch blocks, missing returns, long methods, deep nesting
|
|
11
|
+
- **Best practices** — Strict equality, proper error handling, no debugger statements
|
|
12
|
+
|
|
13
|
+
**How it works:**
|
|
14
|
+
1. Patterns are written in YAML with AST matchers
|
|
15
|
+
2. Runs via `@ast-grep/napi` (fast Rust core) or CLI ast-grep
|
|
16
|
+
3. Severity determines blocking vs warning behavior
|
|
17
|
+
4. Auto-fix available for some rules (applied by Biome/Ruff/ESLint, not ast-grep directly)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Rule Categories
|
|
22
|
+
|
|
23
|
+
### 🔴 Security (Blocking Errors)
|
|
24
|
+
|
|
25
|
+
Rules that detect vulnerabilities exploitable by attackers or guaranteed runtime crashes.
|
|
26
|
+
|
|
27
|
+
| Rule | Languages | What it catches | Severity |
|
|
28
|
+
|------|-----------|-----------------|----------|
|
|
29
|
+
| **no-hardcoded-secrets** | TS, JS | API keys, passwords, tokens hardcoded in source | 🔴 error |
|
|
30
|
+
| **no-sql-in-code** | TS, JS | SQL queries built with string concatenation | 🔴 error |
|
|
31
|
+
| **jwt-no-verify** | TS, JS | JWT verification without secret/key (accepts any token) | 🔴 error |
|
|
32
|
+
| **weak-rsa-key** | TS, JS | RSA keys < 2048 bits (cryptographically weak) | 🔴 error |
|
|
33
|
+
| **no-insecure-randomness** | TS, JS | `Math.random()` for security (predictable) | 🔴 error |
|
|
34
|
+
| **no-inner-html** | TS, JS | `innerHTML` assignment (XSS risk) | 🔴 error |
|
|
35
|
+
| **unchecked-sync-fs** | TS, JS | `fs.statSync/readFileSync` without try/catch | 🔴 error |
|
|
36
|
+
| **unchecked-throwing-call** | TS, JS | `JSON.parse`, `new URL()`, `execSync` without try/catch | 🔴 error |
|
|
37
|
+
| **unchecked-throwing-call-python** | Python | `open()`, `json.loads()` without try/except | 🔴 error |
|
|
38
|
+
| **unchecked-throwing-call-ruby** | Ruby | `File.read`, `JSON.parse` without begin/rescue | 🔴 error |
|
|
39
|
+
| **no-nan-comparison** | TS, JS | `x === NaN` (always false, use `Number.isNaN()`) | 🔴 error |
|
|
40
|
+
| **no-discarded-error** | TS, JS | `new Error()` as standalone statement (forgot throw) | 🔴 error |
|
|
41
|
+
| **toctou** | TS, JS | Time-of-check-time-of-use race conditions | 🔴 error |
|
|
42
|
+
| **no-throw-string** | TS, JS | `throw "string"` (loses stack trace) | 🔴 error |
|
|
43
|
+
| **no-prototype-builtins** | TS, JS | Calling prototype methods directly on objects | 🔴 error |
|
|
44
|
+
|
|
45
|
+
### 🔴 Structural Safety (Blocking Errors)
|
|
46
|
+
|
|
47
|
+
| Rule | Languages | What it catches | Severity |
|
|
48
|
+
|------|-----------|-----------------|----------|
|
|
49
|
+
| **empty-catch** | TS, JS | `catch {}` silently swallowing errors | 🔴 error |
|
|
50
|
+
| **no-bare-except** | Python | `except:` catching all exceptions including SystemExit | 🔴 error |
|
|
51
|
+
| **no-cond-assign** | TS, JS | `if (x = y)` assignment in condition | 🔴 error |
|
|
52
|
+
| **no-constant-condition** | TS, JS | `if (true)` or always-true/false conditions | 🔴 error |
|
|
53
|
+
| **no-constructor-return** | TS, JS | `return` statement in constructor | 🔴 error |
|
|
54
|
+
| **no-async-promise-executor** | TS, JS | `new Promise(async () => {})` (error swallowing) | 🔴 error |
|
|
55
|
+
| **no-await-in-promise-all** | TS, JS | `await` inside `Promise.all()` (sequentializes parallel work) | 🔴 error |
|
|
56
|
+
| **no-compare-neg-zero** | TS, JS | `x === -0` (doesn't work as expected) | 🔴 error |
|
|
57
|
+
| **no-comparison-to-none** | Python | `== None` instead of `is None` | 🔴 error |
|
|
58
|
+
|
|
59
|
+
### 🟡 Code Quality (Warnings)
|
|
60
|
+
|
|
61
|
+
| Rule | Languages | What it catches | Severity |
|
|
62
|
+
|------|-----------|-----------------|----------|
|
|
63
|
+
| **no-console** | TS, JS | `console.log` in production code | 🟡 warning |
|
|
64
|
+
| **no-debugger** | TS, JS | `debugger` statements left in code | 🟡 warning |
|
|
65
|
+
| **no-alert** | TS, JS | `alert()`, `confirm()`, `prompt()` (poor UX) | 🟡 warning |
|
|
66
|
+
| **strict-equality** | TS, JS | `==` instead of `===` | 🟡 warning |
|
|
67
|
+
| **no-await-in-loop** | TS, JS | Sequential await in loops (slow) | 🟡 warning |
|
|
68
|
+
| **missed-concurrency** | TS, JS | Sequential awaits that could be parallel | 🟡 warning |
|
|
69
|
+
| **no-as-any** | TS, JS | `as any` type assertions (unsafe) | 🟡 warning |
|
|
70
|
+
| **no-any-type** | TS | `any` type usage (loses type safety) | 🟡 warning |
|
|
71
|
+
| **long-method** | TS, JS | Functions > 50 lines | 🟡 warning |
|
|
72
|
+
| **long-parameter-list** | TS, JS | Functions with > 5 parameters | 🟡 warning |
|
|
73
|
+
| **nested-ternary** | TS, JS | Ternary nesting > 2 levels | 🟡 warning |
|
|
74
|
+
| **large-class** | TS, JS | Classes > 300 lines | 🟡 warning |
|
|
75
|
+
| **array-callback-return** | TS, JS | Missing return in array callbacks | 🔴 error |
|
|
76
|
+
| **getter-return** | TS, JS | Getter without return statement | 🔴 error |
|
|
77
|
+
| **no-case-declarations** | TS, JS | Variables declared in switch cases | 🟡 warning |
|
|
78
|
+
| **no-array-constructor** | TS, JS | `new Array()` (inconsistent behavior) | 🟡 warning |
|
|
79
|
+
| **jsx-boolean-short-circuit** | TS, JS | `{condition && <Component />}` (0 renders as 0) | 🔴 error |
|
|
80
|
+
| **no-unsafe-optional-chaining** | TS, JS | `a?.b.c` where `a?.b` could be undefined | 🔴 error |
|
|
81
|
+
| **no-unsafe-finally** | TS, JS | `return/throw/break/continue` in finally | 🔴 error |
|
|
82
|
+
|
|
83
|
+
### 🟡 Python-Specific
|
|
84
|
+
|
|
85
|
+
| Rule | What it catches | Severity |
|
|
86
|
+
|------|-----------------|----------|
|
|
87
|
+
| **no-bare-except** | `except:` without exception type | 🔴 error |
|
|
88
|
+
| **no-comparison-to-none** | `== None` instead of `is None` | 🔴 error |
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Severity Philosophy
|
|
93
|
+
|
|
94
|
+
| Level | Signal | Examples |
|
|
95
|
+
|-------|--------|----------|
|
|
96
|
+
| **error** | 🔴 STOP | Security bugs, runtime crashes, NaN comparison, unguarded throwing calls |
|
|
97
|
+
| **warning** | 🟡 | Style issues, readability, performance hints, console/debugger statements |
|
|
98
|
+
|
|
99
|
+
**Why severity matters:**
|
|
100
|
+
- **Errors block the agent** — Must be fixed before proceeding
|
|
101
|
+
- **Warnings accumulate** — Shown in `/lens-booboo` but don't block
|
|
102
|
+
- **Delta tracking** — Only NEW issues shown after first write
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Rule Details
|
|
107
|
+
|
|
108
|
+
### unchecked-throwing-call (Error)
|
|
109
|
+
|
|
110
|
+
**Catches:** `JSON.parse()`, `new URL()`, `execSync()`, `spawnSync()` without try/catch
|
|
111
|
+
|
|
112
|
+
**Why it matters:** These calls throw on invalid input. Without try/catch, they crash the process.
|
|
113
|
+
|
|
114
|
+
**Pattern:**
|
|
115
|
+
```yaml
|
|
116
|
+
rule:
|
|
117
|
+
any:
|
|
118
|
+
- pattern: JSON.parse($INPUT)
|
|
119
|
+
- pattern: new URL($URL)
|
|
120
|
+
- pattern: execSync($CMD)
|
|
121
|
+
not:
|
|
122
|
+
inside:
|
|
123
|
+
kind: try_statement
|
|
124
|
+
stopBy: end # Check all ancestors, not just immediate parent
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Fix:** Wrap in try/catch with proper error handling.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### no-hardcoded-secrets (Error)
|
|
132
|
+
|
|
133
|
+
**Catches:** Hardcoded credentials in variable assignments
|
|
134
|
+
|
|
135
|
+
**Matches:** Variable names matching credential patterns:
|
|
136
|
+
- `password`, `passwd`, `pwd`
|
|
137
|
+
- `secret`, `token`, `apiKey`, `api_secret`
|
|
138
|
+
- `accessKey`, `privateKey`, `clientSecret`
|
|
139
|
+
- `credentials`, `bearer`, `auth`
|
|
140
|
+
|
|
141
|
+
**Pattern:**
|
|
142
|
+
```yaml
|
|
143
|
+
rule:
|
|
144
|
+
any:
|
|
145
|
+
- pattern: const $VAR = "$_"
|
|
146
|
+
- pattern: const $VAR = '$_'
|
|
147
|
+
constraints:
|
|
148
|
+
VAR:
|
|
149
|
+
regex: "(password|secret|token|apiKey|...)"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Fix:** Use environment variables: `const apiKey = process.env.API_KEY`
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
### no-nan-comparison (Error)
|
|
157
|
+
|
|
158
|
+
**Catches:** `x === NaN` or `x == NaN`
|
|
159
|
+
|
|
160
|
+
**Why it matters:** `NaN === NaN` is always `false`. Use `Number.isNaN(x)` instead.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### no-discarded-error (Error)
|
|
165
|
+
|
|
166
|
+
**Catches:** `new Error("...")` as a standalone statement
|
|
167
|
+
|
|
168
|
+
**Why it matters:** Creates an Error object but doesn't throw it. Usually means `throw` was forgotten.
|
|
169
|
+
|
|
170
|
+
**Fix:** Change to `throw new Error("...")`
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### toctou (Error)
|
|
175
|
+
|
|
176
|
+
**Catches:** Time-of-check-time-of-use race conditions in file operations
|
|
177
|
+
|
|
178
|
+
**Pattern:** `fs.existsSync(path)` followed by `fs.readFileSync(path)` — file could be modified between check and use.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
### empty-catch (Error)
|
|
183
|
+
|
|
184
|
+
**Catches:** `catch (e) { }` with empty body
|
|
185
|
+
|
|
186
|
+
**Why it matters:** Swallows errors silently. Makes debugging impossible.
|
|
187
|
+
|
|
188
|
+
**Fix:** Handle the error or remove try/catch if truly don't care.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
### strict-equality (Warning)
|
|
193
|
+
|
|
194
|
+
**Catches:** `==` and `!=` (loose equality)
|
|
195
|
+
|
|
196
|
+
**Why it matters:** `0 == "0"` is true, `0 == []` is true, `"" == false` is true. Type coercion causes bugs.
|
|
197
|
+
|
|
198
|
+
**Fix:** Use `===` and `!==` always.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
### no-await-in-loop (Warning)
|
|
203
|
+
|
|
204
|
+
**Catches:** `await` inside `for` loops
|
|
205
|
+
|
|
206
|
+
**Why it matters:** Sequentializes operations that could run in parallel. Slow.
|
|
207
|
+
|
|
208
|
+
**Fix:** Use `Promise.all(array.map(async item => ...))`
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
### missed-concurrency (Warning)
|
|
213
|
+
|
|
214
|
+
**Catches:** Sequential independent awaits
|
|
215
|
+
|
|
216
|
+
**Pattern:**
|
|
217
|
+
```typescript
|
|
218
|
+
const a = await fetchA(); // Sequential
|
|
219
|
+
const b = await fetchB(); // Doesn't need a's result
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Fix:** `const [a, b] = await Promise.all([fetchA(), fetchB()])`
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## JavaScript Coverage
|
|
227
|
+
|
|
228
|
+
Most TypeScript rules have JavaScript equivalents (`-js.yml` suffix) because:
|
|
229
|
+
1. Different AST node types (TypeScript has `type_annotation`, `interface`, etc.)
|
|
230
|
+
2. JavaScript projects need the same protections
|
|
231
|
+
3. TS and JS run in separate passes
|
|
232
|
+
|
|
233
|
+
**Total rules:** 112 (56 unique patterns × 2 languages)
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Custom Rules
|
|
238
|
+
|
|
239
|
+
Add your own rules to `.pi-lens/rules/`:
|
|
240
|
+
|
|
241
|
+
```yaml
|
|
242
|
+
# .pi-lens/rules/no-fetch-without-timeout.yml
|
|
243
|
+
id: no-fetch-without-timeout
|
|
244
|
+
language: typescript
|
|
245
|
+
severity: warning
|
|
246
|
+
message: "fetch() without timeout can hang indefinitely"
|
|
247
|
+
rule:
|
|
248
|
+
pattern: fetch($URL)
|
|
249
|
+
not:
|
|
250
|
+
inside:
|
|
251
|
+
pattern: Promise.race([fetch($URL), $TIMEOUT])
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Tips:**
|
|
255
|
+
- Use `$VAR` for single node capture
|
|
256
|
+
- Use `$$$VAR` for multi-node capture
|
|
257
|
+
- Use `not: inside:` for negative context
|
|
258
|
+
- Test with `ast-grep scan --rule your-rule.yml`
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## References
|
|
263
|
+
|
|
264
|
+
- [ast-grep documentation](https://ast-grep.github.io/)
|
|
265
|
+
- [AST patterns guide](https://ast-grep.github.io/guide/rule-syntax.html)
|
|
266
|
+
- Source: `rules/ast-grep-rules/rules/*.yml`
|