pi-lens 3.8.40 → 3.8.42
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 +113 -0
- package/README.md +73 -16
- package/clients/cache/rule-cache.ts +1 -1
- package/clients/cache-manager.ts +3 -0
- package/clients/complexity-client.ts +1 -1
- package/clients/dependency-checker.ts +1 -1
- package/clients/dispatch/diagnostic-taxonomy.ts +13 -1
- package/clients/dispatch/dispatcher.ts +9 -0
- package/clients/dispatch/fact-scheduler.ts +1 -1
- package/clients/dispatch/integration.ts +228 -55
- 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/index.ts +2 -0
- package/clients/dispatch/runners/oxlint.ts +51 -10
- package/clients/dispatch/runners/semgrep.ts +269 -0
- package/clients/dispatch/runners/shellcheck.ts +2 -8
- package/clients/dispatch/runners/tree-sitter.ts +53 -21
- package/clients/dispatch/tool-profile.ts +1 -0
- package/clients/file-utils.ts +32 -0
- package/clients/format-service.ts +10 -0
- package/clients/formatters.ts +31 -8
- package/clients/installer/index.ts +126 -4
- package/clients/knip-client.ts +357 -362
- package/clients/lsp/aggregation.ts +91 -0
- package/clients/lsp/client.ts +29 -11
- package/clients/lsp/index.ts +121 -86
- package/clients/lsp/server-strategies.ts +71 -0
- package/clients/path-utils.ts +29 -0
- package/clients/pipeline.ts +7 -1
- package/clients/production-readiness.ts +2 -2
- package/clients/read-guard-logger.ts +41 -1
- package/clients/read-guard-tool-lines.ts +3 -3
- package/clients/read-guard.ts +40 -11
- package/clients/runtime-agent-end.ts +3 -0
- package/clients/runtime-config.ts +5 -0
- package/clients/runtime-context.ts +3 -3
- package/clients/runtime-session.ts +33 -10
- package/clients/runtime-tool-result.ts +26 -1
- package/clients/runtime-turn.ts +137 -102
- package/clients/sanitize.ts +1 -1
- package/clients/semgrep-config.ts +213 -0
- 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 +26 -5
- package/clients/widget-state.ts +283 -0
- package/commands/booboo.ts +15 -3
- package/index.ts +262 -13
- package/package.json +3 -2
- 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/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/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,119 @@ All notable changes to pi-lens will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [3.8.42] - 2026-05-08
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **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.
|
|
12
|
+
- **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.
|
|
13
|
+
- **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.
|
|
14
|
+
- **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.
|
|
15
|
+
- **Fixed `inline_tier: error` typo** on `ts-hallucinated-react-import` and `python-hallucinated-import` (→ `blocking`).
|
|
16
|
+
- **13 new high-confidence blocking promotions across 5 languages** (all `severity: error`, `inline_tier: blocking`):
|
|
17
|
+
- *TypeScript:* `ts-weak-hash` (`createHash("md5"/"sha1")` — confidence: high)
|
|
18
|
+
- *Python:* `python-command-injection`, `python-sql-injection`, `python-insecure-deserialization`, `python-weak-hash`
|
|
19
|
+
- *Go:* `go-command-injection`, `go-sql-injection`, `go-shared-map-write-goroutine`, `go-weak-hash`
|
|
20
|
+
- *Ruby:* `ruby-weak-hash`
|
|
21
|
+
- *Rust:* `rust-lock-held-across-await`
|
|
22
|
+
- **4 new blocking tree-sitter rules (SonarCloud BLOCKER equivalents)**:
|
|
23
|
+
- `ts-xss-dom-sink` (S5696) — flags dynamic values assigned to `innerHTML`/`outerHTML` or passed to `document.write()` / `document.writeln()`
|
|
24
|
+
- `ts-dynamic-require` (S5335) — flags `require()` called with a non-string-literal argument (arbitrary module loading)
|
|
25
|
+
- `ts-open-redirect` (S6105) — flags `res.redirect(variable)` / `response.redirect` / `ctx.redirect` with dynamic URL, and `window.location.href = variable`
|
|
26
|
+
- `ts-nosql-injection` (S5147) — flags any MongoDB `$where` key (JS-execution sink, dangerous regardless of value)
|
|
27
|
+
- **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.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **`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.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- **`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.
|
|
36
|
+
- **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`.
|
|
37
|
+
- **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).
|
|
38
|
+
- **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.
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- **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.
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
|
|
48
|
+
- **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.
|
|
49
|
+
- **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.
|
|
50
|
+
- **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.
|
|
51
|
+
- **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".
|
|
52
|
+
- **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.
|
|
53
|
+
- **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.
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
|
|
57
|
+
- **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.
|
|
58
|
+
|
|
59
|
+
- **`/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.
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
|
|
63
|
+
- **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.
|
|
64
|
+
- **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.
|
|
65
|
+
- **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.
|
|
66
|
+
|
|
67
|
+
### Fixed
|
|
68
|
+
|
|
69
|
+
- **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.
|
|
70
|
+
|
|
71
|
+
- **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.
|
|
72
|
+
|
|
73
|
+
- **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.
|
|
74
|
+
|
|
75
|
+
- **`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.
|
|
76
|
+
|
|
77
|
+
## [3.8.41] - 2026-05-05
|
|
78
|
+
|
|
79
|
+
### Fixed
|
|
80
|
+
|
|
81
|
+
- **tree-sitter wasm abort loop and memory leak (fixes #56)** — when the emscripten wasm runtime aborts (OOM or assertion failure on large workspaces), the module-level heap is permanently corrupted. pi-lens was re-invoking the dead runtime on every subsequent file write, printing `Aborted()` to stderr on each query and leaking memory on each retry. Added a module-level `_wasmAborted` flag: the first abort detected in the query catch loop poisons the singleton and prevents any further tree-sitter calls for the session. The runner skips cleanly with `reason: wasm_aborted_fatal` logged to `tree-sitter.log`.
|
|
82
|
+
- **`turn_end` phases now instrumented in latency log** — `handleTurnEnd` previously had no `logLatency` calls; all timing data was buried in plain-text `dbg()` lines in `sessionstart.log`. Added per-phase latency entries for `cascade_merge`, `jscpd`, `knip`, and `madge`, plus a `tool_result` total with `fileCount` and `blockerSections`. This gives a baseline for measuring the cost of future turn_end additions (e.g. LSP re-query).
|
|
83
|
+
- **Cascade ran graph build on non-code files** — markdown, YAML, JSON, and other files without a dispatchable kind were reaching `buildOrUpdateGraph`, causing cold graph builds that took up to 3–4 seconds per write with zero useful output. `computeCascadeForFile` now exits immediately with `cascade_skip / non_code_file` when `detectFileKind` returns `undefined`, consistent with the existing `shouldDispatch` gate used by the lint pipeline.
|
|
84
|
+
|
|
85
|
+
### Added
|
|
86
|
+
|
|
87
|
+
- **Per-server LSP diagnostic strategies** — new `clients/lsp/server-strategies.ts` codifies known server behavior (TypeScript, rust-analyzer, pyright, ESLint) so timing decisions are automatic rather than one-size-fits-all. Strategies control first-push seeding, debounce window, pull retry budget, aggregate wait timeout, and whether a server benefits from a semantic second pull pass. Env var overrides (`PI_LENS_LSP_*`) take precedence. Unknown servers get a conservative default.
|
|
88
|
+
- **Result-aware diagnostic racing (`raceToCompletion`)** — new `clients/lsp/aggregation.ts` replaces the simple `Promise.race` + grace window pattern with a result-quality-aware aggregator. The grace window only triggers when at least one client has returned non-empty diagnostics, preventing premature resolution when the fastest client returns empty (e.g., TypeScript's syntactic pass). Document mode uses 0ms grace; full mode keeps the 400ms default.
|
|
89
|
+
- **`seedFirstPush` early-exit for clean files** — `raceToCompletion`'s completion predicate now also fires when a `seedFirstPush` server (TypeScript, ESLint) returns any result, even an empty one. These servers' first push is authoritative — waiting further yields nothing. Cuts clean-file diagnostic latency from ~1000ms to ~450ms in full mode and to near-zero in document mode (cascade neighbor touches).
|
|
90
|
+
|
|
91
|
+
- **`/lens-toggle` session switch** — added a single command to toggle pi-lens on/off at runtime without restarting pi. When off, write/edit analysis, read-guard, formatting, cascade, turn-end checks, and context injection are paused; running `/lens-toggle` again resumes them. `--no-lens` starts a session in the disabled state. Closes #49.
|
|
92
|
+
- **Experimental Semgrep CLI dispatch integration** — added a config-gated `semgrep` dispatch runner that normalizes Semgrep JSON findings into pi-lens diagnostics. The runner never auto-installs Semgrep and only runs when a local `.semgrep.yml`/`.semgrep.yaml`/`semgrep.yml`/`semgrep.yaml` is discovered or when explicitly configured with `--lens-semgrep --lens-semgrep-config <auto|p/pack|path>` / `/lens-semgrep enable --config <...>`. Dispatch scans pass `--metrics=off`; local rule scans do not require a Semgrep token, while Semgrep AppSec/Pro/managed configs may require `semgrep login` or `SEMGREP_APP_TOKEN`.
|
|
93
|
+
- **`/lens-semgrep` command** — new project command for managing Semgrep dispatch: `status` shows CLI/config/effective state, `init` writes a starter `.semgrep.yml` and enables dispatch, `enable [--config <auto|p/pack|path>]` persists activation in `.pi-lens/semgrep.json`, `disable` persists opt-out, and `clear` removes the pi-lens Semgrep config to return to local-config auto-discovery.
|
|
94
|
+
- **Semgrep severity policy metadata** — Semgrep rules can opt into pi-lens blocking semantics with metadata such as `metadata.pi-lens.semantic: blocking` and `metadata.pi-lens.defect_class: injection`. Otherwise, pi-lens promotes only high-signal Semgrep `ERROR` findings in security defect classes (`injection`, `secrets`, `safety`) to blockers and leaves other findings as warnings.
|
|
95
|
+
- **Experimental terminal dashboard** — `--lens-dashboard` / `PI_LENS_DASHBOARD=1` streams redacted session telemetry to a per-session JSONL file (`~/.pi-lens/dashboard-events/{sessionId}.jsonl`) and opens a live terminal dashboard. The dashboard shows the working folder, detected languages, formatter/linter activity, LSP servers spawned, diagnostics grouped by file with OSC-8 clickable links, and a session-start summary of languages, tools, configs, and autoinstalls. Each session gets its own event file; old files are pruned after 7 days (configurable via `PI_LENS_DASHBOARD_RETENTION_DAYS`). Use `PI_LENS_DASHBOARD_LOG_ONLY=1` to emit JSONL without opening a terminal. The viewer auto-scrolls to the latest content on each render.
|
|
96
|
+
|
|
97
|
+
### Changed
|
|
98
|
+
|
|
99
|
+
- **LSP diagnostic pipeline latency optimization** — six targeted refactors reduce per-file diagnostic wait times by 50–900ms depending on the language server: first-push seeding skips the debounce timer for TypeScript and ESLint (~150–200ms saved); adaptive debounce computes remaining wait from `pushDiagnosticTimestamps` (50–140ms saved); per-server aggregate wait times (1000ms for TypeScript, 3000ms for rust-analyzer, 1500ms default); semantic settle pass gated to rust-analyzer only; pull retry budget zeroed for TypeScript/ESLint. Global constants `DIAGNOSTICS_DEBOUNCE_MS`, `PULL_DIAGNOSTICS_RETRY_BUDGET_MS`, and `DIAGNOSTICS_AGGREGATE_WAIT_MS` replaced by per-server strategy values from the new `server-strategies.ts`.
|
|
100
|
+
|
|
101
|
+
### Fixed
|
|
102
|
+
|
|
103
|
+
- **Cascade neighbor touch cache ignores `writeSeq` on hit** — the A5 neighbor touch cache checked only `turnSeq` on cache hits, so a neighbor diagnosed at writeSeq=1 was served stale results when a second file write (writeSeq=2) cascaded to the same neighbor in the same turn. Fixed by requiring both `turnSeq` and `writeSeq` to match before using the cached entry.
|
|
104
|
+
- **Cascade fallback neighbors include other primary files** — `appendFallbackNeighbors` (the degraded-LSP path) excluded only the current primary file from the passive diagnostic snapshot sweep, but not other files edited as primary this turn. Those files could appear as cascade neighbors even though their own pipeline run is the authoritative diagnostic source. Fixed by adding a `primaryFilesThisTurn` check consistent with the B10 filter in the main neighbor path.
|
|
105
|
+
|
|
106
|
+
- **Semgrep dispatch plan regression** — kept the experimental Semgrep runner out of static `TOOL_PLANS` exposure and appends it only at runtime when Semgrep is actually configured. Fixes CI regressions in plan-shape tests while preserving config-gated Semgrep dispatch.
|
|
107
|
+
- **Widget theme method binding crash** — `renderWidget` now calls `theme.fg(...)` directly instead of destructuring `fg`, preserving the `this` binding required by pi's `Theme` class. Fixes the `Cannot read properties of undefined (reading 'fgColors')` widget render crash. Closes #53.
|
|
108
|
+
- **Read-guard follow-up edits after own writes** — tuned `file_modified` handling so a file changed by the agent's own prior allowed edit, immediate format, autofix, or deferred `agent_end` formatting does not force a redundant re-read when the next edit is still within already-read ranges. The guard still blocks zero-read and out-of-range edits, and external/stale changes outside the own-edit grace window remain protected. `PI_LENS_READ_GUARD_OWN_EDIT_GRACE_MS` controls the default 120s grace window.
|
|
109
|
+
- **Read-guard log noise and growth** — `~/.pi-lens/read-guard.log` now defaults to block/warn/anomaly events instead of logging every read and allowed edit. Verbose logging is available with `PI_LENS_READ_GUARD_VERBOSE=1` or `PI_LENS_READ_GUARD_LOG=verbose`; allowed-edit logging can be restored with `PI_LENS_READ_GUARD_LOG_ALLOWS=1`. The log now rotates at 1MB by default (`PI_LENS_READ_GUARD_MAX_BYTES`).
|
|
110
|
+
- **Pipelines skipped for external and vendor files** — agents reading dependency source (global npm packages, project-local `node_modules`) previously triggered LSP server spawns, tree-sitter read-range expansion, read-guard recording, and complexity baseline capture on those files — all noise with no diagnostic value. Added `isExternalOrVendorFile()` (built on the existing `isUnderDir` helper for correct Windows case handling) and gated all five pipeline paths: LSP auto-touch, tree-sitter expansion, read-guard recording, complexity baseline, and the full dispatch pipeline on write/edit.
|
|
111
|
+
- **Security: absolute paths for `cmd.exe` and `osascript` spawn calls** — dashboard terminal launch now resolves both executables via `process.env.SystemRoot` / absolute macOS path instead of relying on `PATH`, eliminating the SonarCloud S4036 PATH-injection finding.
|
|
112
|
+
- **Security: installed binary permissions tightened** — `chmod` calls on downloaded tool binaries changed from `0o755` to `0o750`, removing world-execute permission (SonarCloud S2612). GitHub Actions `contents: write` permission moved from workflow level to the `release` job only (S8233).
|
|
113
|
+
- **Agent messages: full-file-read options removed** — read-guard block messages no longer offer "read the full file" as an alternative. The out-of-range block now presents only the pre-computed targeted `offset`/`limit`; the zero-read block gives a single imperative directive. "Re-read the file" fallback text in ambiguous-edit messages replaced with "Re-read the relevant section" throughout.
|
|
114
|
+
- **Agent messages: indentation-mismatch RETRYABLE made explicitly directive** — the block now opens with "Retry the same edit call immediately with the corrected oldText shown below — copy it exactly as-is" and labels each corrected entry with "do not shorten, do not change newText", preventing agents from improvising instead of copying the corrected text verbatim.
|
|
115
|
+
- **SonarCloud reliability fixes** — five `.sort()` calls on string arrays given explicit `localeCompare` comparators (S2871); three identical-branch conditionals collapsed (S3923 in `knip-client.ts`, `shellcheck.ts`, `production-readiness.ts`); emoji character class converted to alternation to handle multi-codepoint variation-selector emojis (S5868); regex alternation precedence made explicit with non-capturing groups (S5850); `| 0` in hash function annotated as intentional 32-bit truncation (S7767).
|
|
116
|
+
- **CI: build step added before tests** — Vitest's native ESM resolver requires compiled `.js` output when `vi.resetModules()` is used; without a prior `tsc` build, imports of newly-added exports resolved as `undefined` in CI.
|
|
117
|
+
- **Widget: diagnostic rows exceeded terminal width** — the custom `truncate()` helper stripped ANSI sequences to measure length but sliced the raw string, losing OSC-8 hyperlinks and SGR sequences from the count. Replaced with pi-tui's `truncateToWidth()` / `visibleWidth()` which correctly account for all escape sequences. All widget lines (header, file rows, separators, diagnostic detail, LSP status) are now clamped. Closes #54.
|
|
118
|
+
- **Widget: file list capped at 5 entries, basename deduplication** — reduced max file rows from 6 to 5 to keep the widget compact. Added basename deduplication (last write wins) so that different files with the same name (e.g. `pi-lens/index.ts` and `pi-webaio/index.ts`) show as a single merged entry instead of flooding the widget with near-identical labels.
|
|
119
|
+
|
|
7
120
|
## [3.8.40] - 2026-05-04
|
|
8
121
|
|
|
9
122
|
### Added
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ On every `write` and `edit`, pi-lens runs a fast, language-aware pipeline (check
|
|
|
16
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
|
|
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
|
-
5. **Dispatch lint** — parallel runner groups: LSP diagnostics, tree-sitter structural rules, ast-grep security/correctness rules, fact rules, language-specific linters, similarity detection
|
|
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
|
|
20
20
|
6. **Cascade diagnostics** — review-graph impact cascade showing which other files were affected and how diagnostics propagated
|
|
21
21
|
|
|
22
22
|
Results are inline and actionable:
|
|
@@ -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
|
|
140
158
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
-
|
|
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.
|
|
164
|
+
|
|
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
|
|
|
@@ -153,6 +173,36 @@ Structural rules are organized by language in `rules/tree-sitter-queries/`:
|
|
|
153
173
|
- **Style/smells** — nested-ternary, long-parameter-list, large-class, prefer-optional-chain, redundant-state, require-await
|
|
154
174
|
- **Agent stubs** — no-unimplemented-stub, no-raise-not-implemented, no-ellipsis-body
|
|
155
175
|
|
|
176
|
+
### Semgrep CLI Integration (Experimental)
|
|
177
|
+
|
|
178
|
+
pi-lens can run the locally installed `semgrep` CLI as an optional dispatch runner for security-focused findings. Semgrep diagnostics are normalized into the same pi-lens `Diagnostic` model as LSP, tree-sitter, ast-grep, and linters: high-signal security findings can become blocking, while other findings remain warnings for `/lens-booboo`/history.
|
|
179
|
+
|
|
180
|
+
Activation is intentionally gated:
|
|
181
|
+
|
|
182
|
+
- pi-lens **does not auto-install Semgrep**.
|
|
183
|
+
- A local `.semgrep.yml`, `.semgrep.yaml`, `semgrep.yml`, or `semgrep.yaml` enables the runner when the `semgrep` CLI is available.
|
|
184
|
+
- Without a local config, Semgrep stays skipped unless explicitly configured with `--lens-semgrep --lens-semgrep-config <auto|p/pack|path>` or `/lens-semgrep enable --config <auto|p/pack|path>`.
|
|
185
|
+
- Local `.semgrep.yml` scans do not require a Semgrep token. Semgrep AppSec/Pro/managed configurations may require `semgrep login` or `SEMGREP_APP_TOKEN`.
|
|
186
|
+
- pi-lens passes `--metrics=off` for dispatch scans.
|
|
187
|
+
|
|
188
|
+
Commands:
|
|
189
|
+
|
|
190
|
+
- `/lens-semgrep status` — show CLI availability, discovered local config, persisted pi-lens config, and effective dispatch state
|
|
191
|
+
- `/lens-semgrep init` — create a starter `.semgrep.yml` with a blocking `eval(...)` rule and enable Semgrep dispatch
|
|
192
|
+
- `/lens-semgrep enable [--config <auto|p/pack|path>]` — persist Semgrep dispatch activation in `.pi-lens/semgrep.json`
|
|
193
|
+
- `/lens-semgrep disable` — persistently disable Semgrep dispatch for this project
|
|
194
|
+
- `/lens-semgrep clear` — remove `.pi-lens/semgrep.json` and return to local-config auto-discovery
|
|
195
|
+
|
|
196
|
+
Local rules can opt into pi-lens blocking semantics with metadata:
|
|
197
|
+
|
|
198
|
+
```yaml
|
|
199
|
+
metadata:
|
|
200
|
+
pi-lens:
|
|
201
|
+
semantic: blocking
|
|
202
|
+
defect_class: injection
|
|
203
|
+
confidence: high
|
|
204
|
+
```
|
|
205
|
+
|
|
156
206
|
## Dependencies
|
|
157
207
|
|
|
158
208
|
Auto-install behavior depends on gate type:
|
|
@@ -199,6 +249,7 @@ Auto-install behavior depends on gate type:
|
|
|
199
249
|
| `vscode-html-languageserver-bin` | HTML LSP | Yes | Language-default |
|
|
200
250
|
| `svelte-language-server` | Svelte LSP | Yes | Flow-gated |
|
|
201
251
|
| `@vue/language-server` | Vue LSP | Yes | Flow-gated |
|
|
252
|
+
| `semgrep` | Experimental security dispatch | Manual | Local config / explicit opt-in |
|
|
202
253
|
| `psscriptanalyzer` | PowerShell linting | Manual | — |
|
|
203
254
|
|
|
204
255
|
Additional language servers (gopls, ruby-lsp, solargraph, etc.) are auto-detected from PATH or installed via native package managers (`go install`, `gem install`) when their language is detected.
|
|
@@ -210,6 +261,7 @@ Additional language servers (gopls, ruby-lsp, solargraph, etc.) are auto-detecte
|
|
|
210
261
|
pi
|
|
211
262
|
|
|
212
263
|
# Optional switches
|
|
264
|
+
pi --no-lens # Start pi-lens disabled for this session; /lens-toggle can re-enable
|
|
213
265
|
pi --no-lsp # Disable unified LSP diagnostics
|
|
214
266
|
pi --no-autoformat # Skip auto-formatting entirely
|
|
215
267
|
pi --immediate-format # Format immediately after each edit instead of deferring to agent_end
|
|
@@ -217,6 +269,8 @@ pi --no-autofix # Skip auto-fix (Biome, Ruff, ESLint, stylelint, sqlfl
|
|
|
217
269
|
pi --no-tests # Skip test runner
|
|
218
270
|
pi --no-delta # Disable delta mode (show all diagnostics, not just new ones)
|
|
219
271
|
pi --lens-guard # Block git commit/push when unresolved blockers exist (experimental)
|
|
272
|
+
pi --lens-semgrep # Enable Semgrep dispatch when a local/configured Semgrep config exists
|
|
273
|
+
pi --lens-semgrep-config p/ci # Explicit Semgrep config for dispatch (requires --lens-semgrep)
|
|
220
274
|
```
|
|
221
275
|
|
|
222
276
|
## Environment Variables
|
|
@@ -233,10 +287,13 @@ pi --lens-guard # Block git commit/push when unresolved blockers exist
|
|
|
233
287
|
|
|
234
288
|
## Key Commands
|
|
235
289
|
|
|
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
|
|
236
292
|
- `/lens-booboo` — full quality report for current project state
|
|
237
293
|
- `/lens-health` — runtime health, latency, and diagnostic telemetry
|
|
238
294
|
- `/lens-tools` — tool installation status: globally installed, auto-installed, or npx fallback
|
|
239
295
|
- `/lens-tdi` — Technical Debt Index (TDI) and project health trend
|
|
296
|
+
- `/lens-semgrep` — manage experimental Semgrep dispatch (`status`, `init`, `enable`, `disable`, `clear`)
|
|
240
297
|
|
|
241
298
|
## Language Coverage
|
|
242
299
|
|
|
@@ -50,7 +50,7 @@ export class RuleCache {
|
|
|
50
50
|
|
|
51
51
|
private computeRuleHash(ruleFiles: string[]): string {
|
|
52
52
|
const hash = crypto.createHash("sha256");
|
|
53
|
-
for (const file of ruleFiles.sort()) {
|
|
53
|
+
for (const file of ruleFiles.sort((a, b) => a.localeCompare(b))) {
|
|
54
54
|
if (fs.existsSync(file)) {
|
|
55
55
|
const stat = fs.statSync(file);
|
|
56
56
|
hash.update(`${file}:${stat.mtimeMs}:${stat.size}`);
|
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];
|
|
@@ -386,7 +386,7 @@ export class ComplexityClient {
|
|
|
386
386
|
let count = 0;
|
|
387
387
|
|
|
388
388
|
const aiPatterns = [
|
|
389
|
-
/
|
|
389
|
+
/(?:🔍|✅|📝|🔧|🐛|⚠️|🚀|💡|🎯|📌|🏷️|🔑|🏗️|🧪|🗑️|🔄|♻️|📋|🔖|📊|💬|🔥|💎|⭐|🌟|🎨|🛠️)/u,
|
|
390
390
|
/\/\/\s*(Initialize|Setup|Clean up|Create|Define|Check if|Handle|Process|Validate|Return|Get|Set|Add|Remove|Update|Fetch)\b/i,
|
|
391
391
|
/\/\/\s*(This function|This method|This code|Here we|Now we)\b/i,
|
|
392
392
|
/\/\*\*?\s*(Overview|Summary|Description|Example|Usage)\s*\*?\//i,
|
|
@@ -408,7 +408,7 @@ export class DependencyChecker {
|
|
|
408
408
|
let output = `[Circular Deps] ${circular.length} cycle(s) found:\n`;
|
|
409
409
|
|
|
410
410
|
for (const dep of circular) {
|
|
411
|
-
const cycleKey = dep.path.sort().join("→");
|
|
411
|
+
const cycleKey = dep.path.sort((a, b) => a.localeCompare(b)).join("→");
|
|
412
412
|
if (seen.has(cycleKey)) continue;
|
|
413
413
|
seen.add(cycleKey);
|
|
414
414
|
|
|
@@ -12,6 +12,9 @@ const SILENT_ERROR_HINTS = [
|
|
|
12
12
|
|
|
13
13
|
const INJECTION_HINTS = [
|
|
14
14
|
"sql-injection",
|
|
15
|
+
"command-injection",
|
|
16
|
+
"template-injection",
|
|
17
|
+
"xss",
|
|
15
18
|
"eval",
|
|
16
19
|
"exec",
|
|
17
20
|
"inner-html",
|
|
@@ -56,7 +59,16 @@ export function classifyDefect(
|
|
|
56
59
|
return "correctness";
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
if (
|
|
62
|
+
if (
|
|
63
|
+
text.includes("unsafe") ||
|
|
64
|
+
text.includes("security") ||
|
|
65
|
+
text.includes("ssrf") ||
|
|
66
|
+
text.includes("path-traversal") ||
|
|
67
|
+
text.includes("deserial") ||
|
|
68
|
+
text.includes("auth-bypass") ||
|
|
69
|
+
text.includes("crypto")
|
|
70
|
+
)
|
|
71
|
+
return "safety";
|
|
60
72
|
if (text.includes("style") || text.includes("format")) return "style";
|
|
61
73
|
|
|
62
74
|
return "unknown";
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import * as path from "node:path";
|
|
18
18
|
import type { FileKind } from "../file-kinds.js";
|
|
19
|
+
import { recordRunner } from "../widget-state.js";
|
|
19
20
|
import { detectFileKind } from "../file-kinds.js";
|
|
20
21
|
import { isTestFile } from "../file-utils.js";
|
|
21
22
|
import { getPrimaryDispatchGroup } from "../language-policy.js";
|
|
@@ -389,6 +390,7 @@ function buildCoverageNotice(
|
|
|
389
390
|
"similarity",
|
|
390
391
|
"spellcheck",
|
|
391
392
|
"fact-rules",
|
|
393
|
+
"semgrep",
|
|
392
394
|
]);
|
|
393
395
|
const anyLinterHasCoverage = runnerLatencies.some(
|
|
394
396
|
(r) =>
|
|
@@ -597,6 +599,13 @@ async function runGroup(
|
|
|
597
599
|
diagnosticCount: result.diagnostics.length,
|
|
598
600
|
semantic: result.semantic ?? semantic,
|
|
599
601
|
});
|
|
602
|
+
recordRunner(
|
|
603
|
+
ctx.filePath,
|
|
604
|
+
runnerId,
|
|
605
|
+
result.status,
|
|
606
|
+
result.diagnostics.length,
|
|
607
|
+
duration,
|
|
608
|
+
);
|
|
600
609
|
|
|
601
610
|
diagnostics.push(...result.diagnostics);
|
|
602
611
|
|
|
@@ -69,7 +69,7 @@ export function scheduleProviders(providers: FactProvider[]): FactProvider[] {
|
|
|
69
69
|
const cycleParticipants = providers
|
|
70
70
|
.filter((p) => !result.includes(p))
|
|
71
71
|
.map((p) => p.id)
|
|
72
|
-
.sort();
|
|
72
|
+
.sort((a, b) => a.localeCompare(b));
|
|
73
73
|
throw new Error(
|
|
74
74
|
`Cycle detected among FactProviders: ${cycleParticipants.join(", ")}`,
|
|
75
75
|
);
|