pi-lens 3.8.44 → 3.8.45
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 +2425 -2349
- package/README.md +70 -42
- package/clients/ast-grep-client.ts +5 -5
- package/clients/cache/rule-cache.ts +2 -1
- package/clients/dispatch/dispatcher.ts +49 -0
- package/clients/dispatch/integration.ts +75 -2
- package/clients/dispatch/plan.ts +5 -2
- package/clients/dispatch/runners/cpp-check.ts +73 -6
- package/clients/dispatch/runners/go-vet.ts +7 -7
- package/clients/dispatch/runners/index.ts +4 -0
- package/clients/dispatch/runners/python-slop.ts +2 -2
- package/clients/dispatch/runners/rust-clippy.ts +9 -9
- package/clients/dispatch/runners/swiftlint.ts +129 -0
- package/clients/dispatch/runners/tree-sitter.ts +164 -54
- package/clients/dispatch/runners/utils/runner-helpers.ts +76 -25
- package/clients/dispatch/runners/vale.ts +175 -0
- package/clients/dispatch/types.ts +3 -2
- package/clients/feature-hints.ts +79 -0
- package/clients/file-role.ts +7 -0
- package/clients/go-client.ts +1 -1
- package/clients/installer/index.ts +45 -0
- package/clients/knip-client.ts +39 -4
- package/clients/language-policy.ts +13 -3
- package/clients/language-profile.ts +21 -0
- package/clients/latency-logger.ts +2 -0
- package/clients/lens-config.ts +94 -0
- package/clients/lsp/client.ts +5 -1
- package/clients/lsp/index.ts +128 -16
- package/clients/lsp/server-strategies.ts +6 -3
- package/clients/lsp/server.ts +168 -16
- package/clients/read-expansion.ts +72 -6
- package/clients/read-guard.ts +148 -17
- package/clients/review-graph/builder.ts +314 -36
- package/clients/review-graph/query.ts +55 -10
- package/clients/review-graph/service.ts +12 -8
- package/clients/review-graph/workspace-modules.ts +482 -0
- package/clients/runtime-session.ts +18 -21
- package/clients/runtime-turn.ts +101 -56
- package/clients/rust-client.ts +1 -1
- package/clients/sg-runner.ts +50 -47
- package/clients/source-groups.ts +140 -0
- package/clients/test-runner-client.ts +37 -2
- package/clients/tool-policy.ts +26 -3
- package/clients/tree-sitter-client.ts +153 -0
- package/clients/tree-sitter-query-loader.ts +23 -7
- package/clients/tree-sitter-symbol-extractor.ts +54 -0
- package/index.ts +36 -20
- package/package.json +3 -1
- package/rules/tree-sitter-queries/c/hardcoded-secrets.yml +55 -0
- package/rules/tree-sitter-queries/c/memset-sensitive-data.yml +50 -0
- package/rules/tree-sitter-queries/c/no-bit-fields.yml +48 -0
- package/rules/tree-sitter-queries/c/no-octal-literals.yml +46 -0
- package/rules/tree-sitter-queries/c/no-pointer-arithmetic-array-access.yml +50 -0
- package/rules/tree-sitter-queries/c/no-redundant-pointer-ops.yml +57 -0
- package/rules/tree-sitter-queries/c/no-reserved-identifiers.yml +49 -0
- package/rules/tree-sitter-queries/c/no-stdlib-name-as-id.yml +53 -0
- package/rules/tree-sitter-queries/c/non-case-label-in-switch.yml +56 -0
- package/rules/tree-sitter-queries/c/noreturn-returns.yml +60 -0
- package/scripts/analyze-pi-lens-logs.mjs +1015 -0
- package/skills/ast-grep/SKILL.md +17 -14
- package/skills/lsp-navigation/SKILL.md +45 -30
- package/tools/ast-grep-search.js +9 -6
- package/tools/ast-grep-search.ts +14 -6
- package/tools/lsp-diagnostics.js +228 -48
- package/tools/lsp-diagnostics.ts +382 -64
- package/tools/lsp-navigation.js +128 -8
- package/tools/lsp-navigation.ts +193 -10
- /package/rules/tree-sitter-queries/{c → c-disabled}/case-range-multiple-values.yml +0 -0
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ pi-lens focuses on real-time inline code feedback for AI agents.
|
|
|
13
13
|
On every `write` and `edit`, pi-lens runs a fast, language-aware pipeline (checks depend on file language, project config, and installed tools):
|
|
14
14
|
|
|
15
15
|
1. **Secrets scan** — blocking; aborts the write if credentials are detected
|
|
16
|
-
2. **Auto-format** — deferred to `agent_end` by default; queued files are formatted once after all agent tool calls complete. Use `--immediate-format` for per-edit formatting
|
|
16
|
+
2. **Auto-format** — deferred to `agent_end` by default; queued files are formatted once after all agent tool calls complete. Use `--immediate-format` or global config `format.mode: "immediate"` for per-edit formatting
|
|
17
17
|
3. **Auto-fix** — safe autofixes from 6 tools (Biome `check --write`, Ruff `check --fix`, ESLint `--fix`, stylelint `--fix`, sqlfluff `fix`, RuboCop `-a`) applied before analysis
|
|
18
18
|
4. **LSP file sync** — opens/updates the file in active language servers
|
|
19
19
|
5. **Dispatch lint** — parallel runner groups: LSP diagnostics, tree-sitter structural rules, ast-grep security/correctness rules, fact rules, language-specific linters, experimental Semgrep security scans, similarity detection
|
|
@@ -83,13 +83,15 @@ pi-lens includes **37 language server definitions**. LSP is **enabled by default
|
|
|
83
83
|
{ "warmFiles": ["src/main.cpp", "src/lib.cpp"] }
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
LSP
|
|
86
|
+
**Agent LSP tools:** `lsp_diagnostics` can check one file, a directory, or an explicit `filePaths` batch with bounded concurrency. `lsp_navigation` provides definitions, references, hover, workspace symbols, call hierarchy, rename edits, and `findSymbol` for filtered document-symbol lookup.
|
|
87
|
+
|
|
88
|
+
LSP servers for: TypeScript, Deno, Python (pyright/basedpyright + jedi), Go, Rust, Ruby (ruby-lsp + solargraph), PHP, C# (omnisharp), F#, Java, Kotlin, Swift, Dart, Lua, C/C++, Zig, Haskell, Elixir, Gleam, OCaml, Clojure, Terraform, Nix, Bash, Docker, YAML, JSON, HTML, TOML, Prisma, Vue, Svelte, ESLint, CSS.
|
|
87
89
|
|
|
88
90
|
### Formatters
|
|
89
91
|
|
|
90
|
-
pi-lens auto-detects and runs **
|
|
92
|
+
pi-lens auto-detects and runs **32 formatters** based on project config:
|
|
91
93
|
|
|
92
|
-
biome, prettier, ruff, black, sqlfluff, gofmt, rustfmt, zig fmt, dart format, shfmt, nixfmt, mix format, ocamlformat, clang-format, ktlint, rubocop, standardrb, gleam format, terraform fmt, php-cs-fixer, csharpier, fantomas, swiftformat, stylua, ormolu, taplo
|
|
94
|
+
biome, prettier, oxfmt, ruff, black, sqlfluff, gofmt, rustfmt, zig fmt, dart format, shfmt, nixfmt, mix format, ocamlformat, clang-format, ktlint, rubocop, standardrb, gleam format, terraform fmt, php-cs-fixer, csharpier, fantomas, swiftformat, stylua, ormolu, taplo, fish_indent, google-java-format, cljfmt, cmake-format, psscriptanalyzer-format
|
|
93
95
|
|
|
94
96
|
Detection rules:
|
|
95
97
|
|
|
@@ -110,7 +112,7 @@ pi-lens enforces a **read-before-edit** policy on all file writes and edits. Bef
|
|
|
110
112
|
- **File-modified block** — blocks if the file changed on disk since the last read (auto-format, external tool, or a previous edit that was then reformatted)
|
|
111
113
|
- **Out-of-range block** — blocks if the edit target lines fall outside the ranges previously read, ensuring the agent cannot modify code it hasn't seen
|
|
112
114
|
|
|
113
|
-
Coverage is tracked across multiple reads: two reads of lines 1–100 and 101–200 together satisfy a full-file write. Symbol-expanded reads (small reads silently widened to the enclosing symbol via tree-sitter) count toward coverage at the symbol level. Markdown, text
|
|
115
|
+
Coverage is tracked across multiple reads: two reads of lines 1–100 and 101–200 together satisfy a full-file write. Symbol-expanded reads (small reads silently widened to the enclosing symbol via tree-sitter) count toward coverage at the symbol level. Markdown files generate a warning instead of blocking (edits outside the section-expanded read range are warned, not silently passed). Plain-text (`.txt`) and log (`.log`) files remain fully exempt.
|
|
114
116
|
|
|
115
117
|
Override for a single edit: `/lens-allow-edit <path>`
|
|
116
118
|
|
|
@@ -127,12 +129,14 @@ Supported: TypeScript, TSX, JavaScript, JSX, Python, Go, Rust, Ruby.
|
|
|
127
129
|
Covers JavaScript/TypeScript, Python, Go, Rust, Ruby, Shell, and CMake. A TypeScript AST-based fact-rule engine extracts function-level metrics and evaluates quality and security rules inline. Blocking rules surface immediately at write time; advisory rules are available via `/lens-booboo`.
|
|
128
130
|
|
|
129
131
|
**Blocking (surface inline at write time):**
|
|
132
|
+
|
|
130
133
|
- **cors-wildcard** — `Access-Control-Allow-Origin: *` in server-side code
|
|
131
134
|
- **error-swallowing** — empty catch block (skips documented local fallbacks and fs-boundary catches)
|
|
132
135
|
- **no-commented-credentials** — password/token/secret in commented-out code
|
|
133
136
|
- **high-entropy-string** — string literals with suspiciously high Shannon entropy (possible hardcoded secret)
|
|
134
137
|
|
|
135
138
|
**Advisory (accessible via `/lens-booboo`):**
|
|
139
|
+
|
|
136
140
|
- **high-complexity** / **no-complex-conditionals** — cyclomatic complexity and deeply nested conditions
|
|
137
141
|
- **high-fan-out** — function calls too many distinct functions (coordination smell)
|
|
138
142
|
- **unsafe-boundary** — dangerous `any` casts at API boundaries
|
|
@@ -273,6 +277,26 @@ pi --lens-semgrep # Enable Semgrep dispatch when a local/configured Semg
|
|
|
273
277
|
pi --lens-semgrep-config p/ci # Explicit Semgrep config for dispatch (requires --lens-semgrep)
|
|
274
278
|
```
|
|
275
279
|
|
|
280
|
+
## Global Config
|
|
281
|
+
|
|
282
|
+
pi-lens reads optional user preferences from `~/.pi-lens/config.json` (`%USERPROFILE%\\.pi-lens\\config.json` on Windows). Unknown keys are ignored, and missing or invalid config falls back to defaults.
|
|
283
|
+
|
|
284
|
+
Hide the diagnostics widget by default and run formatting immediately after write/edit tool calls instead of at `agent_end`:
|
|
285
|
+
|
|
286
|
+
```json
|
|
287
|
+
{
|
|
288
|
+
"widget": {
|
|
289
|
+
"visible": false
|
|
290
|
+
},
|
|
291
|
+
"format": {
|
|
292
|
+
"enabled": true,
|
|
293
|
+
"mode": "immediate"
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
`format.mode` can be `"deferred"` (default) or `"immediate"`. Set `format.enabled` to `false` to match `--no-autoformat`. `/lens-widget-toggle` still works as a session-only override.
|
|
299
|
+
|
|
276
300
|
## Environment Variables
|
|
277
301
|
|
|
278
302
|
- `PILENS_DATA_DIR` — redirect per-project state (scanner caches,
|
|
@@ -291,51 +315,55 @@ pi --lens-semgrep-config p/ci # Explicit Semgrep config for dispatch (requires
|
|
|
291
315
|
- `/lens-widget-toggle` — show/hide the pi-lens diagnostics widget below the editor
|
|
292
316
|
- `/lens-booboo` — full quality report for current project state
|
|
293
317
|
- `/lens-health` — runtime health, latency, and diagnostic telemetry
|
|
318
|
+
- `/lens-allow-edit <path>` — override the read-before-edit guard for a single edit
|
|
294
319
|
- `/lens-tools` — tool installation status: globally installed, auto-installed, or npx fallback
|
|
295
320
|
- `/lens-tdi` — Technical Debt Index (TDI) and project health trend
|
|
296
321
|
- `/lens-semgrep` — manage experimental Semgrep dispatch (`status`, `init`, `enable`, `disable`, `clear`)
|
|
297
322
|
|
|
298
323
|
## Language Coverage
|
|
299
324
|
|
|
300
|
-
pi-lens supports **
|
|
325
|
+
pi-lens supports **36+ languages** through dispatch runners and LSP integration.
|
|
301
326
|
|
|
302
327
|
Formatting uses a single selected formatter per file: explicit project config wins, otherwise pi-lens uses a smart default where supported, and config-first ecosystems do not autoformat without config.
|
|
303
328
|
|
|
304
329
|
Dispatch is diagnostics-oriented: automatic formatting and safe autofix happen in the post-write pipeline rather than through dispatch format-check runners.
|
|
305
330
|
|
|
306
|
-
| Language | LSP | Dispatch Runners | Formatter
|
|
307
|
-
| --------------------- | --- | -------------------------------------------------------------------------------------------------------------- |
|
|
308
|
-
| JavaScript/TypeScript | ✓ | lsp, ts-lsp, biome-check-json, tree-sitter, ast-grep-napi, type-safety, similarity, fact-rules, eslint, oxlint | biome, prettier
|
|
309
|
-
| Python | ✓ | lsp, pyright, ruff-lint, tree-sitter, python-slop | ruff, black
|
|
310
|
-
| Go | ✓ | lsp, go-vet, golangci-lint, tree-sitter | gofmt
|
|
311
|
-
| Rust | ✓ | lsp, rust-clippy, tree-sitter | rustfmt
|
|
312
|
-
| Ruby | ✓ | lsp, rubocop, tree-sitter | rubocop, standardrb
|
|
313
|
-
| C/C++ | ✓ | lsp, cpp-check
|
|
314
|
-
| Shell | ✓ | lsp, shellcheck | shfmt
|
|
315
|
-
|
|
|
316
|
-
|
|
|
317
|
-
|
|
|
318
|
-
|
|
|
319
|
-
|
|
|
320
|
-
|
|
|
321
|
-
|
|
|
322
|
-
|
|
|
323
|
-
|
|
|
324
|
-
|
|
|
325
|
-
|
|
|
326
|
-
|
|
|
327
|
-
|
|
|
328
|
-
|
|
|
329
|
-
|
|
|
330
|
-
|
|
|
331
|
-
|
|
|
332
|
-
|
|
|
333
|
-
|
|
|
334
|
-
|
|
|
335
|
-
|
|
|
336
|
-
|
|
|
337
|
-
|
|
|
338
|
-
|
|
|
339
|
-
|
|
|
340
|
-
|
|
|
341
|
-
|
|
|
331
|
+
| Language | LSP | Dispatch Runners | Formatter |
|
|
332
|
+
| --------------------- | --- | -------------------------------------------------------------------------------------------------------------- | ----------------------- |
|
|
333
|
+
| JavaScript/TypeScript | ✓ | lsp, ts-lsp, biome-check-json, tree-sitter, ast-grep-napi, type-safety, similarity, fact-rules, eslint, oxlint | biome, prettier |
|
|
334
|
+
| Python | ✓ | lsp, pyright, ruff-lint, tree-sitter, python-slop | ruff, black |
|
|
335
|
+
| Go | ✓ | lsp, go-vet, golangci-lint, tree-sitter | gofmt |
|
|
336
|
+
| Rust | ✓ | lsp, rust-clippy, tree-sitter | rustfmt |
|
|
337
|
+
| Ruby | ✓ | lsp, rubocop, tree-sitter | rubocop, standardrb |
|
|
338
|
+
| C/C++ | ✓ | lsp, cpp-check, tree-sitter | clang-format |
|
|
339
|
+
| Shell | ✓ | lsp, shellcheck | shfmt |
|
|
340
|
+
| Fish | ✓ | lsp, fish-indent | fish_indent |
|
|
341
|
+
| CSS/SCSS/Less | ✓ | lsp, stylelint | biome, prettier |
|
|
342
|
+
| HTML | ✓ | lsp, htmlhint | prettier |
|
|
343
|
+
| YAML | ✓ | lsp, yamllint | prettier |
|
|
344
|
+
| JSON | ✓ | lsp | biome, prettier |
|
|
345
|
+
| Svelte | ✓ | lsp | — |
|
|
346
|
+
| Vue | ✓ | lsp | — |
|
|
347
|
+
| SQL | — | sqlfluff | sqlfluff |
|
|
348
|
+
| Markdown | — | spellcheck, markdownlint, vale | prettier |
|
|
349
|
+
| Docker | ✓ | lsp, hadolint | — |
|
|
350
|
+
| PHP | ✓ | lsp, php-lint, phpstan | php-cs-fixer |
|
|
351
|
+
| PowerShell | ✓ | lsp, psscriptanalyzer | psscriptanalyzer-format |
|
|
352
|
+
| Prisma | ✓ | lsp, prisma-validate | — |
|
|
353
|
+
| C# | ✓ | lsp, dotnet-build | csharpier |
|
|
354
|
+
| F# | ✓ | lsp | fantomas |
|
|
355
|
+
| Java | ✓ | lsp, javac | google-java-format |
|
|
356
|
+
| Kotlin | ✓ | lsp, ktlint, detekt | ktlint |
|
|
357
|
+
| Swift | ✓ | lsp, swiftlint | swiftformat |
|
|
358
|
+
| Dart | ✓ | lsp, dart-analyze | dart format |
|
|
359
|
+
| Lua | ✓ | lsp | stylua |
|
|
360
|
+
| Zig | ✓ | lsp, zig-check | zig fmt |
|
|
361
|
+
| Haskell | ✓ | lsp | ormolu |
|
|
362
|
+
| Elixir | ✓ | lsp, elixir-check, credo | mix format |
|
|
363
|
+
| Gleam | ✓ | lsp, gleam-check | gleam format |
|
|
364
|
+
| OCaml | ✓ | lsp | ocamlformat |
|
|
365
|
+
| Clojure | ✓ | lsp | cljfmt |
|
|
366
|
+
| Terraform | ✓ | lsp, tflint | terraform fmt |
|
|
367
|
+
| Nix | ✓ | lsp | nixfmt |
|
|
368
|
+
| TOML | ✓ | lsp, taplo | taplo |
|
|
369
|
+
| CMake | ✓ | lsp | cmake-format |
|
|
@@ -171,14 +171,14 @@ export class AstGrepClient {
|
|
|
171
171
|
/**
|
|
172
172
|
* Run a one-off scan with a temporary rule and configuration
|
|
173
173
|
*/
|
|
174
|
-
private
|
|
174
|
+
private async runTempScanAsync(
|
|
175
175
|
dir: string,
|
|
176
176
|
ruleId: string,
|
|
177
177
|
ruleYaml: string,
|
|
178
178
|
timeout = 30000,
|
|
179
|
-
): AstGrepMatch[] {
|
|
179
|
+
): Promise<AstGrepMatch[]> {
|
|
180
180
|
if (!this.isAvailable()) return [];
|
|
181
|
-
return this.runner.
|
|
181
|
+
return this.runner.tempScanAsync(dir, ruleId, ruleYaml, timeout);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
/**
|
|
@@ -201,7 +201,7 @@ severity: info
|
|
|
201
201
|
message: found
|
|
202
202
|
`;
|
|
203
203
|
|
|
204
|
-
const matches = this.
|
|
204
|
+
const matches = await this.runTempScanAsync(dir, "find-functions", ruleYaml);
|
|
205
205
|
if (matches.length === 0) return [];
|
|
206
206
|
|
|
207
207
|
return this.groupSimilarFunctions(matches);
|
|
@@ -279,7 +279,7 @@ severity: info
|
|
|
279
279
|
message: found
|
|
280
280
|
`;
|
|
281
281
|
|
|
282
|
-
const matches = this.
|
|
282
|
+
const matches = await this.runTempScanAsync(dir, "find-functions", ruleYaml, 15000);
|
|
283
283
|
this.log(`scanExports output length: ${matches.length}`);
|
|
284
284
|
|
|
285
285
|
for (const item of matches) {
|
|
@@ -10,7 +10,7 @@ import * as fs from "node:fs";
|
|
|
10
10
|
import * as path from "node:path";
|
|
11
11
|
import { getProjectDataDir } from "../file-utils.js";
|
|
12
12
|
|
|
13
|
-
const CACHE_VERSION = "
|
|
13
|
+
const CACHE_VERSION = "v2";
|
|
14
14
|
|
|
15
15
|
export interface QueryCacheEntry {
|
|
16
16
|
version: string;
|
|
@@ -27,6 +27,7 @@ export interface QueryCacheEntry {
|
|
|
27
27
|
post_filter?: string;
|
|
28
28
|
// biome-ignore lint/suspicious/noExplicitAny: Flexible filter params
|
|
29
29
|
post_filter_params?: Record<string, any>;
|
|
30
|
+
filePath?: string;
|
|
30
31
|
}>;
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
* - BaselineStore: Track pre-existing issues for delta mode
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import * as fs from "node:fs";
|
|
17
18
|
import * as path from "node:path";
|
|
18
19
|
import type { FileKind } from "../file-kinds.js";
|
|
19
20
|
import { recordRunner } from "../widget-state.js";
|
|
20
21
|
import { detectFileKind } from "../file-kinds.js";
|
|
22
|
+
import { detectFileRole } from "../file-role.js";
|
|
21
23
|
import { isTestFile } from "../file-utils.js";
|
|
22
24
|
import { getPrimaryDispatchGroup } from "../language-policy.js";
|
|
23
25
|
import { resolveLanguageRootForFile } from "../language-profile.js";
|
|
@@ -116,6 +118,26 @@ async function checkToolAvailability(
|
|
|
116
118
|
|
|
117
119
|
// --- Dispatch Context Factory ---
|
|
118
120
|
|
|
121
|
+
function readFilePrefix(filePath: string, maxBytes = 4096): string | undefined {
|
|
122
|
+
let fd: number | undefined;
|
|
123
|
+
try {
|
|
124
|
+
fd = fs.openSync(filePath, "r");
|
|
125
|
+
const buffer = Buffer.alloc(maxBytes);
|
|
126
|
+
const bytesRead = fs.readSync(fd, buffer, 0, maxBytes, 0);
|
|
127
|
+
return buffer.subarray(0, bytesRead).toString("utf8");
|
|
128
|
+
} catch {
|
|
129
|
+
return undefined;
|
|
130
|
+
} finally {
|
|
131
|
+
if (fd !== undefined) {
|
|
132
|
+
try {
|
|
133
|
+
fs.closeSync(fd);
|
|
134
|
+
} catch {
|
|
135
|
+
// ignore close errors
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
119
141
|
export function createDispatchContext(
|
|
120
142
|
filePath: string,
|
|
121
143
|
cwd: string,
|
|
@@ -130,11 +152,16 @@ export function createDispatchContext(
|
|
|
130
152
|
);
|
|
131
153
|
const normalizedFilePath = normalizeMapKey(absoluteFilePath);
|
|
132
154
|
const kind = detectFileKind(normalizedFilePath);
|
|
155
|
+
const fileRole = detectFileRole(
|
|
156
|
+
normalizedFilePath,
|
|
157
|
+
readFilePrefix(normalizedFilePath),
|
|
158
|
+
);
|
|
133
159
|
|
|
134
160
|
return {
|
|
135
161
|
filePath: normalizedFilePath,
|
|
136
162
|
cwd: normalizedCwd,
|
|
137
163
|
kind,
|
|
164
|
+
fileRole,
|
|
138
165
|
pi,
|
|
139
166
|
autofix: false,
|
|
140
167
|
deltaMode: !pi.getFlag("no-delta"),
|
|
@@ -598,6 +625,15 @@ async function runGroup(
|
|
|
598
625
|
status: result.status,
|
|
599
626
|
diagnosticCount: result.diagnostics.length,
|
|
600
627
|
semantic: result.semantic ?? semantic,
|
|
628
|
+
diagnostics:
|
|
629
|
+
result.diagnostics.length > 0
|
|
630
|
+
? result.diagnostics.map((d) => ({
|
|
631
|
+
rule: d.rule,
|
|
632
|
+
message: d.message.slice(0, 120),
|
|
633
|
+
line: d.line,
|
|
634
|
+
semantic: d.semantic,
|
|
635
|
+
}))
|
|
636
|
+
: undefined,
|
|
601
637
|
});
|
|
602
638
|
recordRunner(
|
|
603
639
|
ctx.filePath,
|
|
@@ -634,6 +670,19 @@ export async function dispatchForFile(
|
|
|
634
670
|
registry: RunnerRegistryContract,
|
|
635
671
|
): Promise<DispatchResult> {
|
|
636
672
|
const _overallStart = Date.now();
|
|
673
|
+
if (ctx.fileRole === "generated") {
|
|
674
|
+
return {
|
|
675
|
+
diagnostics: [],
|
|
676
|
+
blockers: [],
|
|
677
|
+
warnings: [],
|
|
678
|
+
baselineWarningCount: 0,
|
|
679
|
+
fixed: [],
|
|
680
|
+
resolvedCount: 0,
|
|
681
|
+
output: "",
|
|
682
|
+
blockerOutput: "",
|
|
683
|
+
hasBlockers: false,
|
|
684
|
+
};
|
|
685
|
+
}
|
|
637
686
|
const allDiagnostics: Diagnostic[] = [];
|
|
638
687
|
let stopped = false;
|
|
639
688
|
const runnerLatencies: RunnerLatency[] = [];
|
|
@@ -42,6 +42,7 @@ export type { DispatchLatencyReport, RunnerLatency };
|
|
|
42
42
|
export { clearLatencyReports, formatLatencyReport, getLatencyReports };
|
|
43
43
|
|
|
44
44
|
import * as nodeFs from "node:fs";
|
|
45
|
+
import { fileURLToPath } from "node:url";
|
|
45
46
|
import { formatCascadeNeighborDiagnostics } from "../cascade-format.js";
|
|
46
47
|
import { logCascade } from "../cascade-logger.js";
|
|
47
48
|
import type { CascadeResult } from "../cascade-types.js";
|
|
@@ -58,6 +59,7 @@ import {
|
|
|
58
59
|
computeImpactCascade,
|
|
59
60
|
formatImpactCascade,
|
|
60
61
|
} from "../review-graph/service.js";
|
|
62
|
+
import { clearModuleGraphCache } from "../review-graph/workspace-modules.js";
|
|
61
63
|
import { RUNTIME_CONFIG } from "../runtime-config.js";
|
|
62
64
|
// Register fact providers
|
|
63
65
|
import { registerProvider, runProviders } from "./fact-runner.js";
|
|
@@ -402,6 +404,7 @@ export function resetDispatchBaselines(): void {
|
|
|
402
404
|
resetSessionSlopScore();
|
|
403
405
|
clearCoverageNoticeState();
|
|
404
406
|
clearReviewGraphWorkspaceCache();
|
|
407
|
+
clearModuleGraphCache();
|
|
405
408
|
neighborTouchCache.clear();
|
|
406
409
|
recentlyCleanNeighborCache.clear();
|
|
407
410
|
primaryFilesThisTurn.clear();
|
|
@@ -470,7 +473,14 @@ function ensureCascadeTurnScope(turnSeq: number): void {
|
|
|
470
473
|
const CASCADE_TTL_MS = 240_000;
|
|
471
474
|
const MAX_PER_FILE = RUNTIME_CONFIG.pipeline.cascadeMaxDiagnosticsPerFile;
|
|
472
475
|
const MAX_FILES = RUNTIME_CONFIG.pipeline.cascadeMaxFiles;
|
|
473
|
-
const CASCADE_GRAPH_KINDS = new Set([
|
|
476
|
+
const CASCADE_GRAPH_KINDS = new Set([
|
|
477
|
+
"jsts",
|
|
478
|
+
"python",
|
|
479
|
+
"go",
|
|
480
|
+
"rust",
|
|
481
|
+
"ruby",
|
|
482
|
+
"cxx",
|
|
483
|
+
]);
|
|
474
484
|
|
|
475
485
|
/**
|
|
476
486
|
* Unified cascade orchestration — builds graph, discovers neighbors, and
|
|
@@ -559,7 +569,70 @@ export async function computeCascadeForFile(
|
|
|
559
569
|
metadata: { graphBuildMode: graphBuildInfo.mode },
|
|
560
570
|
});
|
|
561
571
|
|
|
562
|
-
impact = computeImpactCascade(graph, normalizedFile);
|
|
572
|
+
impact = computeImpactCascade(graph, normalizedFile, cwd);
|
|
573
|
+
|
|
574
|
+
// Symbol-level blast radius via LSP references (precision upgrade over
|
|
575
|
+
// file-level import edges). Only when changed symbols are detected.
|
|
576
|
+
// Keep the budget tight: 750ms per symbol, 1200ms total, max 3 symbols.
|
|
577
|
+
if (impact.changedSymbols.length > 0) {
|
|
578
|
+
const lspService = getLSPService();
|
|
579
|
+
const symbolNodeIds =
|
|
580
|
+
graph.symbolNodesByFile.get(normalizedFileKey) ?? [];
|
|
581
|
+
const refFiles = new Set<string>();
|
|
582
|
+
const refsStart = Date.now();
|
|
583
|
+
for (const symbolName of impact.changedSymbols.slice(0, 3)) {
|
|
584
|
+
const symbolNodeId = symbolNodeIds.find((id) => {
|
|
585
|
+
const node = graph.nodes.get(id);
|
|
586
|
+
return node?.symbolName === symbolName;
|
|
587
|
+
});
|
|
588
|
+
if (!symbolNodeId) continue;
|
|
589
|
+
const node = graph.nodes.get(symbolNodeId);
|
|
590
|
+
const line = Number(node?.metadata?.line ?? 0);
|
|
591
|
+
const column = Number(node?.metadata?.column ?? 0);
|
|
592
|
+
if (line <= 0) continue;
|
|
593
|
+
try {
|
|
594
|
+
const refs = await Promise.race([
|
|
595
|
+
lspService.references(normalizedFile, line - 1, column - 1, false),
|
|
596
|
+
new Promise<never>((_, reject) =>
|
|
597
|
+
setTimeout(() => reject(new Error("timeout")), 750),
|
|
598
|
+
),
|
|
599
|
+
]);
|
|
600
|
+
for (const ref of refs) {
|
|
601
|
+
let resolved: string;
|
|
602
|
+
try {
|
|
603
|
+
resolved = ref.uri.startsWith("file://")
|
|
604
|
+
? fileURLToPath(ref.uri)
|
|
605
|
+
: ref.uri;
|
|
606
|
+
} catch {
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
if (
|
|
610
|
+
normalizeMapKey(resolved) !== normalizedFileKey &&
|
|
611
|
+
nodeFs.existsSync(resolved)
|
|
612
|
+
) {
|
|
613
|
+
refFiles.add(normalizeMapKey(resolved));
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
} catch {
|
|
617
|
+
// Timeout or LSP error — fall back to import-graph neighbors
|
|
618
|
+
}
|
|
619
|
+
if (Date.now() - refsStart > 1200) break; // Hard ceiling
|
|
620
|
+
}
|
|
621
|
+
if (refFiles.size > 0) {
|
|
622
|
+
impact.neighborFiles = [
|
|
623
|
+
...new Set([...impact.neighborFiles, ...refFiles]),
|
|
624
|
+
];
|
|
625
|
+
logCascade({
|
|
626
|
+
phase: "neighbor_snapshot",
|
|
627
|
+
filePath,
|
|
628
|
+
neighborFile: "[lsp-references]",
|
|
629
|
+
diagnosticCount: refFiles.size,
|
|
630
|
+
durationMs: Date.now() - refsStart,
|
|
631
|
+
autoPropagate: false,
|
|
632
|
+
metadata: { lspReferences: true },
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
}
|
|
563
636
|
|
|
564
637
|
// Sort by relationship strength (B6) then cap to MAX_FILES.
|
|
565
638
|
// directImporters are most impactful, then callers, then reference edges.
|
package/clients/dispatch/plan.ts
CHANGED
|
@@ -99,8 +99,11 @@ export const LANGUAGE_CAPABILITY_MATRIX: Record<
|
|
|
99
99
|
},
|
|
100
100
|
cxx: {
|
|
101
101
|
name: "C/C++ Linting",
|
|
102
|
-
capabilities: ["types", "lint"],
|
|
103
|
-
writeGroups: [
|
|
102
|
+
capabilities: ["types", "lint", "smells"],
|
|
103
|
+
writeGroups: [
|
|
104
|
+
primary("cxx"),
|
|
105
|
+
{ mode: "all", runnerIds: ["tree-sitter"], filterKinds: ["cxx"] },
|
|
106
|
+
],
|
|
104
107
|
},
|
|
105
108
|
cmake: {
|
|
106
109
|
name: "CMake Processing",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
1
2
|
import * as path from "node:path";
|
|
2
3
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
3
4
|
import { PRIORITY } from "../priorities.js";
|
|
@@ -12,13 +13,79 @@ type CompilerSpec =
|
|
|
12
13
|
| { command: string; args: string[]; flavor: "gcc" | "msvc" }
|
|
13
14
|
| undefined;
|
|
14
15
|
|
|
16
|
+
const C_SOURCE_EXTENSIONS = new Set([".c"]);
|
|
17
|
+
const C_HEADER_EXTENSIONS = new Set([".h"]);
|
|
18
|
+
const CPP_SOURCE_EXTENSIONS = new Set([
|
|
19
|
+
".c++",
|
|
20
|
+
".cc",
|
|
21
|
+
".cp",
|
|
22
|
+
".cpp",
|
|
23
|
+
".cxx",
|
|
24
|
+
".c++m",
|
|
25
|
+
".cppm",
|
|
26
|
+
".cxxm",
|
|
27
|
+
".ixx",
|
|
28
|
+
".cu",
|
|
29
|
+
".hip",
|
|
30
|
+
".mm",
|
|
31
|
+
".clcpp",
|
|
32
|
+
]);
|
|
33
|
+
const CPP_HEADER_EXTENSIONS = new Set([
|
|
34
|
+
".hh",
|
|
35
|
+
".hpp",
|
|
36
|
+
".hxx",
|
|
37
|
+
".inl",
|
|
38
|
+
".ipp",
|
|
39
|
+
".tpp",
|
|
40
|
+
".txx",
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
function headerLooksLikeCpp(absPath: string): boolean {
|
|
44
|
+
try {
|
|
45
|
+
const content = fs.readFileSync(absPath, "utf-8");
|
|
46
|
+
return /\b(namespace|template|class|constexpr|concept|using)\b|std::|\b(public|private|protected)\s*:/.test(
|
|
47
|
+
content,
|
|
48
|
+
);
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getGccLikeCandidates(absPath: string): Array<{
|
|
55
|
+
command: string;
|
|
56
|
+
args: string[];
|
|
57
|
+
}> {
|
|
58
|
+
const ext = path.extname(absPath).toLowerCase();
|
|
59
|
+
const cMode =
|
|
60
|
+
C_SOURCE_EXTENSIONS.has(ext) ||
|
|
61
|
+
(C_HEADER_EXTENSIONS.has(ext) && !headerLooksLikeCpp(absPath));
|
|
62
|
+
const cppMode =
|
|
63
|
+
CPP_SOURCE_EXTENSIONS.has(ext) || CPP_HEADER_EXTENSIONS.has(ext);
|
|
64
|
+
|
|
65
|
+
if (cMode) {
|
|
66
|
+
const cArgs = C_HEADER_EXTENSIONS.has(ext)
|
|
67
|
+
? ["-x", "c-header", "-fsyntax-only", absPath]
|
|
68
|
+
: ["-x", "c", "-fsyntax-only", absPath];
|
|
69
|
+
return [
|
|
70
|
+
{ command: "clang", args: cArgs },
|
|
71
|
+
{ command: "gcc", args: cArgs },
|
|
72
|
+
{ command: "cc", args: cArgs },
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (cppMode || ext) {
|
|
77
|
+
return [
|
|
78
|
+
{ command: "clang++", args: ["-fsyntax-only", absPath] },
|
|
79
|
+
{ command: "g++", args: ["-fsyntax-only", absPath] },
|
|
80
|
+
{ command: "c++", args: ["-fsyntax-only", absPath] },
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
|
|
15
87
|
async function resolveCompiler(absPath: string): Promise<CompilerSpec> {
|
|
16
|
-
const
|
|
17
|
-
{ command: "clang++", args: ["-fsyntax-only", absPath] },
|
|
18
|
-
{ command: "g++", args: ["-fsyntax-only", absPath] },
|
|
19
|
-
{ command: "c++", args: ["-fsyntax-only", absPath] },
|
|
20
|
-
];
|
|
21
|
-
for (const candidate of gccLike) {
|
|
88
|
+
for (const candidate of getGccLikeCandidates(absPath)) {
|
|
22
89
|
const probe = await safeSpawnAsync(candidate.command, ["--version"], {
|
|
23
90
|
timeout: 5000,
|
|
24
91
|
});
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Runs `go vet` for Go files to catch common mistakes.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { GoClient } from "../../go-client.js";
|
|
7
8
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
8
9
|
import { stripAnsi } from "../../sanitize.js";
|
|
9
10
|
import { parseGoVetOutput } from "./utils/diagnostic-parsers.js";
|
|
@@ -14,6 +15,8 @@ import type {
|
|
|
14
15
|
} from "../types.js";
|
|
15
16
|
import { PRIORITY } from "../priorities.js";
|
|
16
17
|
|
|
18
|
+
const goClient = new GoClient();
|
|
19
|
+
|
|
17
20
|
const goVetRunner: RunnerDefinition = {
|
|
18
21
|
id: "go-vet",
|
|
19
22
|
appliesTo: ["go"],
|
|
@@ -21,17 +24,14 @@ const goVetRunner: RunnerDefinition = {
|
|
|
21
24
|
enabledByDefault: true,
|
|
22
25
|
|
|
23
26
|
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
24
|
-
//
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
if (check.error || check.status !== 0) {
|
|
27
|
+
// Resolve go path using platform-aware lookup (handles system install paths on Windows)
|
|
28
|
+
const goExe = goClient.findGoPath();
|
|
29
|
+
if (!goExe) {
|
|
30
30
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// Run go vet on the file
|
|
34
|
-
const result = await safeSpawnAsync(
|
|
34
|
+
const result = await safeSpawnAsync(goExe, ["vet", ctx.filePath], {
|
|
35
35
|
timeout: 30000,
|
|
36
36
|
});
|
|
37
37
|
|
|
@@ -43,8 +43,10 @@ import similarityRunner from "./similarity.js";
|
|
|
43
43
|
import spellcheckRunner from "./spellcheck.js";
|
|
44
44
|
import sqlfluffRunner from "./sqlfluff.js";
|
|
45
45
|
import stylelintRunner from "./stylelint.js";
|
|
46
|
+
import swiftlintRunner from "./swiftlint.js";
|
|
46
47
|
import taploRunner from "./taplo.js";
|
|
47
48
|
import tflintRunner from "./tflint.js";
|
|
49
|
+
import valeRunner from "./vale.js";
|
|
48
50
|
// Import tree-sitter runner
|
|
49
51
|
import treeSitterRunner from "./tree-sitter.js";
|
|
50
52
|
import tsLspRunner from "./ts-lsp.js";
|
|
@@ -83,11 +85,13 @@ export function registerDefaultRunners(registry: RunnerRegistry): void {
|
|
|
83
85
|
registry.register(markdownlintRunner); // Markdown lint (priority 30)
|
|
84
86
|
registry.register(mypyRunner); // Python type checking — mypy (priority 20, config-gated)
|
|
85
87
|
registry.register(stylelintRunner); // CSS/SCSS/Less lint (priority 10, config-gated)
|
|
88
|
+
registry.register(swiftlintRunner); // Swift lint — out-of-the-box defaults (priority 20)
|
|
86
89
|
registry.register(shfmtRunner); // Shell formatting check (priority 10)
|
|
87
90
|
registry.register(fishIndentRunner); // Fish script formatting check (priority 10)
|
|
88
91
|
registry.register(factRulesRunner); // FactRule pipeline — all registered rules (priority 21)
|
|
89
92
|
registry.register(htmlhintRunner); // HTML linting — tag pairs, attribute rules (priority 20)
|
|
90
93
|
registry.register(hadolintRunner); // Dockerfile linting — syntax, best practices (priority 20)
|
|
94
|
+
registry.register(valeRunner); // Prose/style linting for Markdown — config-gated (.vale.ini) (priority 30)
|
|
91
95
|
registry.register(phpLintRunner); // PHP syntax validation via php -l (priority 20)
|
|
92
96
|
registry.register(psScriptAnalyzerRunner); // PowerShell linting via PSScriptAnalyzer module (priority 20)
|
|
93
97
|
registry.register(prismaValidateRunner); // Prisma schema validation via CLI (priority 20)
|
|
@@ -21,7 +21,7 @@ import type {
|
|
|
21
21
|
import {
|
|
22
22
|
createConfigFinder,
|
|
23
23
|
getSgCommand,
|
|
24
|
-
|
|
24
|
+
isSgAvailableAsync,
|
|
25
25
|
} from "./utils/runner-helpers.js";
|
|
26
26
|
|
|
27
27
|
const findSlopConfig = createConfigFinder("python-slop-rules");
|
|
@@ -35,7 +35,7 @@ const pythonSlopRunner: RunnerDefinition = {
|
|
|
35
35
|
|
|
36
36
|
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
37
37
|
// Check if ast-grep is available
|
|
38
|
-
if (!
|
|
38
|
+
if (!await isSgAvailableAsync()) {
|
|
39
39
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
40
40
|
}
|
|
41
41
|
|