pi-lens 3.8.65 → 3.8.67
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 +59 -0
- package/README.md +0 -1
- package/dist/clients/actionable-warnings-logger.js +11 -36
- package/dist/clients/ast-grep-tool-logger.js +11 -36
- package/dist/clients/biome-client.js +10 -2
- package/dist/clients/bootstrap.js +1 -3
- package/dist/clients/cascade-logger.js +7 -12
- package/dist/clients/complexity-client.js +397 -623
- package/dist/clients/dead-code-logger.js +11 -22
- package/dist/clients/dependency-checker.js +15 -2
- package/dist/clients/deps/vscode-jsonrpc.js +1 -1
- package/dist/clients/diagnostic-logger.js +7 -31
- package/dist/clients/dispatch/auxiliary-lsp.js +38 -0
- package/dist/clients/dispatch/dispatcher.js +4 -39
- package/dist/clients/dispatch/fact-runner.js +9 -57
- package/dist/clients/dispatch/facts/comment-facts.js +12 -25
- package/dist/clients/dispatch/facts/function-facts.js +161 -132
- package/dist/clients/dispatch/facts/import-facts.js +164 -117
- package/dist/clients/dispatch/facts/tree-sitter-facts.js +34 -0
- package/dist/clients/dispatch/facts/try-catch-facts.js +62 -68
- package/dist/clients/dispatch/inline-suppressions.js +61 -0
- package/dist/clients/dispatch/integration.js +94 -32
- package/dist/clients/dispatch/plan.js +0 -49
- package/dist/clients/dispatch/priorities.js +0 -1
- package/dist/clients/dispatch/rules/cors-wildcard.js +54 -0
- package/dist/clients/dispatch/rules/high-import-coupling.js +37 -0
- package/dist/clients/dispatch/rules/no-commented-credentials.js +52 -0
- package/dist/clients/dispatch/runners/index.js +2 -7
- package/dist/clients/dispatch/runners/lsp.js +18 -6
- package/dist/clients/dispatch/runners/tree-sitter.js +11 -64
- package/dist/clients/dispatch/runners/utils/runner-helpers.js +28 -33
- package/dist/clients/dispatch/tool-profile.js +0 -1
- package/dist/clients/dispatch/types.js +1 -1
- package/dist/clients/formatters.js +10 -0
- package/dist/clients/git-guard.js +1 -1
- package/dist/clients/grammar-source.js +65 -2
- package/dist/clients/jscpd-client.js +8 -2
- package/dist/clients/language-policy.js +2 -2
- package/dist/clients/latency-logger.js +10 -15
- package/dist/clients/lsp/client.js +74 -11
- package/dist/clients/lsp/config.js +61 -3
- package/dist/clients/lsp/index.js +43 -3
- package/dist/clients/lsp/launch.js +3 -32
- package/dist/clients/mcp/session.js +0 -1
- package/dist/clients/module-report.js +16 -5
- package/dist/clients/ndjson-logger.js +150 -0
- package/dist/clients/package-manager.js +65 -0
- package/dist/clients/pipeline.js +25 -41
- package/dist/clients/project-diagnostics/extractors.js +85 -0
- package/dist/clients/project-diagnostics/runner-adapters/dead-code.js +44 -0
- package/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +25 -0
- package/dist/clients/project-diagnostics/runner-adapters/govulncheck.js +34 -0
- package/dist/clients/project-diagnostics/runner-adapters/jscpd.js +38 -0
- package/dist/clients/project-diagnostics/runner-adapters/madge.js +47 -0
- package/dist/clients/project-diagnostics/runner-adapters/trivy.js +31 -0
- package/dist/clients/project-diagnostics/scanner.js +11 -10
- package/dist/clients/read-guard-logger.js +11 -36
- package/dist/clients/review-graph/builder.js +337 -27
- package/dist/clients/review-graph/git-identity.js +150 -0
- package/dist/clients/review-graph/service.js +3 -3
- package/dist/clients/runtime-coordinator.js +83 -0
- package/dist/clients/runtime-session.js +28 -2
- package/dist/clients/runtime-tool-result.js +10 -2
- package/dist/clients/runtime-turn.js +25 -7
- package/dist/clients/sg-runner.js +15 -0
- package/dist/clients/test-runner-client.js +12 -6
- package/dist/clients/tree-sitter-cache.js +47 -14
- package/dist/clients/tree-sitter-client.js +9 -1
- package/dist/clients/tree-sitter-logger.js +7 -12
- package/dist/clients/tree-sitter-query-loader.js +1 -0
- package/dist/clients/tree-sitter-shared.js +113 -0
- package/dist/clients/tree-sitter-symbol-extractor.js +19 -12
- package/dist/index.js +4 -22
- package/dist/tools/lens-diagnostics.js +254 -20
- package/grammars/tree-sitter-yaml.wasm +0 -0
- package/grammars/tree-sitter-yaml.wasm.json +3 -3
- package/package.json +4 -3
- package/rules/ast-grep-rules/rule-tests/no-init-return-test.yml +15 -0
- package/rules/ast-grep-rules/rules/no-init-return.yml +11 -5
- package/rules/tree-sitter-queries/python/python-assert-production.yml +4 -0
- package/scripts/download-grammars.js +29 -4
- package/scripts/grammars.lock.json +14 -2
- package/scripts/install-selftest.mjs +8 -7
- package/dist/clients/deps/typescript.js +0 -15
- package/dist/clients/dispatch/rules/quality-rules.js +0 -297
- package/dist/clients/dispatch/rules/sonar-rules.js +0 -493
- package/dist/clients/dispatch/runners/biome.js +0 -69
- package/dist/clients/dispatch/runners/python-slop.js +0 -106
- package/dist/clients/dispatch/runners/ts-lsp.js +0 -109
- package/dist/clients/runner-tracker.js +0 -153
- package/dist/clients/ts-service.js +0 -130
- package/dist/clients/type-coverage-client.js +0 -128
- package/dist/clients/typescript-client.js +0 -509
- package/dist/commands/booboo.js +0 -1412
- package/rules/python-slop-rules/.sgconfig.yml +0 -4
- package/rules/python-slop-rules/rules/slop-rules.yml +0 -647
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,65 @@ All notable changes to pi-lens will be documented in this file.
|
|
|
10
10
|
|
|
11
11
|
### Fixed
|
|
12
12
|
|
|
13
|
+
## [3.8.67] - 2026-07-09
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- nightly clean-signal probe: probe-clean-signal.mjs generalized per-server (Tier 2 publishes-empty vs Tier 3 silent-on-clean among push LSPs) and wired into the tool-smoke nightly so the capability matrix's clean-behavior column self-populates (#460). The probe is phase-aware (dirty touch proves liveness; clean transitions are the discriminator) and classifies 4-way: `publishes-versioned` (tier 2 — affirmative + currency-proven, ast-grep), `publishes-unversioned` (tier 2\* — a version-less publish still early-returns the wait at runtime since the client accepts it as fresh, but currency is only temporally correlated: a staleness-risk note, not a latency cost — opengrep, yaml), `silent` (tier 3 — alive on dirty, silent on clean: the budget-wait case and #458's learned-deadline target set — typescript on a clean file), and `unknown` (no publish — conservatively unclassified). Clean fixtures (`typescript-clean`) are authoritative for a lang's row: typescript measurably re-publishes while dirty but goes silent once clean, so the dirty-fixture 2\* is overridden by the clean fixture's tier 3.
|
|
18
|
+
- nightly LSP-docs commit-back (#390): the tool-smoke nightly now also runs `server-capabilities.mjs` (regenerating `docs/servercapabilities.md` incl. the ws-pull column) and opens/updates a single auto-PR (`bot/lsp-docs-refresh`) with the regenerated `lsp-capability-matrix.md` + `servercapabilities.md` — previously these were generated in CI then discarded. All three generators now **merge** into their docs (keyed by lang/server), so a server the ubuntu host can't spawn keeps its prior dev-box row and the nightly can never regress a richer run. The phase-aware probe refined opengrep's hand-noted Tier 2 to 2\* (re-publishes on clean scans, so the wait early-returns — but version-lessly, so currency is unproven).
|
|
19
|
+
- **`serverOverrides` — per-server `initializationOptions` in project config** (#434) — `.pi-lens/lsp.json` (or `.pi-lens.json` / `pi-lsp.json`) accepts a `serverOverrides` key mapping a built-in server `id` (`"rust"`, `"nix"`, …) to an `initializationOptions` object that is deep-merged onto the server's built-in defaults at spawn time (user wins on conflicts; arrays replaced, not merged). Brings pi-lens diagnostics in line with a user's editor LSP setup (e.g. rust-analyzer `check.command: "clippy"`, nixd options expressions) without forking. Contributed by @vkarasen.
|
|
20
|
+
- `lens_diagnostics` (and MCP `pilens_diagnostics`) accept `paths` to scope any mode to an explicit file/directory list — enables wrappers like "check exactly the git-staged files" (#461).
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- perf: cascade diagnostics now run concurrently after each edit instead of blocking the write pipeline (~26% median per-edit latency reduction); settled at turn_end with a bounded wait (#450)
|
|
25
|
+
- perf: write-path micro batch — ESLint autofix runs a single `--fix` spawn (was dry-run + fix, double cold-start), LSP quick-fix lookups for blocking diagnostics run in parallel, and the lsp runner reuses its already-read file content for nosemgrep suppression (#453)
|
|
26
|
+
- perf/refactor: the eight NDJSON debug loggers (latency, cascade, read-guard, tree-sitter, dead-code, actionable-warnings, ast-grep-tool, diagnostic) now share one buffered async writer (clients/ndjson-logger.ts) — no more synchronous appendFileSync on the per-edit hot path; best-effort sync flush at process exit (#454)
|
|
27
|
+
- perf: the review-graph freshness check now uses RuntimeCoordinator sequence state to skip the per-build O(project) walk+stat sweep when only pi-observed edits occurred (seq fast path; periodic full re-verify every 20 builds/5 min catches external changes; PI_LENS_GRAPH_SEQ_FASTPATH=0 disables) (#451)
|
|
28
|
+
- perf: skip the reverse-dependency index rebuild (O(graph edges)) and its project-snapshot disk write on cascade runs where the review graph didn't actually change — the index is a pure function of the graph, so a cache-hit graph build (or a seq-fastpath build that found nothing graph-relevant to re-parse) now reuses the last-built index instead of redoing both. Freshness keys on a new `ReviewGraph.buildGeneration` stamp that travels with the returned graph instance (`mode` alone can't distinguish a true seq-fastpath no-op from one that re-parsed files, and the global build-info slot can be clobbered by overlapping deferred cascades); per-workspace cache, `PI_LENS_REVERSE_DEPS_REUSE=0` disables (#459)
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- **`no-init-return` no longer flags factory functions** (#439) — the ast-grep rule matched `return` inside any `function_definition` whose *body text* regex-contained `def __init__`, so a factory that returns a class with an `__init__` (and its sibling methods' returns) tripped it. It now matches a `function_definition` whose **name field** is `__init__` (`has: field: name`), so only real `__init__` returns are flagged. Regression fixtures added (factory + sibling-method cases).
|
|
33
|
+
- **`python-assert-production` no longer fires in test files** (#440) — `assert` is the idiomatic test assertion, so flagging every `assert` in `tests/**` was pure noise that trained users to ignore the rule. Tree-sitter rules gain an opt-in `skip_test_files` field (the runner otherwise runs on test files, since structural issues matter there); `python-assert-production` sets it, so production `assert` (the `-O` strip risk) is still flagged while test asserts are skipped. Exercised through the real runner (prod fires, `tests/` skips).
|
|
34
|
+
- **The opengrep/Semgrep runner now honors `# nosemgrep` suppression** (#441) — the canonical Semgrep inline suppression (`# nosemgrep` and `# nosemgrep: <rule-id>[,<rule-id>]`, also `//`) was ignored, leaving only `.pi-lens.json` path globs or code restructuring as escapes. The auxiliary-LSP runner now drops opengrep findings suppressed by a `nosemgrep` comment on the finding's own line (inline) or a standalone comment on the line above — matching Semgrep's placement semantics (an inline comment doesn't leak to the next line).
|
|
35
|
+
- **`lens_diagnostics mode=full` now honors inline `# pi-lens-ignore` comments** (#442) — inline suppression (`// pi-lens-ignore: rule` / `# pi-lens-ignore: rule`) was applied only in the per-edit dispatch path (`mode=all`), so a site cleanly suppressed there reappeared as **blocking** in the project-wide `mode=full` sweep — making `mode=full` unusable as a "clean" gate for any project with a legitimate suppression. The suppression filter is now shared (`clients/dispatch/inline-suppressions.ts`) and applied to the merged `mode=full` summaries too: each flagged file is read and its inline ignores honored (fail-safe — a read error never hides a finding), and counts are re-summarized so a fully-suppressed file reports clean. Rule matching also normalizes `ast-grep:<id>` / `<id>-js` forms, so a bare `pi-lens-ignore: <id>` suppresses the finding in both modes. (The reporter's secondary ask — per-rule enable/disable in `.pi-lens.json` — is tracked separately as a follow-up.)
|
|
36
|
+
- **The review-graph snapshot is now stamped with git HEAD + worktree root, and the read-substitute path drops it on mismatch** (#300) — `git worktree remove` followed by `add` at the same path for a different branch reuses the cwd-derived data-dir slug, so a `module_report`/blast-radius read fired before the first rebuild could return the previous branch's symbols/edges. The persisted snapshot now carries an optional git stamp (HEAD commit + worktree top-level path), resolved purely by reading `.git`/`HEAD`/`refs` files — no `git` subprocess, since the persist path includes the synchronous flush-on-exit handler and spawning at teardown crashes libuv on Windows (#234). The blind read path (`getCachedReviewGraph`, which trusts disk with no other verification) drops a stamped snapshot that mismatches the current repo; the build path deliberately keeps loading it unverified, because its signature/content-hash confirm (#202) already proves file-level freshness — so a plain `git commit` (HEAD moves, files unchanged) still cold-starts as a cheap "cached" reuse, never a full rebuild. An absent stamp (older snapshot, or a non-git cwd) behaves exactly as before. Separately, a cwd that isn't the git worktree top-level is now logged once per process (observability only, no hard-fail) — the review graph's cross-worktree isolation has always rested on that assumption, and it was previously invisible.
|
|
37
|
+
|
|
38
|
+
## [3.8.66] - 2026-07-07
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- **`lens_diagnostics mode=full` now surfaces the heavyweight project analyzers via an extractor registry** — previously only knip crossed from the heavyweight analyzers into the diagnostic surface; the rest reached the agent only via next-turn context injection. New `project-diagnostics/extractors.ts` registry maps each analyzer's **cached** result to per-file `ProjectDiagnostic`s through pure `runner-adapters/*` functions (mirroring `knip.ts`): **jscpd** copy-paste (a clone → a diagnostic on **both** ends, each naming the other span), **madge** circular deps (a cycle → one on **each** participating file), **gitleaks** secrets (→ **blocking**), **govulncheck** reachable Go CVEs (anchored at the first traced source frame), **trivy** dependency CVEs (anchored at the manifest), and **dead-code** (vulture/Python — unused symbols; unlisted deps → **blocking**). **Cache-only, never re-launched:** `mode=full` reads each analyzer's session-start cache and folds the results in — it never spawns a scan, so it can't relaunch or contend with the background runs (which share a global abort signal). Adding a new analyzer is now one adapter + one registry row. Included when `refreshRunners` is `cached`/`cheap`/`all`.
|
|
43
|
+
- **madge now runs whole-project at session-start and caches its result** — bringing it in line with knip/jscpd/gitleaks/govulncheck (it was the lone analyzer running per-edited-file at turn-end and discarding its output). `lens_diagnostics mode=full` reads the new `madge` cache via the extractor registry, giving whole-project circular-dependency coverage.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- **Dropped the `typescript` compiler from runtime dependencies** (#402) — it's now a **devDependency** (the `tsc` build/type-check tool), so it no longer ships to users (`npm install --omit=dev`), saving ~8.4 MB bundle / ~23 MB `node_modules`. The last runtime consumers were the dispatch fact-rules: the low-signal style smells were **dropped** (commented-out-code, duplicate-string-literal, max-switch-cases, no-magic-numbers, no-boolean-params, no-complex-conditionals), two rules were already **dead** (no-magic-numbers, high-entropy-string — never registered; secret detection is covered by the gitleaks extractor), dynamic-regexp/ReDoS was dropped in favor of the existing pattern-based ast-grep `redos-nested-quantifier` + tree-sitter `unsafe-regex` rules, and function-in-loop moves to a follow-up ast-grep rule (#428). The three survivors — **cors-wildcard**, **no-commented-credentials** (regex, multi-language), and **high-import-coupling** (fact-based) — are now individual files under `dispatch/rules/` (matching the one-rule-per-file convention) and register **eagerly** in `integration.ts` like every other rule. That unifies fact-rule registration (#421) and removes the now-obsolete lazy `ensureTypeScriptDispatchUnits` degrade indirection from `fact-runner`: with no `typescript` in the graph and `web-tree-sitter` loaded via a dynamic import, the providers are safe to register eagerly. No user-facing behavior change beyond the dropped rules.
|
|
48
|
+
- **Complexity metrics are now language-agnostic and tree-sitter-based** (#402) — `ComplexityClient` no longer uses the `typescript` compiler; it computes cyclomatic + cognitive complexity, nesting depth, function metrics, LOC/comments, code entropy, and AI-slop indicators over the shared tree-sitter client via a per-language node table. Beyond JS/TS it now also analyzes **Python, Go, and Rust** (adding a language is one table entry). Halstead volume is dropped (its maintainability-index term is replaced with the Halstead-free variant `171 − 0.23·CC − 16.2·ln(LOC)` + comment bonus); the dead `formatMetrics`/`checkThresholds` methods are removed. These are silent session-summary baselines, so the metric surface is unchanged aside from the dropped `halsteadVolume` field. `analyzeFile` is now async (grammar parse). Only the sonar/quality **rules** still import `typescript` — the last #402 Phase-2 step before the dependency is dropped.
|
|
49
|
+
- **The comment / try-catch / function fact extractors now parse via tree-sitter instead of the TypeScript compiler** (#402, Phase 2) — `commentFactProvider`, `tryCatchFactProvider`, and `functionFactProvider` are ported off `ts.createSourceFile` onto the shared tree-sitter client via a new shared `facts/tree-sitter-facts.ts` (parse boilerplate + node-walk helpers, which `import-facts` also now uses). Output is unchanged — the fact-consuming rules (`high-complexity`, `high-fan-out`, `async-noise`, `error-swallowing`, `pass-through-wrappers`, `placeholder-comments`, …) and the review graph read the same `file.comments`/`file.tryCatchSummaries`/`file.functionSummaries`, and these rules still surface as the same code-quality warnings. Parity is locked by the existing try-catch suite + new comment/function-fact tests (names, async/await, pass-through/boundary wrappers, cyclomatic complexity, nesting depth, outgoing calls). Providers are now async; the review-graph caller awaits `functionFactProvider` too. Only `complexity-client` + the sonar/quality rules still use `typescript` (#402 Phase 2 remainder).
|
|
50
|
+
- **`import-facts` now extracts imports via tree-sitter instead of the TypeScript compiler** (#402, Phase 2 pilot) — the `importFactProvider` (static/default/namespace/side-effect imports, dynamic `import()`/`require()`, named + star re-exports, esm/cjs/unknown module-type) is ported off `ts.createSourceFile` onto the shared, cached tree-sitter client (#416). Output (`file.imports`/`file.reexports`, consumed by the `high-import-coupling` rule and the review graph) is unchanged — parity is locked by the existing suite plus new cases (aliased/combined imports, `.tsx`-grammar smoke). The provider's `run()` is now async (grammar parse); the one non-awaited caller (`review-graph/builder.ts`) is fixed to await it. First of the syntactic TS-AST consumers to move off the `typescript` dependency.
|
|
51
|
+
- **Unified tree-sitter parsing on a single shared client** (refs #402) — the dispatch tree-sitter runner, project scanner, `module-report`, and review-graph each held their **own** `TreeSitterClient`, so a file written on the hot path was parsed multiple times with no shared tree cache, and each subsystem re-loaded grammars independently. They now share one process-wide client via `clients/tree-sitter-shared.ts` (`getSharedTreeSitterClient` + a single `resolveTreeSitterLanguage` ext→grammar map), so a file parsed by one subsystem is served from the shared tree cache for the others (one parse per write). This also fixes a latent gap: web-tree-sitter's WASM runtime is module-level (one per process), so an Emscripten `abort()` corrupts it for **everyone** — previously only the runner tracked the poison flag while the scanner/module-report/review-graph kept calling the dead runtime; `markTreeSitterWasmAborted()` now makes every consumer skip. Foundation for porting the syntactic TS-AST consumers (fact providers, complexity, rules) off the `typescript` dependency onto tree-sitter.
|
|
52
|
+
- **CLI tool resolution now finds binaries installed by any package manager, not just npm/PATH** (#375) — the shared `resolveLocalFirstAsync` helper (the runner fleet's "local `.bin` → global → `npx --no`" resolver) plus the per-tool resolvers for **biome** (`biome-client.ts`, `dispatch/runners/biome.ts`, `formatters.ts`), **prettier** (`formatters.ts`), **type-coverage**, **jscpd**, **madge** (`dependency-checker.ts`), **ast-grep** (`sg-runner.ts` + the shared `isSgAvailableAsync`), and the **test runners** (`test-runner-client.ts`) now check every installed manager's global bin dir (npm/pnpm/yarn/bun) by direct file lookup before falling back to `npx`. This finds tools installed via `pnpm add -g` / `bun add -g` (whose bin dirs are often off PATH) and survives PATH-cache staleness right after an `install -g`. Each site's existing `npx` fallback is unchanged — the lookup is purely additive, so no user ever gets a surprise `dlx` download. New `findGlobalBinary` / `findNodeToolBinary` helpers in `package-manager.ts`, which also de-duplicate the identical private lookup in `lsp/launch.ts`.
|
|
53
|
+
- **`lens_diagnostics mode=full` wall-clock ceiling raised from 3 min to 5 min** — large monorepos with many cold language servers were hitting the 180s cap and returning partial results before the sweep finished. The default is now 300s (still env-tunable via `PI_LENS_LENS_DIAGNOSTICS_FULL_TIMEOUT_MS`).
|
|
54
|
+
- **In-flight LSP nav requests now cancel when the turn is abandoned** (#238 Item 1) — `navRequest`/`safeSendRequest` (`clients/lsp/client.ts`) thread the ambient abort signal into a vscode-jsonrpc `CancellationToken`, so aborting a turn (Escape) sends an LSP `$/cancelRequest` and the server stops computing a definition/references/hover/etc. result the agent has already walked away from — reclaiming clangd/pyright/tsserver CPU on the hot path instead of running the request to completion and discarding it. An already-aborted signal skips the send entirely; the resulting cancellation rejection (`RequestCancelled`/`ServerCancelled`) is treated as "no result." Defaults to the ambient signal, so all ~12 nav call sites get it with no signature change.
|
|
55
|
+
|
|
56
|
+
### Removed
|
|
57
|
+
|
|
58
|
+
- **Removed the deprecated built-in TypeScript type-checker fallback** (#402, Phase 1) — the `ts-lsp` dispatch runner and its `TypeScriptClient` (`typescript-client.ts`) are deleted, along with the dead `TypeScriptService` (`ts-service.ts`, which had no consumers). TS type-checking is now **LSP-only** (tsserver via the unified `lsp` runner, which is default-on and was already the primary path — `ts-lsp` merely deferred to it). **Behaviour change:** running with `--no-lsp` no longer provides TS *type* diagnostics; the write-path linters (eslint/oxlint/biome) and structural analysis (tree-sitter, ast-grep, fact-rules) are unaffected. This removes the only `ts.createProgram`/`createLanguageService`/`TypeChecker` usage in the codebase — the first step toward dropping the heavyweight `typescript` dependency entirely (remaining usage is purely syntactic AST parsing, portable to tree-sitter).
|
|
59
|
+
- **The `/lens-booboo` command is gone** — its full-codebase review (design smells, complexity, dead code, duplicates, circular deps, secrets, vulns) is now available through the normal diagnostic surface: **`lens_diagnostics mode=full refreshRunners=all`**, which folds in the same heavyweight analyzers via the extractor registry. Also removed the dormant `TypeCoverageClient` (its only caller was `/lens-booboo`; it was never run on the normal path and is TS-only, redundant with LSP strict-mode + biome). The `--lens-guard` commit-block message and `/lens-tdi` now point to `lens_diagnostics` instead. (`FULL_LINT_PLANS`/`fullOnlyGroups` in `dispatch/plan.ts` were orphaned by this removal and are deleted in the follow-up below.)
|
|
60
|
+
- **Removed the orphaned full-project lint plan machinery** (#399, refs #398) — `FULL_LINT_PLANS` + `toFullPlan()` and the `fullOnlyGroups` field/entries in `dispatch/plan.ts` are gone now that `/lens-booboo` (their only consumer) is removed; `TOOL_PLANS` (the per-write plans) is the sole plan surface. The two full-plan-only runners this orphaned — **`biome-lint`** (`dispatch/runners/biome.ts`) and **`python-slop`** (`dispatch/runners/python-slop.ts`, `PRIORITY.PYTHON_SLOP`) — are deregistered and deleted. The `python-slop` **ast-grep rules** (`rules/python-slop-rules/`, ~45 warning-severity Python "slop" patterns) are **kept in the tree** pending a decision to migrate them into the shipped `ast-grep-rules` corpus or delete them (tracked in #400); they were never loaded by the ast-grep LSP or the `ast-grep-napi` runner.
|
|
61
|
+
|
|
62
|
+
### Fixed
|
|
63
|
+
|
|
64
|
+
- **`pi install git:…` (GitHub install) no longer fails on a clean machine** (#437) — pi builds the GitHub install by cloning master HEAD and running `npm install --omit=dev` in-place, which triggers `prepare` → `build:dist` → `tsc`. Since #402 made `typescript` a **devDependency**, `--omit=dev` omits it, so on a machine without a cached/global `tsc` the from-source build had no compiler and the install failed (`npm run build:dist … exit 1`). The npm-registry install path was unaffected (it ships prebuilt `dist/`; `prepare` never runs). `build:dist` now resolves the compiler on demand via `npx --yes -p typescript@6 tsc …` — it uses the local devDep when present (dev/publish) and fetches it transiently only for a clean `--omit=dev` source build. `typescript` stays a devDependency (nothing extra ships to registry users, no build output committed). The `prod-install-build` CI job is hardened to remove the globally-installed `typescript` first (so the from-source build can no longer pass by leaking `tsc` off the runner's PATH — which is how the regression slipped through), and now also runs a faithful `pi install git:…` simulation: a clean clone + `npm install --omit=dev` that exercises the whole `prepare` (npx build + grammar download) from a pristine tree.
|
|
65
|
+
- **Lua symbols + imports no longer silently break in multi-language repos** (#255) — the aggregator's `tree-sitter-lua.wasm` (`tree-sitter-wasms@0.1.13`) parsed lua correctly only as the **sole** grammar: the moment any second grammar loaded into web-tree-sitter's process-global WASM `Module`, every subsequent lua parse became an `ERROR` tree, so `SYMBOL_QUERIES.lua`/`IMPORT_QUERIES.lua` extracted nothing — lua symbol search, symbol-level impact, and `module_report` outlines were silently empty in essentially every real repo (this is why the #249 lua import query couldn't ship). Root cause is that specific stale wasm, not the runtime (bash/ruby/python/go/js are all fine after a 2nd grammar). Fixed with a new **per-grammar source override** (`GRAMMAR_SOURCE_OVERRIDES`): lua now downloads from the maintained **`@tree-sitter-grammars/tree-sitter-lua@0.4.1`** prebuilt wasm instead of the frozen aggregator, which parses cleanly in a multi-grammar process. The lua defs/refs/import queries are rewritten for that grammar's node types (`function_declaration`/`function_call`/`dot_index_expression`), and lua is now covered by the symbol + import smokes plus a dedicated shared-Module regression test.
|
|
66
|
+
- **The bundled YAML grammar now actually loads** (#427) — the aggregator's `tree-sitter-yaml.wasm` (`tree-sitter-wasms@0.1.13`) is ABI-incompatible with the pinned `web-tree-sitter@0.25` and fails `Language.load` outright, so YAML parsing silently returned nothing despite the grammar shipping in the bundled CORE set (dead weight in the tarball, and the lone grammar the grammar-health sweep reported as "unavailable"). Uses the same new `GRAMMAR_SOURCE_OVERRIDES` mechanism to pull the maintained **`@tree-sitter-grammars/tree-sitter-yaml@0.7.1`** prebuilt wasm, which loads + parses cleanly. Covered by a load regression test.
|
|
67
|
+
- **Swift files no longer crash `pi` on Node 24** (#423, #432) — the prebuilt `tree-sitter-swift.wasm` (from `tree-sitter-wasms@0.1.13`) triggers a **fatal, uncatchable V8 crash** (`Fatal process out of memory: Zone`, in the background Turboshaft-WASM optimizer) the first time a `.swift` file is analyzed on **Node 24, every OS** — taking down the whole agent. The crash is a process **abort**, so it can't be caught or degraded in-process, and rebuilding the grammar from source does **not** reliably dodge it (proven by the grammar-health nightly: the from-source wasm crashes on Node 24.18 identically to the prebuilt). pi-lens now **refuses to load the grammar at the point of use on the affected runtime** (`BLOCKED_GRAMMARS` / `grammarBlockReason`, gated on V8 + Node major ≥ 24): a `.swift` file simply gets no tree-sitter structural symbols (graceful degrade) instead of crashing the session. **bun (JavaScriptCore) and Node ≤ 22 are unaffected** and keep full Swift support via the normal CDN grammar download. Membership of the blocklist is **guard-driven**: the **`npm run check:grammar-load`** guard (loads each grammar in an isolated child process, skipping blocked ones — a hard gate for any *new* crasher) plus the **nightly cross-OS grammar-health workflow** watch (via a force-load probe) for when a future Node/V8 makes it safe to lift. Supersedes the earlier from-source **vendoring** approach (#426), which added a committed-wasm + provenance mechanism without actually dodging the crash — now removed.
|
|
68
|
+
- **Tree-sitter no longer leaks WASM heap memory across a session** (#417) — web-tree-sitter `Tree` objects live in the WASM heap, which JS GC does **not** reclaim (0.25 has no auto-free); the tree cache dropped evicted/invalidated/overwritten trees with `Map.delete()` and never called `tree.delete()`, so every removed tree leaked. The cache bounded entry count (50) but not the heap, so it grew unbounded over a long editing session. `TreeCache` now frees the WASM tree on every removal path — eviction, same-file re-parse (same-key overwrite), on-disk change/deletion invalidations, `invalidate()`, and `clear()` — via a guarded `freeTree()` (best-effort; tolerates a dead/aborted runtime). The retained-for-incremental path (content changed, tree kept) is deliberately not freed. Safe because every consumer uses a parsed tree transiently (parse → extract → discard) and eviction only ever targets the oldest entry, never a just-parsed tree still in use.
|
|
69
|
+
- **`lsp_diagnostics` now stops opening files in the language server once the turn is abandoned** (#343) — the batch and directory scans thread the tool-call + turn (`ctx.signal`) abort signal into their concurrency fan-out, so an Escape/abort mid-scan stops scheduling new files (each in-flight file stays bounded by `waitMs`) and returns partial results, instead of grinding the whole capped batch into the server after the agent has moved on.
|
|
70
|
+
- **LSP nav requests retry once on `ContentModified` instead of returning empty** (#238 Item 2) — when a file changes under an in-flight `definition`/`references`/`hover`/etc. request the server rejects with `ContentModified` (-32801); `safeSendRequest` now does a single safe retry against the fresh state (correctness-under-edit is the hot path), returning empty only if it still can't answer. `RequestCancelled`/`ServerCancelled` are surfaced as "no result" (no retry) and `RequestFailed` (-32803) is treated as permanent — the JSON-RPC error code is now discriminated rather than blanket-rethrown.
|
|
71
|
+
|
|
13
72
|
## [3.8.65] - 2026-07-04
|
|
14
73
|
|
|
15
74
|
### Added
|
package/README.md
CHANGED
|
@@ -57,7 +57,6 @@ pi-lens is released under the [MIT License](LICENSE).
|
|
|
57
57
|
## Contributors
|
|
58
58
|
|
|
59
59
|
Thanks goes to these wonderful people:
|
|
60
|
-
|
|
61
60
|
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
62
61
|
<!-- prettier-ignore-start -->
|
|
63
62
|
<!-- markdownlint-disable -->
|
|
@@ -1,51 +1,26 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
1
|
import * as path from "node:path";
|
|
3
2
|
import { isTestMode } from "./env-utils.js";
|
|
4
3
|
import { getGlobalPiLensDir } from "./file-utils.js";
|
|
4
|
+
import { createNdjsonLogger } from "./ndjson-logger.js";
|
|
5
5
|
const AW_LOG_DIR = getGlobalPiLensDir();
|
|
6
6
|
const AW_LOG_FILE = path.join(AW_LOG_DIR, "actionable-warnings.log");
|
|
7
7
|
const AW_LOG_BACKUP_FILE = path.join(AW_LOG_DIR, "actionable-warnings.log.1");
|
|
8
8
|
const MAX_LOG_BYTES = Math.max(128 * 1024, Number.parseInt(process.env.PI_LENS_AW_LOG_MAX_BYTES ?? "1048576", 10) || 1048576);
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
catch (err) {
|
|
15
|
-
void err;
|
|
16
|
-
}
|
|
17
|
-
function rotateIfNeeded() {
|
|
18
|
-
try {
|
|
19
|
-
if (!fs.existsSync(AW_LOG_FILE))
|
|
20
|
-
return;
|
|
21
|
-
const size = fs.statSync(AW_LOG_FILE).size;
|
|
22
|
-
if (size < MAX_LOG_BYTES)
|
|
23
|
-
return;
|
|
24
|
-
try {
|
|
25
|
-
fs.rmSync(AW_LOG_BACKUP_FILE, { force: true });
|
|
26
|
-
}
|
|
27
|
-
catch (err) {
|
|
28
|
-
void err;
|
|
29
|
-
}
|
|
30
|
-
fs.renameSync(AW_LOG_FILE, AW_LOG_BACKUP_FILE);
|
|
31
|
-
}
|
|
32
|
-
catch (err) {
|
|
33
|
-
void err;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
9
|
+
const writer = createNdjsonLogger({
|
|
10
|
+
filePath: AW_LOG_FILE,
|
|
11
|
+
maxBytes: MAX_LOG_BYTES,
|
|
12
|
+
backupPath: AW_LOG_BACKUP_FILE,
|
|
13
|
+
});
|
|
36
14
|
export function logActionableWarningsEvent(entry) {
|
|
37
15
|
if (isTestMode()) {
|
|
38
16
|
return;
|
|
39
17
|
}
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
rotateIfNeeded();
|
|
43
|
-
fs.appendFileSync(AW_LOG_FILE, line);
|
|
44
|
-
}
|
|
45
|
-
catch (err) {
|
|
46
|
-
void err;
|
|
47
|
-
}
|
|
18
|
+
writer.log({ ts: new Date().toISOString(), ...entry });
|
|
48
19
|
}
|
|
49
20
|
export function getActionableWarningsLogPath() {
|
|
50
21
|
return AW_LOG_FILE;
|
|
51
22
|
}
|
|
23
|
+
/** Resolve once all enqueued actionable-warnings writes are on disk. */
|
|
24
|
+
export function flushActionableWarningsLog() {
|
|
25
|
+
return writer.flush();
|
|
26
|
+
}
|
|
@@ -9,22 +9,19 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Mirrors `actionable-warnings-logger.ts` for shape + rotation behaviour.
|
|
11
11
|
*/
|
|
12
|
-
import * as fs from "node:fs";
|
|
13
12
|
import * as path from "node:path";
|
|
14
13
|
import { isTestMode } from "./env-utils.js";
|
|
15
14
|
import { getGlobalPiLensDir } from "./file-utils.js";
|
|
15
|
+
import { createNdjsonLogger } from "./ndjson-logger.js";
|
|
16
16
|
const AG_LOG_DIR = getGlobalPiLensDir();
|
|
17
17
|
const AG_LOG_FILE = path.join(AG_LOG_DIR, "ast-grep-tools.log");
|
|
18
18
|
const AG_LOG_BACKUP_FILE = path.join(AG_LOG_DIR, "ast-grep-tools.log.1");
|
|
19
19
|
const MAX_LOG_BYTES = Math.max(128 * 1024, Number.parseInt(process.env.PI_LENS_AST_GREP_LOG_MAX_BYTES ?? "1048576", 10) || 1048576);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
catch (err) {
|
|
26
|
-
void err;
|
|
27
|
-
}
|
|
20
|
+
const writer = createNdjsonLogger({
|
|
21
|
+
filePath: AG_LOG_FILE,
|
|
22
|
+
maxBytes: MAX_LOG_BYTES,
|
|
23
|
+
backupPath: AG_LOG_BACKUP_FILE,
|
|
24
|
+
});
|
|
28
25
|
const PATTERN_TRUNCATE_AT = 500;
|
|
29
26
|
const ERROR_TRUNCATE_AT = 300;
|
|
30
27
|
function truncate(value, max) {
|
|
@@ -105,25 +102,6 @@ export function astGrepRemediationHint(kind) {
|
|
|
105
102
|
return "Hint: verify the pattern is a single valid AST node for this `lang` (use ast_grep_dump to discover node kinds), or fall back to grep for plain-text search.";
|
|
106
103
|
}
|
|
107
104
|
}
|
|
108
|
-
function rotateIfNeeded() {
|
|
109
|
-
try {
|
|
110
|
-
if (!fs.existsSync(AG_LOG_FILE))
|
|
111
|
-
return;
|
|
112
|
-
const size = fs.statSync(AG_LOG_FILE).size;
|
|
113
|
-
if (size < MAX_LOG_BYTES)
|
|
114
|
-
return;
|
|
115
|
-
try {
|
|
116
|
-
fs.rmSync(AG_LOG_BACKUP_FILE, { force: true });
|
|
117
|
-
}
|
|
118
|
-
catch (err) {
|
|
119
|
-
void err;
|
|
120
|
-
}
|
|
121
|
-
fs.renameSync(AG_LOG_FILE, AG_LOG_BACKUP_FILE);
|
|
122
|
-
}
|
|
123
|
-
catch (err) {
|
|
124
|
-
void err;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
105
|
export function logAstGrepToolEvent(event) {
|
|
128
106
|
if (isTestMode())
|
|
129
107
|
return;
|
|
@@ -135,16 +113,13 @@ export function logAstGrepToolEvent(event) {
|
|
|
135
113
|
rewriteLineCount: event.rewriteLineCount,
|
|
136
114
|
errorRaw: truncate(event.errorRaw, ERROR_TRUNCATE_AT),
|
|
137
115
|
};
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
rotateIfNeeded();
|
|
141
|
-
fs.appendFileSync(AG_LOG_FILE, line);
|
|
142
|
-
}
|
|
143
|
-
catch (err) {
|
|
144
|
-
void err;
|
|
145
|
-
}
|
|
116
|
+
writer.log({ ts: new Date().toISOString(), ...payload });
|
|
146
117
|
}
|
|
147
118
|
export function getAstGrepToolLogPath() {
|
|
148
119
|
return AG_LOG_FILE;
|
|
149
120
|
}
|
|
121
|
+
/** Resolve once all enqueued ast-grep-tool writes are on disk. */
|
|
122
|
+
export function flushAstGrepToolLog() {
|
|
123
|
+
return writer.flush();
|
|
124
|
+
}
|
|
150
125
|
export { countLines as _countLinesForTest };
|
|
@@ -11,6 +11,7 @@ import * as fs from "node:fs";
|
|
|
11
11
|
import * as os from "node:os";
|
|
12
12
|
import * as path from "node:path";
|
|
13
13
|
import { isFileKind } from "./file-kinds.js";
|
|
14
|
+
import { findGlobalBinary } from "./package-manager.js";
|
|
14
15
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
15
16
|
// --- Client ---
|
|
16
17
|
export class BiomeClient {
|
|
@@ -41,7 +42,7 @@ export class BiomeClient {
|
|
|
41
42
|
* falls back to `process.cwd()`, which is wrong when pi is invoked from
|
|
42
43
|
* a different directory than the file being edited.
|
|
43
44
|
*/
|
|
44
|
-
getBiomeBinary(cwd) {
|
|
45
|
+
async getBiomeBinary(cwd) {
|
|
45
46
|
const resolveCwd = cwd ?? process.cwd();
|
|
46
47
|
const cached = this.localBinaryByCwd.get(resolveCwd);
|
|
47
48
|
if (cached)
|
|
@@ -74,11 +75,18 @@ export class BiomeClient {
|
|
|
74
75
|
return { cmd: p, args: [] };
|
|
75
76
|
}
|
|
76
77
|
}
|
|
78
|
+
// Any package manager's global bin dir (npm/pnpm/yarn/bun) before npx —
|
|
79
|
+
// catches `pnpm add -g @biomejs/biome` installs that PATH misses (#375).
|
|
80
|
+
const global = await findGlobalBinary("biome");
|
|
81
|
+
if (global) {
|
|
82
|
+
this.localBinaryByCwd.set(resolveCwd, global);
|
|
83
|
+
return { cmd: global, args: [] };
|
|
84
|
+
}
|
|
77
85
|
// Fallback: npx (slower but works anywhere)
|
|
78
86
|
return { cmd: "npx", args: ["@biomejs/biome"] };
|
|
79
87
|
}
|
|
80
88
|
async spawnBiomeAsync(args, timeout = 15000, cwd) {
|
|
81
|
-
const { cmd, args: prefix } = this.getBiomeBinary(cwd);
|
|
89
|
+
const { cmd, args: prefix } = await this.getBiomeBinary(cwd);
|
|
82
90
|
return safeSpawnAsync(cmd, [...prefix, ...args], { timeout });
|
|
83
91
|
}
|
|
84
92
|
/**
|
|
@@ -63,13 +63,12 @@ export function loadBootstrapClients() {
|
|
|
63
63
|
return [];
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
const [ruffClient, biomeClient, knipClient, todoScanner, jscpdClient,
|
|
66
|
+
const [ruffClient, biomeClient, knipClient, todoScanner, jscpdClient, depChecker, testRunnerClient, metricsClient, complexityClient, goClient, govulncheckClient, gitleaksClient, trivyClient, rustClient, agentBehaviorClient, deadCodeClients,] = await Promise.all([
|
|
67
67
|
load("ruff", async () => new (await import("./ruff-client.js")).RuffClient()),
|
|
68
68
|
load("biome", async () => new (await import("./biome-client.js")).BiomeClient()),
|
|
69
69
|
load("knip", async () => new (await import("./knip-client.js")).KnipClient()),
|
|
70
70
|
load("todo", async () => new (await import("./todo-scanner.js")).TodoScanner()),
|
|
71
71
|
load("jscpd", async () => new (await import("./jscpd-client.js")).JscpdClient()),
|
|
72
|
-
load("type-coverage", async () => new (await import("./type-coverage-client.js")).TypeCoverageClient()),
|
|
73
72
|
load("dependency-checker", async () => new (await import("./dependency-checker.js")).DependencyChecker()),
|
|
74
73
|
load("test-runner", async () => new (await import("./test-runner-client.js")).TestRunnerClient()),
|
|
75
74
|
load("metrics", async () => new (await import("./metrics-client.js")).MetricsClient()),
|
|
@@ -90,7 +89,6 @@ export function loadBootstrapClients() {
|
|
|
90
89
|
knipClient,
|
|
91
90
|
todoScanner,
|
|
92
91
|
jscpdClient,
|
|
93
|
-
typeCoverageClient,
|
|
94
92
|
depChecker,
|
|
95
93
|
testRunnerClient,
|
|
96
94
|
metricsClient,
|
|
@@ -1,25 +1,20 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
1
|
import * as path from "node:path";
|
|
3
2
|
import { isTestMode } from "./env-utils.js";
|
|
4
3
|
import { getGlobalPiLensDir } from "./file-utils.js";
|
|
4
|
+
import { createNdjsonLogger } from "./ndjson-logger.js";
|
|
5
5
|
const CASCADE_LOG_DIR = getGlobalPiLensDir();
|
|
6
6
|
const CASCADE_LOG_FILE = path.join(CASCADE_LOG_DIR, "cascade.log");
|
|
7
|
-
|
|
8
|
-
if (!fs.existsSync(CASCADE_LOG_DIR)) {
|
|
9
|
-
fs.mkdirSync(CASCADE_LOG_DIR, { recursive: true });
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
catch { }
|
|
7
|
+
const writer = createNdjsonLogger({ filePath: CASCADE_LOG_FILE });
|
|
13
8
|
export function logCascade(entry) {
|
|
14
9
|
if (isTestMode()) {
|
|
15
10
|
return;
|
|
16
11
|
}
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
fs.appendFileSync(CASCADE_LOG_FILE, line);
|
|
20
|
-
}
|
|
21
|
-
catch { }
|
|
12
|
+
writer.log({ ts: new Date().toISOString(), ...entry });
|
|
22
13
|
}
|
|
23
14
|
export function getCascadeLogPath() {
|
|
24
15
|
return CASCADE_LOG_FILE;
|
|
25
16
|
}
|
|
17
|
+
/** Resolve once all enqueued cascade writes are on disk (tests/shutdown). */
|
|
18
|
+
export function flushCascadeLog() {
|
|
19
|
+
return writer.flush();
|
|
20
|
+
}
|