pi-lens 3.8.21 → 3.8.23
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 +28 -0
- package/README.md +2 -0
- package/clients/dispatch/dispatcher.ts +75 -91
- package/clients/dispatch/fact-provider-types.ts +22 -0
- package/clients/dispatch/fact-rule-runner.ts +22 -0
- package/clients/dispatch/fact-runner.ts +28 -0
- package/clients/dispatch/fact-scheduler.ts +78 -0
- package/clients/dispatch/fact-store.ts +67 -0
- package/clients/dispatch/facts/comment-facts.ts +59 -0
- package/clients/dispatch/facts/file-content.ts +20 -0
- package/clients/dispatch/facts/function-facts.ts +177 -0
- package/clients/dispatch/facts/try-catch-facts.ts +80 -0
- package/clients/dispatch/integration.ts +130 -24
- package/clients/dispatch/priorities.ts +22 -0
- package/clients/dispatch/rules/async-noise.ts +43 -0
- package/clients/dispatch/rules/error-obscuring.ts +40 -0
- package/clients/dispatch/rules/error-swallowing.ts +35 -0
- package/clients/dispatch/rules/pass-through-wrappers.ts +52 -0
- package/clients/dispatch/rules/placeholder-comments.ts +47 -0
- package/clients/dispatch/runners/architect.ts +2 -1
- package/clients/dispatch/runners/ast-grep-napi.ts +2 -1
- package/clients/dispatch/runners/biome-check.ts +40 -8
- package/clients/dispatch/runners/biome.ts +2 -1
- package/clients/dispatch/runners/eslint.ts +34 -6
- package/clients/dispatch/runners/go-vet.ts +2 -1
- package/clients/dispatch/runners/golangci-lint.ts +2 -1
- package/clients/dispatch/runners/index.ts +29 -27
- package/clients/dispatch/runners/lsp.ts +60 -4
- package/clients/dispatch/runners/oxlint.ts +2 -1
- package/clients/dispatch/runners/pyright.ts +2 -1
- package/clients/dispatch/runners/python-slop.ts +2 -1
- package/clients/dispatch/runners/rubocop.ts +2 -1
- package/clients/dispatch/runners/ruff.ts +2 -1
- package/clients/dispatch/runners/rust-clippy.ts +2 -1
- package/clients/dispatch/runners/shellcheck.ts +2 -1
- package/clients/dispatch/runners/similarity.ts +2 -1
- package/clients/dispatch/runners/spellcheck.ts +2 -1
- package/clients/dispatch/runners/sqlfluff.ts +2 -1
- package/clients/dispatch/runners/tree-sitter.ts +469 -1
- package/clients/dispatch/runners/ts-lsp.ts +2 -1
- package/clients/dispatch/runners/type-safety.ts +2 -1
- package/clients/dispatch/runners/yamllint.ts +2 -1
- package/clients/dispatch/tool-profile.ts +40 -0
- package/clients/dispatch/types.ts +3 -13
- package/clients/lsp/client.ts +366 -12
- package/clients/lsp/index.ts +374 -76
- package/clients/lsp/launch.ts +42 -2
- package/clients/lsp/server.ts +186 -12
- package/clients/pipeline.ts +2 -2
- package/clients/runtime-context.ts +2 -2
- package/clients/runtime-session.ts +43 -5
- package/clients/session-summary.ts +21 -0
- package/clients/tree-sitter-client.ts +162 -0
- package/clients/tree-sitter-logger.ts +47 -0
- package/clients/tree-sitter-query-loader.ts +13 -2
- package/index.ts +67 -17
- package/package.json +3 -1
- package/rules/rule-catalog.json +64 -0
- package/rules/tree-sitter-queries/go/go-bare-error.yml +19 -7
- package/rules/tree-sitter-queries/go/go-command-injection.yml +55 -0
- package/rules/tree-sitter-queries/go/go-direct-panic.yml +45 -0
- package/rules/tree-sitter-queries/go/go-empty-if-err.yml +47 -0
- package/rules/tree-sitter-queries/go/go-goroutine-loop-capture.yml +49 -0
- package/rules/tree-sitter-queries/go/go-ignored-call-result.yml +51 -0
- package/rules/tree-sitter-queries/go/go-insecure-random.yml +51 -0
- package/rules/tree-sitter-queries/go/go-log-fatal.yml +49 -0
- package/rules/tree-sitter-queries/go/go-path-traversal.yml +51 -0
- package/rules/tree-sitter-queries/go/go-shared-map-write-goroutine.yml +54 -0
- package/rules/tree-sitter-queries/go/go-sql-injection.yml +55 -0
- package/rules/tree-sitter-queries/go/go-weak-hash.yml +51 -0
- package/rules/tree-sitter-queries/python/python-command-injection.yml +63 -0
- package/rules/tree-sitter-queries/python/python-insecure-deserialization.yml +48 -0
- package/rules/tree-sitter-queries/python/python-insecure-random.yml +51 -0
- package/rules/tree-sitter-queries/python/python-path-traversal.yml +55 -0
- package/rules/tree-sitter-queries/python/python-sql-injection.yml +47 -0
- package/rules/tree-sitter-queries/python/python-ssrf.yml +50 -0
- package/rules/tree-sitter-queries/python/python-thread-global-write.yml +58 -0
- package/rules/tree-sitter-queries/python/python-weak-hash.yml +51 -0
- package/rules/tree-sitter-queries/ruby/ruby-command-injection.yml +56 -0
- package/rules/tree-sitter-queries/ruby/ruby-insecure-deserialization.yml +47 -0
- package/rules/tree-sitter-queries/ruby/ruby-insecure-random.yml +54 -0
- package/rules/tree-sitter-queries/ruby/ruby-weak-hash.yml +50 -0
- package/rules/tree-sitter-queries/rust/rust-lock-held-across-await.yml +59 -0
- package/rules/tree-sitter-queries/typescript/ts-command-injection.yml +60 -0
- package/rules/tree-sitter-queries/typescript/ts-detached-async-call.yml +56 -0
- package/rules/tree-sitter-queries/typescript/ts-insecure-random.yml +54 -0
- package/rules/tree-sitter-queries/typescript/ts-ssrf.yml +53 -0
- package/rules/tree-sitter-queries/typescript/ts-weak-hash.yml +54 -0
- package/scripts/validate-rule-catalog.mjs +227 -0
- package/skills/lsp-navigation/SKILL.md +15 -3
- package/tools/lsp-navigation.js +466 -79
- package/tools/lsp-navigation.ts +587 -85
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { isTestFile } from "../../file-utils.js";
|
|
2
|
+
import type { FactRule } from "../fact-provider-types.js";
|
|
3
|
+
import type { CommentSummary } from "../facts/comment-facts.js";
|
|
4
|
+
import type { Diagnostic } from "../types.js";
|
|
5
|
+
|
|
6
|
+
const PLACEHOLDER_PATTERNS = [
|
|
7
|
+
/add\s+more\s+validation/i,
|
|
8
|
+
/handle\s+(additional|more)\s+cases?/i,
|
|
9
|
+
/can\s+be\s+extended\s+in\s+the\s+future/i,
|
|
10
|
+
/extend\s+this\s+(logic|function|method|handler|module)/i,
|
|
11
|
+
/customize\s+this\s+(logic|behavior|function|method|handler)/i,
|
|
12
|
+
/future\s+enhancement/i,
|
|
13
|
+
/implement\s+.+\s+here/i,
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export const placeholderCommentsRule: FactRule = {
|
|
17
|
+
id: "placeholder-comments",
|
|
18
|
+
requires: ["file.comments"],
|
|
19
|
+
appliesTo(ctx) {
|
|
20
|
+
return /\.tsx?$/.test(ctx.filePath) && !isTestFile(ctx.filePath);
|
|
21
|
+
},
|
|
22
|
+
evaluate(ctx, store) {
|
|
23
|
+
const comments = store.getFileFact<CommentSummary[]>(ctx.filePath, "file.comments");
|
|
24
|
+
if (!comments) return [];
|
|
25
|
+
|
|
26
|
+
const diagnostics: Diagnostic[] = [];
|
|
27
|
+
for (const comment of comments) {
|
|
28
|
+
const matched = PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(comment.text));
|
|
29
|
+
if (!matched) continue;
|
|
30
|
+
|
|
31
|
+
diagnostics.push({
|
|
32
|
+
id: `placeholder-comments:${ctx.filePath}:${comment.line}:1`,
|
|
33
|
+
tool: "placeholder-comments",
|
|
34
|
+
filePath: ctx.filePath,
|
|
35
|
+
line: comment.line,
|
|
36
|
+
column: 1,
|
|
37
|
+
severity: "warning",
|
|
38
|
+
semantic: "warning",
|
|
39
|
+
rule: "placeholder-comments",
|
|
40
|
+
message:
|
|
41
|
+
"Placeholder comment detected. Prefer comments that describe current behavior over future intent.",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return diagnostics;
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
RunnerDefinition,
|
|
18
18
|
RunnerResult,
|
|
19
19
|
} from "../types.js";
|
|
20
|
+
import { PRIORITY } from "../priorities.js";
|
|
20
21
|
import { readFileContent } from "./utils.js";
|
|
21
22
|
|
|
22
23
|
// Module-level singleton — loadConfig once per cwd, not on every file write
|
|
@@ -40,7 +41,7 @@ function getClient(cwd: string): ArchitectClient {
|
|
|
40
41
|
const architectRunner: RunnerDefinition = {
|
|
41
42
|
id: "architect",
|
|
42
43
|
appliesTo: ["jsts", "python", "go", "rust", "cxx", "shell", "cmake"],
|
|
43
|
-
priority:
|
|
44
|
+
priority: PRIORITY.ARCHITECTURE,
|
|
44
45
|
enabledByDefault: true,
|
|
45
46
|
skipTestFiles: true, // Skip test files - rules can be noisy there
|
|
46
47
|
|
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
RunnerDefinition,
|
|
18
18
|
RunnerResult,
|
|
19
19
|
} from "../types.js";
|
|
20
|
+
import { PRIORITY } from "../priorities.js";
|
|
20
21
|
import {
|
|
21
22
|
calculateRuleComplexity,
|
|
22
23
|
hasUnsupportedConditions,
|
|
@@ -389,7 +390,7 @@ function getAllNodes(node: any, currentDepth: number): unknown[] {
|
|
|
389
390
|
const astGrepNapiRunner: RunnerDefinition = {
|
|
390
391
|
id: "ast-grep-napi",
|
|
391
392
|
appliesTo: ["jsts"],
|
|
392
|
-
priority:
|
|
393
|
+
priority: PRIORITY.SPECIALIZED_ANALYSIS,
|
|
393
394
|
enabledByDefault: true,
|
|
394
395
|
skipTestFiles: true,
|
|
395
396
|
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
RunnerDefinition,
|
|
19
19
|
RunnerResult,
|
|
20
20
|
} from "../types.js";
|
|
21
|
+
import { PRIORITY } from "../priorities.js";
|
|
21
22
|
|
|
22
23
|
const BIOME_CONFIGS = ["biome.json", "biome.jsonc"];
|
|
23
24
|
|
|
@@ -52,12 +53,16 @@ interface BiomeDiagnostic {
|
|
|
52
53
|
tags?: string[];
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
function parseBiomeJson(
|
|
56
|
+
function parseBiomeJson(
|
|
57
|
+
raw: string,
|
|
58
|
+
filePath: string,
|
|
59
|
+
): { diagnostics: Diagnostic[]; parseError?: string } {
|
|
56
60
|
try {
|
|
57
61
|
const result = JSON.parse(raw);
|
|
58
62
|
const diagnostics: BiomeDiagnostic[] = result.diagnostics || [];
|
|
59
63
|
|
|
60
|
-
return
|
|
64
|
+
return {
|
|
65
|
+
diagnostics: diagnostics.map((d) => ({
|
|
61
66
|
id: `biome:${d.category}:${d.location.start.line}`,
|
|
62
67
|
message: d.message,
|
|
63
68
|
filePath,
|
|
@@ -67,16 +72,20 @@ function parseBiomeJson(raw: string, filePath: string): Diagnostic[] {
|
|
|
67
72
|
semantic: d.severity === "error" ? "blocking" : ("warning" as const),
|
|
68
73
|
tool: "biome",
|
|
69
74
|
rule: d.category,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
})),
|
|
76
|
+
};
|
|
77
|
+
} catch (err) {
|
|
78
|
+
return {
|
|
79
|
+
diagnostics: [],
|
|
80
|
+
parseError: err instanceof Error ? err.message : String(err),
|
|
81
|
+
};
|
|
73
82
|
}
|
|
74
83
|
}
|
|
75
84
|
|
|
76
85
|
const biomeCheckJsonRunner: RunnerDefinition = {
|
|
77
86
|
id: "biome-check-json",
|
|
78
87
|
appliesTo: ["jsts"],
|
|
79
|
-
priority:
|
|
88
|
+
priority: PRIORITY.FORMAT_AND_LINT_PRIMARY,
|
|
80
89
|
enabledByDefault: true,
|
|
81
90
|
|
|
82
91
|
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
@@ -122,13 +131,36 @@ const biomeCheckJsonRunner: RunnerDefinition = {
|
|
|
122
131
|
{ timeout: 30000, cwd },
|
|
123
132
|
);
|
|
124
133
|
|
|
125
|
-
const
|
|
134
|
+
const parsed =
|
|
126
135
|
checkResult.status === 0 || checkResult.status === 1
|
|
127
136
|
? parseBiomeJson(
|
|
128
137
|
checkResult.stdout || checkResult.stderr || "",
|
|
129
138
|
ctx.filePath,
|
|
130
139
|
)
|
|
131
|
-
: [];
|
|
140
|
+
: { diagnostics: [] as Diagnostic[] };
|
|
141
|
+
|
|
142
|
+
if (parsed.parseError) {
|
|
143
|
+
const raw = checkResult.stdout || checkResult.stderr || "";
|
|
144
|
+
const preview = raw.replace(/\s+/g, " ").slice(0, 160);
|
|
145
|
+
return {
|
|
146
|
+
status: "failed",
|
|
147
|
+
diagnostics: [
|
|
148
|
+
{
|
|
149
|
+
id: "biome:parse-error:1",
|
|
150
|
+
message: `Biome JSON parse failed: ${parsed.parseError}${preview ? ` (output preview: ${preview})` : ""}`,
|
|
151
|
+
filePath: ctx.filePath,
|
|
152
|
+
line: 1,
|
|
153
|
+
column: 1,
|
|
154
|
+
severity: "warning",
|
|
155
|
+
semantic: "warning",
|
|
156
|
+
tool: "biome",
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
semantic: "warning",
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const diagnostics = parsed.diagnostics;
|
|
132
164
|
|
|
133
165
|
// Step 2: Auto-fix (silently)
|
|
134
166
|
await safeSpawnAsync(
|
|
@@ -10,13 +10,14 @@ import type {
|
|
|
10
10
|
RunnerDefinition,
|
|
11
11
|
RunnerResult,
|
|
12
12
|
} from "../types.js";
|
|
13
|
+
import { PRIORITY } from "../priorities.js";
|
|
13
14
|
import { createBiomeParser } from "./utils/diagnostic-parsers.js";
|
|
14
15
|
import { biome } from "./utils/runner-helpers.js";
|
|
15
16
|
|
|
16
17
|
const biomeRunner: RunnerDefinition = {
|
|
17
18
|
id: "biome-lint",
|
|
18
19
|
appliesTo: ["jsts", "json"],
|
|
19
|
-
priority:
|
|
20
|
+
priority: PRIORITY.FORMAT_AND_LINT_PRIMARY,
|
|
20
21
|
enabledByDefault: true,
|
|
21
22
|
|
|
22
23
|
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
RunnerDefinition,
|
|
18
18
|
RunnerResult,
|
|
19
19
|
} from "../types.js";
|
|
20
|
+
import { PRIORITY } from "../priorities.js";
|
|
20
21
|
|
|
21
22
|
const ESLINT_CONFIGS = [
|
|
22
23
|
".eslintrc",
|
|
@@ -75,7 +76,10 @@ interface EslintFileResult {
|
|
|
75
76
|
messages: EslintMessage[];
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
function parseEslintJson(
|
|
79
|
+
function parseEslintJson(
|
|
80
|
+
raw: string,
|
|
81
|
+
filePath: string,
|
|
82
|
+
): { diagnostics: Diagnostic[]; parseError?: string } {
|
|
79
83
|
try {
|
|
80
84
|
const results: EslintFileResult[] = JSON.parse(raw);
|
|
81
85
|
const diagnostics: Diagnostic[] = [];
|
|
@@ -98,16 +102,19 @@ function parseEslintJson(raw: string, filePath: string): Diagnostic[] {
|
|
|
98
102
|
}
|
|
99
103
|
}
|
|
100
104
|
|
|
101
|
-
return diagnostics;
|
|
102
|
-
} catch {
|
|
103
|
-
return
|
|
105
|
+
return { diagnostics };
|
|
106
|
+
} catch (err) {
|
|
107
|
+
return {
|
|
108
|
+
diagnostics: [],
|
|
109
|
+
parseError: err instanceof Error ? err.message : String(err),
|
|
110
|
+
};
|
|
104
111
|
}
|
|
105
112
|
}
|
|
106
113
|
|
|
107
114
|
const eslintRunner: RunnerDefinition = {
|
|
108
115
|
id: "eslint",
|
|
109
116
|
appliesTo: ["jsts"],
|
|
110
|
-
priority:
|
|
117
|
+
priority: PRIORITY.LINT_SECONDARY,
|
|
111
118
|
enabledByDefault: true,
|
|
112
119
|
|
|
113
120
|
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
@@ -166,7 +173,28 @@ const eslintRunner: RunnerDefinition = {
|
|
|
166
173
|
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
167
174
|
}
|
|
168
175
|
|
|
169
|
-
const
|
|
176
|
+
const parsed = parseEslintJson(raw, ctx.filePath);
|
|
177
|
+
if (parsed.parseError && raw.trim().length > 0) {
|
|
178
|
+
const preview = raw.replace(/\s+/g, " ").slice(0, 160);
|
|
179
|
+
return {
|
|
180
|
+
status: "failed",
|
|
181
|
+
diagnostics: [
|
|
182
|
+
{
|
|
183
|
+
id: "eslint:parse-error:1",
|
|
184
|
+
message: `ESLint JSON parse failed: ${parsed.parseError}${preview ? ` (output preview: ${preview})` : ""}`,
|
|
185
|
+
filePath: ctx.filePath,
|
|
186
|
+
line: 1,
|
|
187
|
+
column: 1,
|
|
188
|
+
severity: "warning",
|
|
189
|
+
semantic: "warning",
|
|
190
|
+
tool: "eslint",
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
semantic: "warning",
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const diagnostics = parsed.diagnostics;
|
|
170
198
|
if (diagnostics.length === 0) {
|
|
171
199
|
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
172
200
|
}
|
|
@@ -12,11 +12,12 @@ import type {
|
|
|
12
12
|
RunnerDefinition,
|
|
13
13
|
RunnerResult,
|
|
14
14
|
} from "../types.js";
|
|
15
|
+
import { PRIORITY } from "../priorities.js";
|
|
15
16
|
|
|
16
17
|
const goVetRunner: RunnerDefinition = {
|
|
17
18
|
id: "go-vet",
|
|
18
19
|
appliesTo: ["go"],
|
|
19
|
-
priority:
|
|
20
|
+
priority: PRIORITY.SPECIALIZED_ANALYSIS,
|
|
20
21
|
enabledByDefault: true,
|
|
21
22
|
|
|
22
23
|
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
RunnerDefinition,
|
|
22
22
|
RunnerResult,
|
|
23
23
|
} from "../types.js";
|
|
24
|
+
import { PRIORITY } from "../priorities.js";
|
|
24
25
|
|
|
25
26
|
const GOLANGCI_CONFIGS = [
|
|
26
27
|
".golangci.yml",
|
|
@@ -82,7 +83,7 @@ function parseGolangciJson(raw: string, filePath: string): Diagnostic[] {
|
|
|
82
83
|
const golangciRunner: RunnerDefinition = {
|
|
83
84
|
id: "golangci-lint",
|
|
84
85
|
appliesTo: ["go"],
|
|
85
|
-
priority:
|
|
86
|
+
priority: PRIORITY.GENERAL_ANALYSIS,
|
|
86
87
|
enabledByDefault: true,
|
|
87
88
|
|
|
88
89
|
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Runner definitions for pi-lens dispatch system
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import type { RunnerRegistry } from "../types.js";
|
|
6
6
|
import architectRunner from "./architect.js";
|
|
7
7
|
import astGrepNapiRunner from "./ast-grep-napi.js";
|
|
8
8
|
import biomeRunner from "./biome.js";
|
|
@@ -28,29 +28,31 @@ import treeSitterRunner from "./tree-sitter.js";
|
|
|
28
28
|
import tsLspRunner from "./ts-lsp.js";
|
|
29
29
|
import typeSafetyRunner from "./type-safety.js";
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
31
|
+
export function registerDefaultRunners(registry: RunnerRegistry): void {
|
|
32
|
+
// Register all runners (ordered by priority)
|
|
33
|
+
// Unified LSP runner for all languages (TypeScript, Python, Go, Rust, etc.) - priority 4
|
|
34
|
+
registry.register(lspRunner); // Unified LSP type-checking for all languages (priority 4)
|
|
35
|
+
registry.register(tsLspRunner); // TypeScript type-checking (priority 5) - fallback when --lens-lsp disabled
|
|
36
|
+
registry.register(pyrightRunner); // Python type-checking (priority 5) - fallback when --lens-lsp disabled
|
|
37
|
+
registry.register(biomeCheckJsonRunner); // Biome check with JSON output for diagnostic capture (priority 9)
|
|
38
|
+
// DISABLED in post-write dispatch - ast-grep-napi can crash. Enabled via /lens-booboo plan only.
|
|
39
|
+
registry.register(astGrepNapiRunner); // TS/JS structural analysis via NAPI (priority 15, post-write disabled)
|
|
40
|
+
registry.register(biomeRunner); // Biome formatting/linting (priority 10)
|
|
41
|
+
registry.register(treeSitterRunner); // Tree-sitter structural analysis (priority 14)
|
|
42
|
+
registry.register(ruffRunner); // Python linting (priority 10)
|
|
43
|
+
registry.register(pythonSlopRunner); // Python slop via CLI (priority 25)
|
|
44
|
+
registry.register(typeSafetyRunner); // Type safety checks (priority 20)
|
|
45
|
+
registry.register(shellcheckRunner); // Shell script linting (priority 20)
|
|
46
|
+
// DISABLED: registerRunner(astGrepRunner); // Replaced by ast-grep-napi for dispatch
|
|
47
|
+
// CLI ast-grep kept for ast_grep_search/ast_grep_replace tools only
|
|
48
|
+
registry.register(similarityRunner); // Semantic reuse detection (priority 35)
|
|
49
|
+
registry.register(architectRunner); // Architectural rules (priority 40)
|
|
50
|
+
registry.register(eslintRunner); // ESLint (priority 12, jsts, config-gated)
|
|
51
|
+
registry.register(golangciRunner); // golangci-lint (priority 20, go, config-gated)
|
|
52
|
+
registry.register(rubocopRunner); // RuboCop lint (priority 10, ruby)
|
|
53
|
+
registry.register(spellcheckRunner); // Spellcheck for markdown/docs (priority 30)
|
|
54
|
+
registry.register(yamllintRunner); // YAML lint (priority 22)
|
|
55
|
+
registry.register(sqlfluffRunner); // SQL lint (priority 24)
|
|
56
|
+
registry.register(goVetRunner); // Go analysis (priority 50)
|
|
57
|
+
registry.register(rustClippyRunner); // Rust analysis (priority 50)
|
|
58
|
+
}
|
|
@@ -21,10 +21,38 @@ import type {
|
|
|
21
21
|
RunnerDefinition,
|
|
22
22
|
RunnerResult,
|
|
23
23
|
} from "../types.js";
|
|
24
|
+
import { PRIORITY } from "../priorities.js";
|
|
24
25
|
import { readFileContent } from "./utils.js";
|
|
25
26
|
|
|
26
27
|
const LSP_MAX_FILE_BYTES = RUNTIME_CONFIG.pipeline.lspMaxFileBytes;
|
|
27
28
|
const LSP_MAX_FILE_LINES = RUNTIME_CONFIG.pipeline.lspMaxFileLines;
|
|
29
|
+
const MAX_CODE_ACTION_LOOKUPS = 6;
|
|
30
|
+
const MAX_CODE_ACTION_TITLES = 3;
|
|
31
|
+
|
|
32
|
+
function normalizeActionTitle(title: string): string {
|
|
33
|
+
return title.replace(/\s+/g, " ").trim();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function buildCodeActionSuggestion(
|
|
37
|
+
actions: import("../../lsp/client.js").LSPCodeAction[],
|
|
38
|
+
): string | undefined {
|
|
39
|
+
if (!actions.length) return undefined;
|
|
40
|
+
const quickFixes = actions.filter((action) =>
|
|
41
|
+
action.kind?.startsWith("quickfix"),
|
|
42
|
+
);
|
|
43
|
+
if (!quickFixes.length) return undefined;
|
|
44
|
+
|
|
45
|
+
const titles = Array.from(
|
|
46
|
+
new Set(
|
|
47
|
+
quickFixes
|
|
48
|
+
.map((action) => normalizeActionTitle(action.title))
|
|
49
|
+
.filter((title) => title.length > 0),
|
|
50
|
+
),
|
|
51
|
+
).slice(0, MAX_CODE_ACTION_TITLES);
|
|
52
|
+
|
|
53
|
+
if (!titles.length) return undefined;
|
|
54
|
+
return `LSP quick fixes: ${titles.join("; ")}`;
|
|
55
|
+
}
|
|
28
56
|
|
|
29
57
|
const lspRunner: RunnerDefinition = {
|
|
30
58
|
id: "lsp",
|
|
@@ -42,7 +70,7 @@ const lspRunner: RunnerDefinition = {
|
|
|
42
70
|
"css",
|
|
43
71
|
"yaml",
|
|
44
72
|
],
|
|
45
|
-
priority:
|
|
73
|
+
priority: PRIORITY.LSP_PRIMARY,
|
|
46
74
|
enabledByDefault: true,
|
|
47
75
|
|
|
48
76
|
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
@@ -125,9 +153,35 @@ const lspRunner: RunnerDefinition = {
|
|
|
125
153
|
|
|
126
154
|
// Convert LSP diagnostics to our format
|
|
127
155
|
// Defensive: filter out malformed diagnostics that may lack range
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
156
|
+
const validLspDiags = lspDiags.filter((d) => d.range?.start?.line !== undefined);
|
|
157
|
+
const fixSuggestionByIndex = new Map<number, string>();
|
|
158
|
+
|
|
159
|
+
const blockingDiagIndexes = validLspDiags
|
|
160
|
+
.map((d, idx) => ({ d, idx }))
|
|
161
|
+
.filter(({ d }) => d.severity === 1)
|
|
162
|
+
.slice(0, MAX_CODE_ACTION_LOOKUPS);
|
|
163
|
+
|
|
164
|
+
for (const { d, idx } of blockingDiagIndexes) {
|
|
165
|
+
try {
|
|
166
|
+
const start = d.range.start;
|
|
167
|
+
const end = d.range.end ?? d.range.start;
|
|
168
|
+
const actions = await lspService.codeAction(
|
|
169
|
+
ctx.filePath,
|
|
170
|
+
start.line,
|
|
171
|
+
start.character,
|
|
172
|
+
end.line,
|
|
173
|
+
end.character,
|
|
174
|
+
);
|
|
175
|
+
const suggestion = buildCodeActionSuggestion(actions);
|
|
176
|
+
if (suggestion) {
|
|
177
|
+
fixSuggestionByIndex.set(idx, suggestion);
|
|
178
|
+
}
|
|
179
|
+
} catch {
|
|
180
|
+
// Best-effort enrichment only; base diagnostics remain authoritative.
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const diagnostics: Diagnostic[] = validLspDiags.map((d, idx) => ({
|
|
131
185
|
id: `lsp:${d.code ?? "unknown"}:${d.range.start.line}`,
|
|
132
186
|
message: d.message,
|
|
133
187
|
filePath: diagnosticPath,
|
|
@@ -143,6 +197,8 @@ const lspRunner: RunnerDefinition = {
|
|
|
143
197
|
: "none",
|
|
144
198
|
tool: "lsp",
|
|
145
199
|
code: String(d.code ?? ""),
|
|
200
|
+
fixable: fixSuggestionByIndex.has(idx),
|
|
201
|
+
fixSuggestion: fixSuggestionByIndex.get(idx),
|
|
146
202
|
}));
|
|
147
203
|
|
|
148
204
|
const hasErrors = diagnostics.some((d) => d.semantic === "blocking");
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
RunnerDefinition,
|
|
15
15
|
RunnerResult,
|
|
16
16
|
} from "../types.js";
|
|
17
|
+
import { PRIORITY } from "../priorities.js";
|
|
17
18
|
import { createAvailabilityChecker } from "./utils/runner-helpers.js";
|
|
18
19
|
|
|
19
20
|
const oxlint = createAvailabilityChecker("oxlint", ".exe");
|
|
@@ -21,7 +22,7 @@ const oxlint = createAvailabilityChecker("oxlint", ".exe");
|
|
|
21
22
|
const oxlintRunner: RunnerDefinition = {
|
|
22
23
|
id: "oxlint",
|
|
23
24
|
appliesTo: ["jsts"],
|
|
24
|
-
priority:
|
|
25
|
+
priority: PRIORITY.LINT_SECONDARY,
|
|
25
26
|
enabledByDefault: false, // Opt-in: may conflict with ESLint in existing projects
|
|
26
27
|
skipTestFiles: true,
|
|
27
28
|
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
RunnerDefinition,
|
|
17
17
|
RunnerResult,
|
|
18
18
|
} from "../types.js";
|
|
19
|
+
import { PRIORITY } from "../priorities.js";
|
|
19
20
|
import { createAvailabilityChecker } from "./utils/runner-helpers.js";
|
|
20
21
|
|
|
21
22
|
const pyright = createAvailabilityChecker("pyright", ".exe");
|
|
@@ -23,7 +24,7 @@ const pyright = createAvailabilityChecker("pyright", ".exe");
|
|
|
23
24
|
const pyrightRunner: RunnerDefinition = {
|
|
24
25
|
id: "pyright",
|
|
25
26
|
appliesTo: ["python"],
|
|
26
|
-
priority:
|
|
27
|
+
priority: PRIORITY.LSP_FALLBACK,
|
|
27
28
|
enabledByDefault: true,
|
|
28
29
|
|
|
29
30
|
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
RunnerDefinition,
|
|
19
19
|
RunnerResult,
|
|
20
20
|
} from "../types.js";
|
|
21
|
+
import { PRIORITY } from "../priorities.js";
|
|
21
22
|
import {
|
|
22
23
|
createConfigFinder,
|
|
23
24
|
getSgCommand,
|
|
@@ -29,7 +30,7 @@ const findSlopConfig = createConfigFinder("python-slop-rules");
|
|
|
29
30
|
const pythonSlopRunner: RunnerDefinition = {
|
|
30
31
|
id: "python-slop",
|
|
31
32
|
appliesTo: ["python"],
|
|
32
|
-
priority:
|
|
33
|
+
priority: PRIORITY.PYTHON_SLOP,
|
|
33
34
|
enabledByDefault: true,
|
|
34
35
|
skipTestFiles: true, // Slop rules can be noisy in test files
|
|
35
36
|
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
RunnerDefinition,
|
|
19
19
|
RunnerResult,
|
|
20
20
|
} from "../types.js";
|
|
21
|
+
import { PRIORITY } from "../priorities.js";
|
|
21
22
|
|
|
22
23
|
function findRubocop(cwd: string): { cmd: string; args: string[] } {
|
|
23
24
|
// Prefer bundle exec if Gemfile exists
|
|
@@ -93,7 +94,7 @@ function parseRubocopJson(raw: string, filePath: string): Diagnostic[] {
|
|
|
93
94
|
const rubocopRunner: RunnerDefinition = {
|
|
94
95
|
id: "rubocop",
|
|
95
96
|
appliesTo: ["ruby"],
|
|
96
|
-
priority:
|
|
97
|
+
priority: PRIORITY.FORMAT_AND_LINT_PRIMARY,
|
|
97
98
|
enabledByDefault: true,
|
|
98
99
|
|
|
99
100
|
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
RunnerDefinition,
|
|
15
15
|
RunnerResult,
|
|
16
16
|
} from "../types.js";
|
|
17
|
+
import { PRIORITY } from "../priorities.js";
|
|
17
18
|
import { parseRuffOutput } from "./utils/diagnostic-parsers.js";
|
|
18
19
|
import { createAvailabilityChecker } from "./utils/runner-helpers.js";
|
|
19
20
|
|
|
@@ -55,7 +56,7 @@ function parseRuffJson(raw: string, filePath: string): Diagnostic[] {
|
|
|
55
56
|
const ruffRunner: RunnerDefinition = {
|
|
56
57
|
id: "ruff-lint",
|
|
57
58
|
appliesTo: ["python"],
|
|
58
|
-
priority:
|
|
59
|
+
priority: PRIORITY.FORMAT_AND_LINT_PRIMARY,
|
|
59
60
|
enabledByDefault: true,
|
|
60
61
|
|
|
61
62
|
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
@@ -15,11 +15,12 @@ import type {
|
|
|
15
15
|
RunnerDefinition,
|
|
16
16
|
RunnerResult,
|
|
17
17
|
} from "../types.js";
|
|
18
|
+
import { PRIORITY } from "../priorities.js";
|
|
18
19
|
|
|
19
20
|
const rustClippyRunner: RunnerDefinition = {
|
|
20
21
|
id: "rust-clippy",
|
|
21
22
|
appliesTo: ["rust"],
|
|
22
|
-
priority:
|
|
23
|
+
priority: PRIORITY.SPECIALIZED_ANALYSIS,
|
|
23
24
|
enabledByDefault: true,
|
|
24
25
|
|
|
25
26
|
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
@@ -30,6 +30,7 @@ import type {
|
|
|
30
30
|
RunnerDefinition,
|
|
31
31
|
RunnerResult,
|
|
32
32
|
} from "../types.js";
|
|
33
|
+
import { PRIORITY } from "../priorities.js";
|
|
33
34
|
|
|
34
35
|
const shellcheck = createAvailabilityChecker("shellcheck", ".exe");
|
|
35
36
|
|
|
@@ -129,7 +130,7 @@ function parseShellcheckOutput(raw: string, filePath: string): Diagnostic[] {
|
|
|
129
130
|
const shellcheckRunner: RunnerDefinition = {
|
|
130
131
|
id: "shellcheck",
|
|
131
132
|
appliesTo: ["shell"],
|
|
132
|
-
priority:
|
|
133
|
+
priority: PRIORITY.GENERAL_ANALYSIS,
|
|
133
134
|
enabledByDefault: true,
|
|
134
135
|
skipTestFiles: false, // Shell scripts in test directories should still be checked
|
|
135
136
|
|
|
@@ -24,6 +24,7 @@ import type {
|
|
|
24
24
|
RunnerDefinition,
|
|
25
25
|
RunnerResult,
|
|
26
26
|
} from "../types.js";
|
|
27
|
+
import { PRIORITY } from "../priorities.js";
|
|
27
28
|
|
|
28
29
|
// Singleton Rust client — initialised once, reused across runner invocations.
|
|
29
30
|
const rustClient = new NativeRustCoreClient();
|
|
@@ -97,7 +98,7 @@ export function hasMeaningfulNameOverlap(sourceName: string, targetName: string)
|
|
|
97
98
|
const similarityRunner: RunnerDefinition = {
|
|
98
99
|
id: "similarity",
|
|
99
100
|
appliesTo: ["jsts"], // TypeScript/JavaScript only for MVP
|
|
100
|
-
priority:
|
|
101
|
+
priority: PRIORITY.SIMILARITY,
|
|
101
102
|
enabledByDefault: true,
|
|
102
103
|
|
|
103
104
|
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
@@ -27,6 +27,7 @@ import type {
|
|
|
27
27
|
RunnerDefinition,
|
|
28
28
|
RunnerResult,
|
|
29
29
|
} from "../types.js";
|
|
30
|
+
import { PRIORITY } from "../priorities.js";
|
|
30
31
|
|
|
31
32
|
const typos = createAvailabilityChecker("typos", ".exe");
|
|
32
33
|
|
|
@@ -91,7 +92,7 @@ function parseTyposOutput(raw: string, filePath: string): Diagnostic[] {
|
|
|
91
92
|
const spellcheckRunner: RunnerDefinition = {
|
|
92
93
|
id: "spellcheck",
|
|
93
94
|
appliesTo: ["markdown"],
|
|
94
|
-
priority:
|
|
95
|
+
priority: PRIORITY.DOC_QUALITY,
|
|
95
96
|
enabledByDefault: true,
|
|
96
97
|
skipTestFiles: false, // Check docs in test files too
|
|
97
98
|
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
RunnerDefinition,
|
|
10
10
|
RunnerResult,
|
|
11
11
|
} from "../types.js";
|
|
12
|
+
import { PRIORITY } from "../priorities.js";
|
|
12
13
|
|
|
13
14
|
const sqlfluff = createAvailabilityChecker("sqlfluff", ".exe");
|
|
14
15
|
|
|
@@ -90,7 +91,7 @@ function parseSqlfluffOutput(raw: string, filePath: string): Diagnostic[] {
|
|
|
90
91
|
const sqlfluffRunner: RunnerDefinition = {
|
|
91
92
|
id: "sqlfluff",
|
|
92
93
|
appliesTo: ["sql"],
|
|
93
|
-
priority:
|
|
94
|
+
priority: PRIORITY.SQL_LINT,
|
|
94
95
|
enabledByDefault: true,
|
|
95
96
|
skipTestFiles: false,
|
|
96
97
|
|