pi-lens 3.8.41 → 3.8.43
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 +89 -0
- package/README.md +36 -15
- package/clients/cache-manager.ts +3 -0
- package/clients/dispatch/dispatcher.ts +3 -1
- package/clients/dispatch/integration.ts +178 -56
- package/clients/dispatch/plan.ts +13 -2
- package/clients/dispatch/rules/error-swallowing.ts +2 -2
- package/clients/dispatch/rules/quality-rules.ts +14 -4
- package/clients/dispatch/rules/sonar-rules.ts +36 -8
- package/clients/dispatch/runners/oxlint.ts +56 -14
- package/clients/dispatch/runners/tree-sitter.ts +28 -10
- package/clients/dispatch/types.ts +6 -0
- package/clients/file-utils.ts +32 -0
- package/clients/formatters.ts +9 -0
- package/clients/installer/index.ts +123 -1
- package/clients/knip-client.ts +1 -4
- package/clients/lsp/client.ts +16 -1
- package/clients/lsp/index.ts +94 -18
- package/clients/lsp/server-strategies.ts +7 -0
- package/clients/lsp/server.ts +6 -7
- package/clients/path-utils.ts +15 -3
- package/clients/pipeline.ts +61 -3
- package/clients/read-guard-tool-lines.ts +1 -1
- package/clients/review-graph/builder.ts +80 -7
- package/clients/runtime-config.ts +5 -0
- package/clients/runtime-context.ts +3 -3
- package/clients/runtime-coordinator.ts +20 -0
- package/clients/runtime-session.ts +30 -10
- package/clients/runtime-tool-result.ts +9 -1
- package/clients/runtime-turn.ts +108 -109
- package/clients/source-filter.ts +3 -2
- package/clients/test-runner-client.ts +99 -6
- package/clients/tool-policy.ts +39 -1
- package/clients/tree-sitter-client.ts +84 -4
- package/clients/widget-state.ts +1 -1
- package/commands/booboo.ts +14 -2
- package/index.ts +74 -17
- package/package.json +3 -3
- package/rules/tree-sitter-queries/go/go-command-injection.yml +2 -2
- package/rules/tree-sitter-queries/go/go-shared-map-write-goroutine.yml +2 -2
- package/rules/tree-sitter-queries/go/go-sql-injection.yml +2 -2
- package/rules/tree-sitter-queries/go/go-weak-hash.yml +2 -2
- package/rules/tree-sitter-queries/python/python-command-injection.yml +2 -2
- package/rules/tree-sitter-queries/python/python-hallucinated-import.yml +1 -1
- package/rules/tree-sitter-queries/python/python-insecure-deserialization.yml +2 -2
- package/rules/tree-sitter-queries/python/python-sql-injection.yml +2 -2
- package/rules/tree-sitter-queries/python/python-weak-hash.yml +2 -2
- package/rules/tree-sitter-queries/ruby/ruby-weak-hash.yml +2 -2
- package/rules/tree-sitter-queries/rust/rust-lock-held-across-await.yml +2 -2
- package/rules/tree-sitter-queries/typescript/incomplete-assertion.yml +50 -0
- package/rules/tree-sitter-queries/typescript/switch-non-case-labels.yml +53 -0
- package/rules/tree-sitter-queries/typescript/ts-command-injection.yml +2 -2
- package/rules/tree-sitter-queries/typescript/ts-dynamic-require.yml +55 -0
- package/rules/tree-sitter-queries/typescript/ts-hallucinated-react-import.yml +1 -1
- package/rules/tree-sitter-queries/typescript/ts-nosql-injection.yml +54 -0
- package/rules/tree-sitter-queries/typescript/ts-open-redirect.yml +111 -0
- package/rules/tree-sitter-queries/typescript/ts-react-antipatterns.yml +6 -3
- package/rules/tree-sitter-queries/typescript/ts-ssrf.yml +2 -2
- package/rules/tree-sitter-queries/typescript/ts-weak-hash.yml +2 -2
- package/rules/tree-sitter-queries/typescript/ts-xss-dom-sink.yml +100 -0
- package/rules/tree-sitter-queries/typescript/unsafe-regex.yml +3 -2
- package/rules/tree-sitter-queries/typescript-disabled/ts-path-traversal.yml +71 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,95 @@ All notable changes to pi-lens will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [3.8.43] - 2026-05-10
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Unresolved inline blocker re-surfacing at turn_end** — when the agent ignores a blocking diagnostic shown during a write/edit and moves to the next turn without fixing it, the blocker now reappears in the turn_end injection framed as `"Unresolved from this turn — <file>: 🔴 STOP…"`. Previously, unresolved inline blockers were silently lost until cascade happened to re-touch the same file via an importer. `RuntimeCoordinator` tracks the last-seen blocking output per file (`_pendingInlineBlockers`); a subsequent write that produces no blockers clears the entry, so only genuinely unresolved issues resurface. The map is cleared at `beginTurn` to prevent cross-turn contamination.
|
|
12
|
+
- **S1219 (switch non-case labels) and S2970 (incomplete assertions) blocking tree-sitter rules** — S1219 detects labeled statements inside switch cases in TypeScript (SonarCloud S1219); S2970 detects Jest/Vitest `expect()` chains that are never called (e.g. `expect(x).toBe(y)` without `await`), with Chai property assertion exclusion. S2083 (path traversal) moved to disabled — regex heuristics on tree-sitter syntax are the wrong layer; needs taint/data-flow analysis. Adds `parent?` field to `TreeSitterNode` interface.
|
|
13
|
+
- **Inline code snippets in blocker output** — each 🔴 STOP diagnostic now includes the exact source line the agent wrote that caused the violation, so the agent can identify and fix the issue without re-reading the file. `fixSuggestion` is also surfaced inline when present. Snippet capped at 120 chars.
|
|
14
|
+
- **AST node type and matched text in blocker output** — tree-sitter diagnostics now carry `matchedText` (the exact matched node, more precise than the full source line) and `astNodeType` (e.g. `call_expression`, `template_string`). The agent sees: `L12: SQL query built with string interpolation (template_string) → db.query(...)`.
|
|
15
|
+
- **Persist review graph to disk** — `_workspaceGraphCache` is now backed by `.pi-lens/cache/review-graph.json`. On cold start, if source file signatures match the stored cache, the full 2–4 s tree-sitter + import-fact build is skipped (~20 ms JSON parse + `rebuildIndexes` instead). Write is fire-and-forget, never blocks dispatch.
|
|
16
|
+
- **Preserve last known LSP diagnostics when LSP goes inactive** — when no live clients are available (dead client respawning, circuit-breaker cooldown), `getDiagnostics` now returns the last non-empty result for that file instead of `[]`. The widget keeps showing the last known issues rather than going blank mid-session. Live clients returning `[]` clears the stale entry. Stale hits are logged as `failureKind: "no_clients_stale"`.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **Read-guard false-positive block on files outside the project root** — edits to files outside `projectRoot` (e.g. `C:/llama/*.bat`, scripts in arbitrary directories) were always blocked with `zero_read` because reads for external files are intentionally not recorded (`isExternalOrVendor` gate in the read handler), but the `checkEdit` call had no matching guard. Added `!isExternalOrVendor` to the `checkEdit` condition so external files bypass the read-guard entirely, consistent with how reads are handled.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- **Replace pyright-langserver and pylsp with jedi-language-server for Python LSP** — `PythonServer` (pyright-langserver) and `PythonPylspServer` (pylsp) removed from `LSP_SERVERS`; replaced by `PythonJediServer` which spawns `jedi-language-server`. pyright-langserver was causing 5–14 s cold-start delays on large Python projects (e.g. tinygrad) because it performs full workspace analysis on startup; jedi starts in ~200–500 ms via lazy per-file analysis. pylsp was removed because it consistently returned 0 diagnostics (no venv → jedi can't resolve imports; 1500 ms aggregate timeout hit on warm runs). Deep type checking is unaffected — the standalone `pyright` CLI runner and `mypy` runner continue to run in parallel. Added `"python-jedi"` strategy entry (`seedFirstPush: true`, `aggregateWaitMs: 1000`). Wall-clock gate for Python dispatch shifts from LSP (~5–14 s) to mypy (~3.5 s).
|
|
25
|
+
|
|
26
|
+
## [3.8.42] - 2026-05-08
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **Fact-rules wired into all language dispatch plans** — the `fact-rules` runner was registered but never listed in any `RunnerGroup`; 20 TypeScript FactRule instances (`corsWildcardRule`, `jwtWithoutVerifyRule`, `dynamicRegexpRule`, `errorObscuringRule`, `highComplexityRule`, etc.) were never executing. Added `mode:all fact-rules` group to jsts, python, go, rust, ruby, cmake, and shell write plans.
|
|
31
|
+
- **3 fact-rules promoted to blocking (inline at write time):** `cors-wildcard` (CORS `*` origin — no ast-grep/tree-sitter equivalent), `error-swallowing` (empty catch — smarter than the disabled tree-sitter `empty-catch`, skips fs-boundary and documented fallbacks), `no-commented-credentials` (credentials in commented code — complementary to ast-grep which covers live code). `high-entropy-string` was already blocking.
|
|
32
|
+
- **Fact-rule false-positive reductions:** `no-boolean-params` now exempts names with `*Only`/`*Enabled`/`*Disabled` suffixes, `allow*`/`skip*`/`needs*`/`auto*` prefixes, and `_`-prefixed params. `duplicate-string-literal` SKIP_STRINGS expanded with DSL discriminators (`types`, `fallback`, `direct`, `all`, `mode`, `source`) and infrastructure strings (`github`, `rubocop`, `arm64`). `high-import-coupling` threshold raised 10→15 and exempts `index.ts`/`integration.ts` registry/hub files. `no-commented-credentials` exempts scanner/fixture files.
|
|
33
|
+
- **Severity alignment for 3 existing TS tree-sitter blocking rules** — `ts-command-injection`, `ts-ssrf`, `unsafe-regex` had `inline_tier: blocking` but `severity: warning`, producing `semantic: "warning"` which is never shown inline. Fixed to `severity: error` → `semantic: "blocking"` → actually surfaces to the agent.
|
|
34
|
+
- **Fixed `inline_tier: error` typo** on `ts-hallucinated-react-import` and `python-hallucinated-import` (→ `blocking`).
|
|
35
|
+
- **13 new high-confidence blocking promotions across 5 languages** (all `severity: error`, `inline_tier: blocking`):
|
|
36
|
+
- *TypeScript:* `ts-weak-hash` (`createHash("md5"/"sha1")` — confidence: high)
|
|
37
|
+
- *Python:* `python-command-injection`, `python-sql-injection`, `python-insecure-deserialization`, `python-weak-hash`
|
|
38
|
+
- *Go:* `go-command-injection`, `go-sql-injection`, `go-shared-map-write-goroutine`, `go-weak-hash`
|
|
39
|
+
- *Ruby:* `ruby-weak-hash`
|
|
40
|
+
- *Rust:* `rust-lock-held-across-await`
|
|
41
|
+
- **4 new blocking tree-sitter rules (SonarCloud BLOCKER equivalents)**:
|
|
42
|
+
- `ts-xss-dom-sink` (S5696) — flags dynamic values assigned to `innerHTML`/`outerHTML` or passed to `document.write()` / `document.writeln()`
|
|
43
|
+
- `ts-dynamic-require` (S5335) — flags `require()` called with a non-string-literal argument (arbitrary module loading)
|
|
44
|
+
- `ts-open-redirect` (S6105) — flags `res.redirect(variable)` / `response.redirect` / `ctx.redirect` with dynamic URL, and `window.location.href = variable`
|
|
45
|
+
- `ts-nosql-injection` (S5147) — flags any MongoDB `$where` key (JS-execution sink, dangerous regardless of value)
|
|
46
|
+
- **2 existing security rules promoted to `inline_tier: blocking`** — `ts-command-injection` (maps to SonarCloud S2076) and `ts-ssrf` (maps to S5146) were previously `warning`; now block the agent turn on detection.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- **`fact-rules` `RuleCache` blind to built-in rule changes** — the cache hash only covered project-local rule files; for any project with no local `rules/` directory the hash was a constant, so new pi-lens built-in rules were silently ignored after the first run. Fixed by including both project-local files and `resolvePackagePath()`-resolved built-in files in the hash, with a `Set` to deduplicate when pi-lens analyzes itself.
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- **`max-switch-cases` threshold raised 30→40** — `applyPostFilter` dispatch table now has 31 cases and is expected to grow; the old threshold triggered a false positive on pi-lens itself.
|
|
55
|
+
- **Package scope migration** — all `@mariozechner/*` import references updated to `@earendil-works/*` following the repo move to `earendil-works/pi-mono`. `@earendil-works/pi-tui` dependency bumped to `^0.74.0`.
|
|
56
|
+
- **Startup: `lsp-config` phase is now fully fire-and-forget** — `loadLSPConfig` and `igniteWarmFiles` no longer block the interactive path, removing ~1s from session start on Windows (previously dominated by sequential ENOENT `readFile` calls walking the directory tree to find a config file).
|
|
57
|
+
- **Startup: persistent tool probe cache** — `ensureTool` now checks `~/.pi-lens/probe-cache.json` before falling back to the full `verifyToolBinary` process spawn. Cache entries are validated with `fs.access` + mtime check and expire after 24 h; stale or missing entries fall through to the full probe and update the cache on success.
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
|
|
61
|
+
- **Startup observability** — `checkProbeCache` now logs the reason for each cache miss (`ttl expired`, `gone`, `mtime changed`); the lsp-config fire-and-forget callback logs how many warm files were configured once the config resolves asynchronously.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
|
|
67
|
+
- **Test runner: import-based fallback discovery** — when basename pattern lookup finds no test file for a modified source file (e.g. `cline.test.ts` for `cline-auth.ts`), the runner now scans `tests/`, `__tests__/`, and the source file's own directory for any `*.test.*` file whose content references the source basename in an import path. Fixes the silent `no test file found` for files whose test is named after a module rather than the source file.
|
|
68
|
+
- **Test runner: prefer local `node_modules/.bin` binary over `npx`** — `vitest` and `jest` now resolve the project-local binary (`node_modules/.bin/vitest.cmd` on Windows, `node_modules/.bin/vitest` on Unix) before falling back to `npx`, saving ~150ms of startup overhead per test run.
|
|
69
|
+
- **Turn-end test runner logging** — `turn_end` now logs the outcome of every test run: `turn_end: test vitest util.test.ts → PASS 8p/0f (412ms)` or `FAIL 2p/8f (930ms)`. Stale results (turn advanced while tests ran) are logged with a `[stale]` prefix instead of being silently discarded. All-pass turns are no longer silent.
|
|
70
|
+
- **Per-file test target logging** — `turn_end` now logs which test file was resolved for each modified source file, or `no test file found` when none matched. Previously silent; impossible to distinguish "runner disabled" from "no test found".
|
|
71
|
+
- **Session-scoped turn-end dedup** — `turn-end-findings-last` now stores the current session ID alongside the content signature. Identical findings from a previous session are no longer suppressed — each new session sees its blockers fresh. Same-session dedup continues to work as before.
|
|
72
|
+
- **Cross-session turn state eviction** — turn state (modified file ranges) now carries the session ID set at first edit. If `turn_end` reads a turn state written by a different session, it evicts it immediately and logs `turn_end: evicting stale turn state (session X ≠ current Y)`, preventing stale cross-session file lists from triggering jscpd, madge, or test runs.
|
|
73
|
+
|
|
74
|
+
### Changed
|
|
75
|
+
|
|
76
|
+
- **Context injections framed as automated checks** — all three `consume*` injections (`turn-end findings`, `test findings`, `session guidance`) now prefix their content with `[pi-lens automated check — not a user request]` so the agent cannot mistake a hook-injected message for a direct user command. Advisory sections additionally carry `ℹ️ Advisory — no action required this turn:` before their content; blockers (🔴) continue to require action.
|
|
77
|
+
|
|
78
|
+
- **`/lens-widget-toggle` command** — toggles the pi-lens diagnostics widget below the editor on/off for the current session, so users can reclaim footer/editor space without disabling pi-lens analysis.
|
|
79
|
+
|
|
80
|
+
### Changed
|
|
81
|
+
|
|
82
|
+
- **Removed per-turn jscpd scans** — jscpd remains in the session-start project scan, but no longer runs unconditionally at `turn_end`; inline structural-similarity checks cover the high-value duplicate-code signal during active edits without the repeated multi-second clone scan.
|
|
83
|
+
- **Cascade avoids low-value work** — unsupported graph kinds now skip review-graph construction and go straight to passive LSP fallback diagnostics, and neighbor files that recently returned clean can skip repeated active LSP touches for a few turns unless the passive snapshot already contains fresh errors.
|
|
84
|
+
- **Knip now surfaces unused-export regressions** — newly unused exports in modified files are shown as advisory end-of-turn findings when they were absent from the previous Knip cache.
|
|
85
|
+
|
|
86
|
+
### Fixed
|
|
87
|
+
|
|
88
|
+
- **Knip latency log now includes result metadata** — the `turn_end` Knip phase previously logged only duration with empty `metadata: {}`, making it impossible to distinguish a clean run from a silent failure. It now logs `success`, `totalIssues`, `newIssues`, `blockerIssues`, and `skipped` when the startup scan is still in flight.
|
|
89
|
+
|
|
90
|
+
- **LSP timeout log now includes `serverIds`** — `lsp_client_wait_timeout` previously only recorded `maxWaitMs`, making it impossible to identify which server consistently failed to respond within the budget. The event now includes the array of server IDs that were being waited on.
|
|
91
|
+
|
|
92
|
+
- **Vendor/third-party files excluded from cascade neighbor analysis** — `isExternalOrVendorFile()` previously only checked `node_modules`; it now checks every path segment against `vendor`, `vendors`, `third_party`, and `third-party` as well. Cascade neighbor discovery and fallback neighbor injection both skip files inside these directories, preventing vendored dependency diagnostics from surfacing in cascade output.
|
|
93
|
+
|
|
94
|
+
- **`lens-booboo` hangs on repos with large vendored trees (fixes #57)** — `collectSourceFiles` and the `sg scan` runner in `lens-booboo` now exclude `vendor/`, `third_party/`, `third-party/`, and `vendors/` by default (added to `EXCLUDED_DIRS`). Additionally, `readGitignoreDirs()` reads the root `.gitignore` and extracts simple directory-name entries (bare names and `name/` patterns — no wildcards, negations, or internal slashes), merging them into the exclusion list for `collectSourceFiles` and the `sg scan` glob arguments. This covers project-specific large dirs (e.g. `my-upstream/`) without requiring full gitignore-spec compliance.
|
|
95
|
+
|
|
7
96
|
## [3.8.41] - 2026-05-05
|
|
8
97
|
|
|
9
98
|
### Fixed
|
package/README.md
CHANGED
|
@@ -124,25 +124,45 @@ Supported: TypeScript, TSX, JavaScript, JSX, Python, Go, Rust, Ruby.
|
|
|
124
124
|
|
|
125
125
|
### Fact Rules Pipeline
|
|
126
126
|
|
|
127
|
-
Covers JavaScript/TypeScript, Python, Go, Rust, Ruby, Shell, and CMake.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
- **
|
|
131
|
-
- **
|
|
132
|
-
- **
|
|
133
|
-
- **
|
|
134
|
-
|
|
135
|
-
|
|
127
|
+
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
|
+
|
|
129
|
+
**Blocking (surface inline at write time):**
|
|
130
|
+
- **cors-wildcard** — `Access-Control-Allow-Origin: *` in server-side code
|
|
131
|
+
- **error-swallowing** — empty catch block (skips documented local fallbacks and fs-boundary catches)
|
|
132
|
+
- **no-commented-credentials** — password/token/secret in commented-out code
|
|
133
|
+
- **high-entropy-string** — string literals with suspiciously high Shannon entropy (possible hardcoded secret)
|
|
134
|
+
|
|
135
|
+
**Advisory (accessible via `/lens-booboo`):**
|
|
136
|
+
- **high-complexity** / **no-complex-conditionals** — cyclomatic complexity and deeply nested conditions
|
|
137
|
+
- **high-fan-out** — function calls too many distinct functions (coordination smell)
|
|
138
|
+
- **unsafe-boundary** — dangerous `any` casts at API boundaries
|
|
139
|
+
- **async-noise** / **async-unnecessary-wrapper** — async functions with no await; wrappers that add no value
|
|
140
|
+
- **pass-through-wrappers** — trivial wrapper functions
|
|
141
|
+
- **dynamic-regexp** — `new RegExp(variable)` (potential ReDoS; complements tree-sitter `unsafe-regex`)
|
|
142
|
+
- **jwt-without-verify** — `jwt.sign()` without `jwt.verify()` in the same file
|
|
143
|
+
- **missing-error-propagation** — catch blocks that log but don't rethrow
|
|
144
|
+
- **error-obscuring** — catch blocks that wrap errors in a different type
|
|
145
|
+
- **duplicate-string-literal** / **no-boolean-params** / **high-import-coupling** — code-quality signals
|
|
136
146
|
|
|
137
147
|
### Tree-sitter Rules
|
|
138
148
|
|
|
139
|
-
Structural rules
|
|
149
|
+
Structural rules organized by language in `rules/tree-sitter-queries/`. Rules marked **🔴** block the agent inline at write time (only for lines in the current edit); others are advisory.
|
|
150
|
+
|
|
151
|
+
**TypeScript (23 rules):**
|
|
152
|
+
🔴 `eval`, `sql-injection`, `ts-command-injection`, `ts-ssrf`, `ts-xss-dom-sink`, `ts-dynamic-require`, `ts-open-redirect`, `ts-nosql-injection`, `ts-weak-hash`, `ts-hallucinated-react-import`, `unsafe-regex`, `debugger`, `default-not-last`, `duplicate-function-arg`, `empty-switch-case`, `infinite-loop`, `self-assignment`, `switch-case-termination`
|
|
153
|
+
⚠️ `console-statement`, `deep-promise-chain`, `mixed-async-styles`, `ts-insecure-random`, `ts-detached-async-call`, `ts-react-antipatterns`, `ts-weak-hash`, `variable-shadowing`
|
|
154
|
+
|
|
155
|
+
**Python:** 🔴 `python-command-injection`, `python-sql-injection`, `python-insecure-deserialization`, `python-weak-hash`, `python-hallucinated-import` + 20 advisory rules
|
|
156
|
+
|
|
157
|
+
**Go:** 🔴 `go-command-injection`, `go-sql-injection`, `go-shared-map-write-goroutine`, `go-weak-hash` + 13 advisory rules
|
|
158
|
+
|
|
159
|
+
**Rust:** 🔴 `rust-lock-held-across-await` + 3 advisory rules (`rust-unsafe-block`, `rust-expect`, `rust-clone-in-loop`)
|
|
160
|
+
|
|
161
|
+
**Ruby:** 🔴 `ruby-weak-hash` + 14 advisory rules
|
|
162
|
+
|
|
163
|
+
**Suppressing a finding:** add `// pi-lens-ignore: rule-id` on the flagged line or the line above (JS/TS), or `# pi-lens-ignore: rule-id` for Python/Ruby/Shell. This suppresses that specific rule at that location only.
|
|
140
164
|
|
|
141
|
-
- **
|
|
142
|
-
- **Python** (26 rules): debug statements, hardcoded secrets, mutable class attrs, unsafe regex, empty except, and more
|
|
143
|
-
- **Go** (17 rules): defer-in-loop, hardcoded secrets, unchecked errors, and more
|
|
144
|
-
- **Rust** (6 rules): unsafe blocks, unwrap outside tests, and more
|
|
145
|
-
- **Ruby** (15 rules): empty rescue, rescue Exception, debugger, hardcoded secrets, and more
|
|
165
|
+
**Project-wide disabling** is not currently supported through config — there is no `.pi-lens/disabled-rules` file. Use inline suppression for per-occurrence overrides. When editing pi-lens itself, move a rule file to the `<language>-disabled/` directory to prevent it from running.
|
|
146
166
|
|
|
147
167
|
### Ast-Grep Rules
|
|
148
168
|
|
|
@@ -268,6 +288,7 @@ pi --lens-semgrep-config p/ci # Explicit Semgrep config for dispatch (requires
|
|
|
268
288
|
## Key Commands
|
|
269
289
|
|
|
270
290
|
- `/lens-toggle` — toggle pi-lens on/off for the current session without restarting
|
|
291
|
+
- `/lens-widget-toggle` — show/hide the pi-lens diagnostics widget below the editor
|
|
271
292
|
- `/lens-booboo` — full quality report for current project state
|
|
272
293
|
- `/lens-health` — runtime health, latency, and diagnostic telemetry
|
|
273
294
|
- `/lens-tools` — tool installation status: globally installed, auto-installed, or npx fallback
|
package/clients/cache-manager.ts
CHANGED
|
@@ -43,6 +43,7 @@ export interface TurnState {
|
|
|
43
43
|
turnCycles: number;
|
|
44
44
|
maxCycles: number;
|
|
45
45
|
lastUpdated: string;
|
|
46
|
+
sessionId?: string;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
// --- Defaults ---
|
|
@@ -250,8 +251,10 @@ export class CacheManager {
|
|
|
250
251
|
range: ModifiedRange,
|
|
251
252
|
importsChanged: boolean,
|
|
252
253
|
cwd: string,
|
|
254
|
+
sessionId?: string,
|
|
253
255
|
): TurnState {
|
|
254
256
|
const state = this.readTurnState(cwd);
|
|
257
|
+
if (sessionId) state.sessionId = sessionId;
|
|
255
258
|
const normalizedPath = this.toTurnStateKey(filePath, cwd);
|
|
256
259
|
|
|
257
260
|
const existing = state.files[normalizedPath];
|
|
@@ -742,7 +742,8 @@ export async function dispatchForFile(
|
|
|
742
742
|
|
|
743
743
|
// Format output — only blocking issues shown inline
|
|
744
744
|
// Warnings tracked but not shown (noise) — surfaced via /lens-booboo
|
|
745
|
-
|
|
745
|
+
const blockerOutput = formatDiagnostics(inlineBlockers, "blocking");
|
|
746
|
+
let output = blockerOutput;
|
|
746
747
|
output += formatDiagnostics(inlineFixed, "fixed");
|
|
747
748
|
if (coverageNotice) {
|
|
748
749
|
output += formatDiagnostics([coverageNotice], "warning", 1);
|
|
@@ -807,6 +808,7 @@ export async function dispatchForFile(
|
|
|
807
808
|
fixed: fixedItems,
|
|
808
809
|
resolvedCount,
|
|
809
810
|
output,
|
|
811
|
+
blockerOutput,
|
|
810
812
|
hasBlockers: blockers.length > 0,
|
|
811
813
|
};
|
|
812
814
|
}
|
|
@@ -48,7 +48,7 @@ import type { CascadeResult } from "../cascade-types.js";
|
|
|
48
48
|
import { getDiagnosticTracker } from "../diagnostic-tracker.js";
|
|
49
49
|
import { getServersForFileWithConfig } from "../lsp/config.js";
|
|
50
50
|
import { getLSPService } from "../lsp/index.js";
|
|
51
|
-
import { normalizeMapKey } from "../path-utils.js";
|
|
51
|
+
import { isExternalOrVendorFile, normalizeMapKey } from "../path-utils.js";
|
|
52
52
|
import {
|
|
53
53
|
clearReviewGraphWorkspaceCache,
|
|
54
54
|
getLastGraphBuildInfo,
|
|
@@ -325,7 +325,8 @@ function withSemgrepGroup(
|
|
|
325
325
|
config: ctx.pi.getFlag("lens-semgrep-config"),
|
|
326
326
|
});
|
|
327
327
|
if (!config.enabled) return groups;
|
|
328
|
-
if (groups.some((group) => group.runnerIds.includes("semgrep")))
|
|
328
|
+
if (groups.some((group) => group.runnerIds.includes("semgrep")))
|
|
329
|
+
return groups;
|
|
329
330
|
return [
|
|
330
331
|
...groups,
|
|
331
332
|
{
|
|
@@ -402,16 +403,29 @@ export function resetDispatchBaselines(): void {
|
|
|
402
403
|
clearCoverageNoticeState();
|
|
403
404
|
clearReviewGraphWorkspaceCache();
|
|
404
405
|
neighborTouchCache.clear();
|
|
406
|
+
recentlyCleanNeighborCache.clear();
|
|
405
407
|
primaryFilesThisTurn.clear();
|
|
406
408
|
cascadeDiagnosticBaselines.clear();
|
|
407
|
-
cascadeSessionStats = {
|
|
409
|
+
cascadeSessionStats = {
|
|
410
|
+
runs: 0,
|
|
411
|
+
diagnosticsSurfaced: 0,
|
|
412
|
+
coldSnapshotTouches: 0,
|
|
413
|
+
};
|
|
408
414
|
for (const timer of astGrepWarnDebounceTimers.values()) clearTimeout(timer);
|
|
409
415
|
astGrepWarnDebounceTimers.clear();
|
|
410
416
|
}
|
|
411
417
|
|
|
412
|
-
let cascadeSessionStats = {
|
|
418
|
+
let cascadeSessionStats = {
|
|
419
|
+
runs: 0,
|
|
420
|
+
diagnosticsSurfaced: 0,
|
|
421
|
+
coldSnapshotTouches: 0,
|
|
422
|
+
};
|
|
413
423
|
|
|
414
|
-
export function getCascadeSessionStats(): {
|
|
424
|
+
export function getCascadeSessionStats(): {
|
|
425
|
+
runs: number;
|
|
426
|
+
diagnosticsSurfaced: number;
|
|
427
|
+
coldSnapshotTouches: number;
|
|
428
|
+
} {
|
|
415
429
|
return { ...cascadeSessionStats };
|
|
416
430
|
}
|
|
417
431
|
|
|
@@ -425,6 +439,16 @@ type NeighborCacheEntry = {
|
|
|
425
439
|
};
|
|
426
440
|
const neighborTouchCache = new Map<string, NeighborCacheEntry>();
|
|
427
441
|
|
|
442
|
+
// Cross-turn clean cache: neighbor touches that recently returned no errors can
|
|
443
|
+
// be skipped for a few turns. LSP servers push diagnostics proactively when a
|
|
444
|
+
// file becomes unhealthy, so repeatedly re-opening known-clean neighbors is low value.
|
|
445
|
+
type RecentlyCleanNeighborEntry = { turnSeq: number; checkedAt: number };
|
|
446
|
+
const recentlyCleanNeighborCache = new Map<
|
|
447
|
+
string,
|
|
448
|
+
RecentlyCleanNeighborEntry
|
|
449
|
+
>();
|
|
450
|
+
const RECENTLY_CLEAN_TTL_TURNS = 5;
|
|
451
|
+
|
|
428
452
|
// B10: tracks files that were the *primary* edited file this turn.
|
|
429
453
|
// These are excluded from cascade neighbor results — their own pipeline run
|
|
430
454
|
// already reported their diagnostics authoritatively.
|
|
@@ -436,11 +460,17 @@ function ensureCascadeTurnScope(turnSeq: number): void {
|
|
|
436
460
|
cascadeTurnScope = turnSeq;
|
|
437
461
|
primaryFilesThisTurn.clear();
|
|
438
462
|
neighborTouchCache.clear();
|
|
463
|
+
for (const [key, entry] of recentlyCleanNeighborCache) {
|
|
464
|
+
if (turnSeq - entry.turnSeq > RECENTLY_CLEAN_TTL_TURNS) {
|
|
465
|
+
recentlyCleanNeighborCache.delete(key);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
439
468
|
}
|
|
440
469
|
|
|
441
470
|
const CASCADE_TTL_MS = 240_000;
|
|
442
471
|
const MAX_PER_FILE = RUNTIME_CONFIG.pipeline.cascadeMaxDiagnosticsPerFile;
|
|
443
472
|
const MAX_FILES = RUNTIME_CONFIG.pipeline.cascadeMaxFiles;
|
|
473
|
+
const CASCADE_GRAPH_KINDS = new Set(["jsts", "python", "go", "rust", "ruby"]);
|
|
444
474
|
|
|
445
475
|
/**
|
|
446
476
|
* Unified cascade orchestration — builds graph, discovers neighbors, and
|
|
@@ -477,7 +507,8 @@ export async function computeCascadeForFile(
|
|
|
477
507
|
return undefined;
|
|
478
508
|
}
|
|
479
509
|
|
|
480
|
-
|
|
510
|
+
const fileKind = detectFileKind(filePath);
|
|
511
|
+
if (!fileKind) {
|
|
481
512
|
logCascade({ phase: "cascade_skip", filePath, reason: "non_code_file" });
|
|
482
513
|
return undefined;
|
|
483
514
|
}
|
|
@@ -489,54 +520,87 @@ export async function computeCascadeForFile(
|
|
|
489
520
|
// turn won't show it as a neighbor.
|
|
490
521
|
primaryFilesThisTurn.add(normalizedFileKey);
|
|
491
522
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
523
|
+
let impact: ReturnType<typeof computeImpactCascade> = {
|
|
524
|
+
filePath: normalizedFile,
|
|
525
|
+
changedSymbols: [],
|
|
526
|
+
directImporters: [],
|
|
527
|
+
directCallers: [],
|
|
528
|
+
neighborFiles: [],
|
|
529
|
+
riskFlags: [],
|
|
530
|
+
};
|
|
531
|
+
let sortedNeighbors: string[] = [];
|
|
532
|
+
let importerSet = new Set<string>();
|
|
533
|
+
let callerSet = new Set<string>();
|
|
534
|
+
let referenceCount = 0;
|
|
535
|
+
|
|
536
|
+
if (CASCADE_GRAPH_KINDS.has(fileKind)) {
|
|
537
|
+
const graphStart = Date.now();
|
|
538
|
+
const graph = await buildOrUpdateGraph(cwd, [normalizedFile], sessionFacts);
|
|
539
|
+
const graphMs = Date.now() - graphStart;
|
|
540
|
+
|
|
541
|
+
// Count files represented in the graph (nodes with a filePath).
|
|
542
|
+
const graphFileCount = new Set(
|
|
543
|
+
[...graph.nodes.values()].flatMap((n) =>
|
|
544
|
+
n.filePath ? [n.filePath] : [],
|
|
545
|
+
),
|
|
546
|
+
).size;
|
|
500
547
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
548
|
+
const graphBuildInfo = getLastGraphBuildInfo();
|
|
549
|
+
logCascade({
|
|
550
|
+
phase: "graph_build",
|
|
551
|
+
filePath,
|
|
552
|
+
graphBuiltMs: graphMs,
|
|
553
|
+
graphReused: graphBuildInfo.reused,
|
|
554
|
+
graphNodeCount: graph.nodes.size,
|
|
555
|
+
graphFileCount,
|
|
556
|
+
graphChangedSymbolCount: (
|
|
557
|
+
graph.changedSymbolsByFile.get(normalizedFileKey) ?? []
|
|
558
|
+
).length,
|
|
559
|
+
metadata: { graphBuildMode: graphBuildInfo.mode },
|
|
560
|
+
});
|
|
514
561
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
562
|
+
impact = computeImpactCascade(graph, normalizedFile);
|
|
563
|
+
|
|
564
|
+
// Sort by relationship strength (B6) then cap to MAX_FILES.
|
|
565
|
+
// directImporters are most impactful, then callers, then reference edges.
|
|
566
|
+
importerSet = new Set(impact.directImporters);
|
|
567
|
+
callerSet = new Set(impact.directCallers);
|
|
568
|
+
// neighbors that are neither direct importers nor callers are reference-edge neighbors
|
|
569
|
+
const importerOrCallerSet = new Set([
|
|
570
|
+
...impact.directImporters,
|
|
571
|
+
...impact.directCallers,
|
|
572
|
+
]);
|
|
573
|
+
referenceCount = impact.neighborFiles.filter(
|
|
574
|
+
(n) => !importerOrCallerSet.has(n),
|
|
575
|
+
).length;
|
|
576
|
+
sortedNeighbors = [...impact.neighborFiles]
|
|
577
|
+
.filter((n) => nodeFs.existsSync(n))
|
|
578
|
+
.filter((n) => !isExternalOrVendorFile(n, cwd))
|
|
579
|
+
// B10: exclude files already edited as primary this turn — their own pipeline
|
|
580
|
+
// run is the authoritative diagnostic source; showing them as neighbors is noise.
|
|
581
|
+
.filter((n) => !primaryFilesThisTurn.has(normalizeMapKey(n)))
|
|
582
|
+
.sort((a, b) => {
|
|
583
|
+
const rank = (p: string) =>
|
|
584
|
+
importerSet.has(p) ? 0 : callerSet.has(p) ? 1 : 2;
|
|
585
|
+
return rank(a) - rank(b);
|
|
586
|
+
})
|
|
587
|
+
.slice(0, MAX_FILES);
|
|
588
|
+
} else {
|
|
589
|
+
logCascade({
|
|
590
|
+
phase: "graph_build",
|
|
591
|
+
filePath,
|
|
592
|
+
graphBuiltMs: 0,
|
|
593
|
+
graphReused: false,
|
|
594
|
+
graphNodeCount: 0,
|
|
595
|
+
graphFileCount: 0,
|
|
596
|
+
graphChangedSymbolCount: 0,
|
|
597
|
+
metadata: {
|
|
598
|
+
graphBuildMode: "skipped",
|
|
599
|
+
reason: "unsupported_kind",
|
|
600
|
+
fileKind,
|
|
601
|
+
},
|
|
602
|
+
});
|
|
603
|
+
}
|
|
540
604
|
|
|
541
605
|
logCascade({
|
|
542
606
|
phase: "neighbors_computed",
|
|
@@ -634,6 +698,41 @@ export async function computeCascadeForFile(
|
|
|
634
698
|
const neighborStart = Date.now();
|
|
635
699
|
const cacheKey = normalizeMapKey(neighborPath);
|
|
636
700
|
|
|
701
|
+
const passiveEntry = allDiags.get(cacheKey);
|
|
702
|
+
const hasFreshPassiveErrors =
|
|
703
|
+
passiveEntry != null &&
|
|
704
|
+
Date.now() - passiveEntry.ts < CASCADE_TTL_MS &&
|
|
705
|
+
passiveEntry.diags.some((d) => d.severity === 1);
|
|
706
|
+
const recentlyClean = recentlyCleanNeighborCache.get(cacheKey);
|
|
707
|
+
if (
|
|
708
|
+
recentlyClean &&
|
|
709
|
+
turnSeq - recentlyClean.turnSeq <= RECENTLY_CLEAN_TTL_TURNS &&
|
|
710
|
+
!hasFreshPassiveErrors
|
|
711
|
+
) {
|
|
712
|
+
producedLspData = true;
|
|
713
|
+
const durationMs = Date.now() - neighborStart;
|
|
714
|
+
logCascade({
|
|
715
|
+
phase: "neighbor_snapshot",
|
|
716
|
+
filePath,
|
|
717
|
+
neighborFile: neighborPath,
|
|
718
|
+
diagnosticCount: 0,
|
|
719
|
+
durationMs,
|
|
720
|
+
autoPropagate: false,
|
|
721
|
+
snapshotMissing: false,
|
|
722
|
+
metadata: {
|
|
723
|
+
recentlyClean: true,
|
|
724
|
+
cleanTurnSeq: recentlyClean.turnSeq,
|
|
725
|
+
},
|
|
726
|
+
});
|
|
727
|
+
return {
|
|
728
|
+
filePath: neighborPath,
|
|
729
|
+
reason: neighborReason(importerSet, callerSet, neighborPath),
|
|
730
|
+
diagnostics: [],
|
|
731
|
+
lspTouched: false,
|
|
732
|
+
durationMs,
|
|
733
|
+
} satisfies CascadeResult["neighbors"][number];
|
|
734
|
+
}
|
|
735
|
+
|
|
637
736
|
// A5: skip re-touch if this neighbor was already diagnosed at the current
|
|
638
737
|
// write sequence. A new write (higher writeSeq) invalidates the cache entry.
|
|
639
738
|
const cached =
|
|
@@ -704,6 +803,14 @@ export async function computeCascadeForFile(
|
|
|
704
803
|
diagnostics: diags,
|
|
705
804
|
});
|
|
706
805
|
}
|
|
806
|
+
if (diags.length === 0) {
|
|
807
|
+
recentlyCleanNeighborCache.set(cacheKey, {
|
|
808
|
+
turnSeq,
|
|
809
|
+
checkedAt: Date.now(),
|
|
810
|
+
});
|
|
811
|
+
} else {
|
|
812
|
+
recentlyCleanNeighborCache.delete(cacheKey);
|
|
813
|
+
}
|
|
707
814
|
producedLspData = true;
|
|
708
815
|
|
|
709
816
|
logCascade({
|
|
@@ -769,7 +876,7 @@ export async function computeCascadeForFile(
|
|
|
769
876
|
// CR-3/A2: degraded fallback when no neighbor produced trustworthy LSP data —
|
|
770
877
|
// not merely when the graph returned zero neighbors.
|
|
771
878
|
if (!producedLspData) {
|
|
772
|
-
appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey);
|
|
879
|
+
appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey, cwd);
|
|
773
880
|
if (neighbors.some((n) => n.reason === "fallback")) {
|
|
774
881
|
logCascade({
|
|
775
882
|
phase: "neighbor_fallback",
|
|
@@ -867,6 +974,7 @@ function appendFallbackNeighbors(
|
|
|
867
974
|
{ diags: import("../lsp/client.js").LSPDiagnostic[]; ts: number }
|
|
868
975
|
>,
|
|
869
976
|
normalizedFileKey: string,
|
|
977
|
+
cwd: string,
|
|
870
978
|
): void {
|
|
871
979
|
const now = Date.now();
|
|
872
980
|
const seen = new Set(neighbors.map((n) => normalizeMapKey(n.filePath)));
|
|
@@ -874,6 +982,7 @@ function appendFallbackNeighbors(
|
|
|
874
982
|
const diagKey = normalizeMapKey(diagPath);
|
|
875
983
|
if (diagKey === normalizedFileKey || seen.has(diagKey)) continue;
|
|
876
984
|
if (primaryFilesThisTurn.has(diagKey)) continue;
|
|
985
|
+
if (isExternalOrVendorFile(diagPath, cwd)) continue;
|
|
877
986
|
if (!nodeFs.existsSync(diagPath)) continue;
|
|
878
987
|
if (now - ts > CASCADE_TTL_MS) continue;
|
|
879
988
|
const errors = convertLspDiagnostics(
|
|
@@ -921,7 +1030,10 @@ function formatCascadeResult(
|
|
|
921
1030
|
});
|
|
922
1031
|
if (!diagnosticsBlock) return "";
|
|
923
1032
|
|
|
924
|
-
const impactHeader = formatImpactCascade(
|
|
1033
|
+
const impactHeader = formatImpactCascade(
|
|
1034
|
+
impact,
|
|
1035
|
+
RUNTIME_CONFIG.pipeline.cascadeMaxFiles,
|
|
1036
|
+
);
|
|
925
1037
|
let out = impactHeader
|
|
926
1038
|
? `${impactHeader}\n${diagnosticsBlock}`
|
|
927
1039
|
: diagnosticsBlock;
|
|
@@ -972,7 +1084,11 @@ export async function dispatchLint(
|
|
|
972
1084
|
const kind = ctx.kind;
|
|
973
1085
|
if (!kind) return "";
|
|
974
1086
|
|
|
975
|
-
const groups = withSemgrepGroup(
|
|
1087
|
+
const groups = withSemgrepGroup(
|
|
1088
|
+
kind,
|
|
1089
|
+
getDispatchGroupsForKind(kind, pi),
|
|
1090
|
+
ctx,
|
|
1091
|
+
);
|
|
976
1092
|
if (groups.length === 0) return "";
|
|
977
1093
|
|
|
978
1094
|
await runProviders(ctx);
|
|
@@ -1011,11 +1127,16 @@ export async function dispatchLintWithResult(
|
|
|
1011
1127
|
fixed: [],
|
|
1012
1128
|
resolvedCount: 0,
|
|
1013
1129
|
output: "",
|
|
1130
|
+
blockerOutput: "",
|
|
1014
1131
|
hasBlockers: false,
|
|
1015
1132
|
};
|
|
1016
1133
|
}
|
|
1017
1134
|
|
|
1018
|
-
const groups = withSemgrepGroup(
|
|
1135
|
+
const groups = withSemgrepGroup(
|
|
1136
|
+
kind,
|
|
1137
|
+
getDispatchGroupsForKind(kind, pi),
|
|
1138
|
+
ctx,
|
|
1139
|
+
);
|
|
1019
1140
|
if (groups.length === 0) {
|
|
1020
1141
|
return {
|
|
1021
1142
|
diagnostics: [],
|
|
@@ -1025,6 +1146,7 @@ export async function dispatchLintWithResult(
|
|
|
1025
1146
|
fixed: [],
|
|
1026
1147
|
resolvedCount: 0,
|
|
1027
1148
|
output: "",
|
|
1149
|
+
blockerOutput: "",
|
|
1028
1150
|
hasBlockers: false,
|
|
1029
1151
|
};
|
|
1030
1152
|
}
|
package/clients/dispatch/plan.ts
CHANGED
|
@@ -35,6 +35,7 @@ export const LANGUAGE_CAPABILITY_MATRIX: Record<
|
|
|
35
35
|
writeGroups: [
|
|
36
36
|
primary("jsts"),
|
|
37
37
|
{ mode: "all", runnerIds: ["tree-sitter"], filterKinds: ["jsts"] },
|
|
38
|
+
{ mode: "all", runnerIds: ["fact-rules"], filterKinds: ["jsts"] },
|
|
38
39
|
{ mode: "all", runnerIds: ["ast-grep-napi"], filterKinds: ["jsts"] },
|
|
39
40
|
{ mode: "fallback", runnerIds: ["type-safety"], filterKinds: ["jsts"] },
|
|
40
41
|
{
|
|
@@ -59,6 +60,7 @@ export const LANGUAGE_CAPABILITY_MATRIX: Record<
|
|
|
59
60
|
{ mode: "fallback", runnerIds: ["ruff-lint"], filterKinds: ["python"] },
|
|
60
61
|
{ mode: "fallback", runnerIds: ["mypy"], filterKinds: ["python"] },
|
|
61
62
|
{ mode: "all", runnerIds: ["tree-sitter"], filterKinds: ["python"] },
|
|
63
|
+
{ mode: "all", runnerIds: ["fact-rules"], filterKinds: ["python"] },
|
|
62
64
|
],
|
|
63
65
|
fullOnlyGroups: [
|
|
64
66
|
{ mode: "fallback", runnerIds: ["python-slop"], filterKinds: ["python"] },
|
|
@@ -72,6 +74,7 @@ export const LANGUAGE_CAPABILITY_MATRIX: Record<
|
|
|
72
74
|
{ mode: "fallback", runnerIds: ["go-vet"], filterKinds: ["go"] },
|
|
73
75
|
{ mode: "fallback", runnerIds: ["golangci-lint"], filterKinds: ["go"] },
|
|
74
76
|
{ mode: "all", runnerIds: ["tree-sitter"], filterKinds: ["go"] },
|
|
77
|
+
{ mode: "all", runnerIds: ["fact-rules"], filterKinds: ["go"] },
|
|
75
78
|
],
|
|
76
79
|
},
|
|
77
80
|
rust: {
|
|
@@ -81,6 +84,7 @@ export const LANGUAGE_CAPABILITY_MATRIX: Record<
|
|
|
81
84
|
primary("rust"),
|
|
82
85
|
{ mode: "fallback", runnerIds: ["rust-clippy"], filterKinds: ["rust"] },
|
|
83
86
|
{ mode: "all", runnerIds: ["tree-sitter"], filterKinds: ["rust"] },
|
|
87
|
+
{ mode: "all", runnerIds: ["fact-rules"], filterKinds: ["rust"] },
|
|
84
88
|
],
|
|
85
89
|
},
|
|
86
90
|
ruby: {
|
|
@@ -90,6 +94,7 @@ export const LANGUAGE_CAPABILITY_MATRIX: Record<
|
|
|
90
94
|
primary("ruby"),
|
|
91
95
|
{ mode: "fallback", runnerIds: ["rubocop"], filterKinds: ["ruby"] },
|
|
92
96
|
{ mode: "all", runnerIds: ["tree-sitter"], filterKinds: ["ruby"] },
|
|
97
|
+
{ mode: "all", runnerIds: ["fact-rules"], filterKinds: ["ruby"] },
|
|
93
98
|
],
|
|
94
99
|
},
|
|
95
100
|
cxx: {
|
|
@@ -100,12 +105,18 @@ export const LANGUAGE_CAPABILITY_MATRIX: Record<
|
|
|
100
105
|
cmake: {
|
|
101
106
|
name: "CMake Processing",
|
|
102
107
|
capabilities: ["lint"],
|
|
103
|
-
writeGroups: [
|
|
108
|
+
writeGroups: [
|
|
109
|
+
primary("cmake"),
|
|
110
|
+
{ mode: "all", runnerIds: ["fact-rules"], filterKinds: ["cmake"] },
|
|
111
|
+
],
|
|
104
112
|
},
|
|
105
113
|
shell: {
|
|
106
114
|
name: "Shell Script Linting",
|
|
107
115
|
capabilities: ["lint", "security"],
|
|
108
|
-
writeGroups: [
|
|
116
|
+
writeGroups: [
|
|
117
|
+
primary("shell"),
|
|
118
|
+
{ mode: "all", runnerIds: ["fact-rules"], filterKinds: ["shell"] },
|
|
119
|
+
],
|
|
109
120
|
},
|
|
110
121
|
json: {
|
|
111
122
|
name: "JSON Processing",
|
|
@@ -24,8 +24,8 @@ export const errorSwallowingRule: FactRule = {
|
|
|
24
24
|
filePath: ctx.filePath,
|
|
25
25
|
line: s.line,
|
|
26
26
|
column: s.column,
|
|
27
|
-
severity: "
|
|
28
|
-
semantic: "
|
|
27
|
+
severity: "error",
|
|
28
|
+
semantic: "blocking",
|
|
29
29
|
message: `Empty catch block silently swallows errors`,
|
|
30
30
|
});
|
|
31
31
|
}
|