pi-lens 3.8.32 → 3.8.34
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 +101 -1
- package/README.md +23 -2
- package/clients/cache-manager.ts +2 -1
- package/clients/cascade-format.ts +27 -0
- package/clients/cascade-logger.ts +78 -0
- package/clients/cascade-types.ts +17 -0
- package/clients/diagnostic-logger.ts +6 -0
- package/clients/dispatch/integration.ts +504 -4
- package/clients/dispatch/plan.ts +6 -2
- package/clients/dispatch/runners/detekt.ts +111 -0
- package/clients/dispatch/runners/elixir-check.ts +2 -1
- package/clients/dispatch/runners/index.ts +2 -0
- package/clients/dispatch/runners/lsp.ts +9 -25
- package/clients/dispatch/runners/similarity.ts +0 -121
- package/clients/dispatch/types.ts +5 -1
- package/clients/dispatch/utils/lsp-diagnostics.ts +48 -0
- package/clients/file-kinds.ts +194 -70
- package/clients/file-utils.ts +36 -0
- package/clients/fix-worklog.ts +3 -2
- package/clients/formatters.ts +61 -2
- package/clients/installer/index.ts +36 -16
- package/clients/language-policy.ts +11 -11
- package/clients/latency-logger.ts +6 -0
- package/clients/log-cleanup.ts +9 -7
- package/clients/lsp/client.ts +206 -40
- package/clients/lsp/config.ts +2 -0
- package/clients/lsp/index.ts +180 -40
- package/clients/lsp/interactive-install.ts +2 -1
- package/clients/lsp/launch.ts +39 -17
- package/clients/lsp/server.ts +148 -108
- package/clients/metrics-history.ts +6 -5
- package/clients/pipeline.ts +233 -219
- package/clients/project-index.ts +4 -3
- package/clients/read-expansion.ts +213 -0
- package/clients/read-guard-logger.ts +6 -0
- package/clients/read-guard-tool-lines.ts +275 -18
- package/clients/read-guard.ts +6 -6
- package/clients/review-graph/builder.ts +109 -17
- package/clients/review-graph/format.ts +6 -6
- package/clients/review-graph/query.ts +9 -0
- package/clients/review-graph/service.ts +2 -1
- package/clients/rules-scanner.ts +1 -2
- package/clients/runtime-coordinator.ts +18 -35
- package/clients/runtime-session.ts +60 -0
- package/clients/runtime-tool-result.ts +105 -35
- package/clients/runtime-turn.ts +70 -36
- package/clients/safe-spawn.ts +22 -2
- package/clients/sg-runner.ts +4 -4
- package/clients/test-runner-client.ts +1 -1
- package/clients/tool-policy.ts +1837 -1603
- package/clients/tree-sitter-client.ts +1 -0
- package/clients/tree-sitter-logger.ts +6 -0
- package/index.ts +210 -154
- package/package.json +11 -5
- package/tools/ast-grep-replace.js +7 -2
- package/tools/ast-grep-replace.ts +10 -5
- package/tools/ast-grep-search.js +7 -2
- package/tools/ast-grep-search.ts +10 -5
- package/tools/lsp-navigation.js +34 -5
- package/tools/lsp-navigation.ts +42 -5
- package/tsconfig.json +9 -5
- package/clients/architect-client.ts +0 -386
- package/clients/dispatch/runners/architect.ts +0 -129
- package/clients/native-rust-client.ts +0 -546
- package/rust/Cargo.toml +0 -34
- package/rust/src/cache.rs +0 -127
- package/rust/src/index.rs +0 -407
- package/rust/src/lib.rs +0 -209
- package/rust/src/main.rs +0 -24
- package/rust/src/scan.rs +0 -116
- package/rust/src/similarity.rs +0 -387
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,102 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to pi-lens will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [3.8.34] - 2026-05-01
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **LSP config `warmFiles` option** — added `warmFiles` to the LSP config schema. Accepts an array of relative or absolute file paths that pi-lens opens at full session startup to seed language servers that perform lazy translation-unit indexing (e.g. clangd). Without this, a short-lived `workspaceSymbol` query may return empty results for symbols in TUs clangd has not yet built an AST for, and background indexing timing is unreliable at LLVM scale. Specify entry-point files that transitively cover most of the project. The feature is general — any LSP that indexes lazily benefits.
|
|
10
|
+
- **TypeScript tsconfig split into build and lint configs** — `tsconfig.build.json` now drives `npm run build` (emits, excludes tests), while `tsconfig.json` drives `npm run lint` (no-emit, includes tests, `allowImportingTsExtensions`, `noUnusedLocals`, `noUnusedParameters`). CI lint step consolidated to `npm run lint`. Surfaced and fixed several latent type errors: unused imports removed, `error: null → undefined` alignment, `_ctx` unused-param rename, `void resolveSlowWait` for intentional float.
|
|
11
|
+
- **`GITHUB_TOOLS` const array and `GitHubToolId` type exported from installer** — the set of tools resolved via GitHub releases is now an exported `as const` array with a derived type, eliminating the duplicate definition that previously lived only in the test file.
|
|
12
|
+
- **`startupFailureWindowMs` option on `launchLSP`** — callers can now override the startup-failure detection window per-launch instead of relying solely on the Windows/non-Windows heuristic. Used by the LSP lifecycle test to avoid the full `WINDOWS_NAV_STARTUP_FAILURE_WINDOW_MS` delay in CI.
|
|
13
|
+
- **Test log pollution fix for read-guard** — `read-guard.test.ts` now mocks `read-guard-logger` unconditionally, so test events never reach `~/.pi-lens/read-guard.log` regardless of how the test suite is invoked.
|
|
14
|
+
- **Tab/space indentation mismatch correction in the edit hook** — some models output spaces in `oldText` when the file uses tabs (or vice versa), causing edits to fail with a cryptic "not found" error. The `tool_call` hook now detects this before execution by trying tabs↔2-spaces and tabs↔4-spaces conversions against the actual file. On mismatch it blocks with a `🔄 RETRYABLE` message containing the corrected `oldText` verbatim, so the model retries successfully on the next attempt at zero cost when `oldText` already matches.
|
|
15
|
+
- **Global project-data storage is now the default for new projects** — project-scoped pi-lens artifacts (turn state, worklog, metrics history, index, install choices, runner scratch data) now default to `~/.pi-lens/projects/<project-slug>/` instead of creating `<project>/.pi-lens/`. Existing projects that already have `<project>/.pi-lens/` continue to reuse it unless `PILENS_DATA_DIR` is explicitly set. This closes issue #40 while preserving backward compatibility.
|
|
16
|
+
- **`PILENS_DATA_DIR` and `PI_LENS_STARTUP_MODE` documented in README** — both env vars are now listed under a dedicated _Environment Variables_ section between `## Run` and `## Key Commands`.
|
|
17
|
+
- **Tree-sitter read expansion for the read-before-edit guard** — partial reads (requested `limit ≤ 60` lines) are now automatically expanded to cover the full enclosing function, method, or class using the tree-sitter AST. The agent receives the full symbol as context, and the read guard records symbol-level coverage so edits anywhere within the symbol pass without requiring the agent to have read every line. Supports TypeScript, TSX, JavaScript, JSX, Python, Go, Rust, and Ruby. Runs within a 200 ms budget; falls back silently on parse failure or unsupported extension. Replaces the dead LSP-based expansion (which required `limit = 1` and a warm server — zero production hits).
|
|
18
|
+
- **`read_pattern` structured log on every read** — `~/.pi-lens/read-guard.log` now records a `read_pattern` JSONL event for each read tool call: `offset`, `limit`, `totalLines`, `fractionRead`, `isPartial`, `fileKind`, and `expandedByTs`. Enables analysis of actual agent read behaviour across sessions.
|
|
19
|
+
- **`prettier.config.ts` and `eslint.config.ts` added to config detection arrays** — both config filenames are now recognised by `hasPrettierConfig` and `hasEslintConfig` respectively. Previously only `.js`/`.cjs`/`.mjs` variants were listed, so TypeScript-based configs were silently ignored.
|
|
20
|
+
- **Walk-up boundary stops at nearest `package.json`** — all 8 config-detection walk-up functions (`hasEslintConfig`, `getBiomeConfigPath`, `hasOxlintConfig`, `hasMypyConfig`, `hasDetektConfig`, `hasBlackConfig`, `hasRuffConfig`, `hasPrettierConfig`) now stop ascending once they reach the directory containing the nearest `package.json` instead of walking all the way to the filesystem root. This prevents cross-project config bleed in monorepos where an unrelated project higher up the tree happens to have a config file. A shared `walkUpDirsUntilPackageJson` helper encapsulates the boundary logic.
|
|
21
|
+
- **Formatter and linter selection logged to `latency.log`** — `getFormattersForFile` now emits a `formatter_selected` phase entry recording the chosen formatter name, selection reason (`explicit-config`, `smart-default`, `detect`, or `none`), and `cwd`. `getLinterPolicyForCwd` emits a `linter_selected` phase entry recording the chosen runner, gate, `cwd`, and the full detection-context flags. Both events are skipped in test mode.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **Config detection walks up the directory tree for all competing tools** — `hasEslintConfig`, `hasBiomeConfig` / `getBiomeConfigPath`, `hasOxlintConfig`, `hasMypyConfig`, `hasDetektConfig`, `hasBlackConfig`, and `hasRuffConfig` now all walk up to the filesystem root (matching the `findNearestPackageJsonPath` pattern) instead of only checking `cwd`. In monorepos where pi-lens passes a subdirectory as `cwd`, configs at the project root are now found correctly. Prevents wrong smart-default selection (e.g. oxlint firing instead of eslint, ruff firing instead of black) and restores optional runners (mypy, detekt) that were silently dropped when their configs lived above `cwd`. Functions with no competing smart-default (stylelint, sqlfluff, rubocop, golangci-lint, etc.) are unchanged.
|
|
26
|
+
- **Biome smart-default no longer overrides explicit Prettier config** — `getFormattersForFile` now only activates the Biome smart-default when no candidate formatter has explicit project config. Previously, a project with `.prettierrc` but no `biome.json` would still have Biome auto-installed and selected. `hasPrettierConfig` also now walks up the directory tree (matching the `findUp` pattern used elsewhere) so a Prettier config in a parent directory is detected even when pi-lens passes a subdirectory as `cwd`. The inline `package.json#prettier` field check uses `Object.prototype.hasOwnProperty` instead of truthiness, correctly handling `"prettier": false` and `"prettier": null`.
|
|
27
|
+
- **Duplicate `oldText` in edit calls now blocked early** — the read guard pre-flight check (`resolveOldTextEdits`) returns a `🔴 BLOCKED` error before the edit tool executes when `oldText` matches more than one location in the file, with per-match line numbers so the model can tighten its context.
|
|
28
|
+
- **Read-guard `oldText` inference hardened** — unresolved `oldText` targets no longer degrade into permissive `no_line_info` allows. Missing matches now return a blocking preflight error, partial multi-edit resolution blocks the whole edit, and indentation-correctable `oldText` is recognized during touched-line derivation as well as in the retryable pipeline guard.
|
|
29
|
+
- **Cascade diagnostics unified through review graph + LSP touch flow** — cascade results now accumulate as structured `CascadeResult` values across the turn, merge/deduplicate by dependent file at turn end, use review-graph references for broader neighbor discovery, respect TypeScript/Deno auto-propagation capabilities, and fall back to passive LSP snapshots when no trustworthy neighbor LSP data is produced.
|
|
30
|
+
- **Cascade LSP diagnostics now use shared conversion/tracking** — cascade diagnostics are converted through the shared LSP→dispatch diagnostic utility, participate in `DiagnosticTracker`, use separate cascade delta baselines (`session.baseline.cascade.*`), and share centralized cascade formatting.
|
|
31
|
+
- **`touchFile({ collectDiagnostics: true })`** — LSP touch can now return merged diagnostics from the clients it opened/synced, allowing cascade to collect diagnostics from the same silently touched clients without a second aggregate `getDiagnostics()` call.
|
|
32
|
+
- **Review graph workspace cache** — cascade graph builds now reuse the parsed review graph across pipeline invocations when source file mtimes/sizes are unchanged, while still applying per-write changed-symbol state. Cascade logs now record whether the graph was reused and the build mode.
|
|
33
|
+
- **`PILENS_DATA_DIR` env var for external project data storage** — when set, all project-generated data (caches, index, worklog, LSP install choices, elixir outputs, metrics history) is written to `$PILENS_DATA_DIR/<project-slug>/`. Slug is derived from the project's absolute path using the existing cross-platform `normalizeFilePath` utility.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- **Cascade silent LSP opens no longer broadcast file-watch changes** — cascade neighbor reads now open documents with `silent: true`, suppressing `workspace/didChangeWatchedFiles` so TypeScript/Python servers do not schedule project-wide rechecks for every dependent file touched.
|
|
38
|
+
- **Cascade cache/fallback correctness** — per-turn cascade caches are scoped by turn/write sequence, empty cascade results are suppressed, no-LSP neighbors are treated as no signal, and degraded fallback now triggers when no neighbor produced LSP data rather than only when the graph returned zero neighbors.
|
|
39
|
+
- **LSP touch `no_clients` latency diagnostics** — `lsp_touch_file` no-client records now include attempted server count, source, and wait budget so slow no-client outcomes can be distinguished from unsupported-file fast paths.
|
|
40
|
+
- **Misleading LSP error when `filePath` is a directory** — `lsp_navigation` now stat-checks the resolved path before server lookup. Passing a directory (e.g. `.`) to `workspaceDiagnostics` falls through to workspace-scoped mode; file-scoped operations return a clear `filepath_is_directory` error instead of the previous "No LSP server available … Check that the language server is installed" message, which incorrectly implied an install problem.
|
|
41
|
+
- **LSP `didChangeWatchedFiles` sends correct change type** — `handleNotifyOpen` now uses `type: 2` (Changed) for existing files instead of unconditionally sending `type: 1` (Created). File-watching LSPs no longer treat every open as a newly created file, which could invalidate caches differently than intended.
|
|
42
|
+
- **`getAllDiagnostics()` deduplicates across multiple LSP clients** — when TypeScript + ESLint both report an error on the same line, the fallback/snapshot path now merges and deduplicates instead of showing both. Prevents duplicates from pushing out unique diagnostics under the `MAX_PER_FILE` cap.
|
|
43
|
+
- **`formatImpactCascade` respects configurable `cascadeMaxFiles`** — removed hardcoded `MAX_FILES = 4` in `format.ts`; the display cap now matches `RUNTIME_CONFIG.pipeline.cascadeMaxFiles` (default 8), so the impact header and truncation hint are consistent with actual analysis.
|
|
44
|
+
- **Turn-end cascade merge preserves impact context** — previously `runtime-turn.ts` rebuilt output from raw `neighbors`, discarding impact headers, changed symbols, risk flags, and truncation hints. It now uses the pre-built `CascadeResult.formatted` field (deduplicated by primary file), so the agent sees causal context ("Changed symbols: X", "Direct importers: Y", "Risk: Z") alongside diagnostics.
|
|
45
|
+
- **Neighbor touch cache is turn-scoped** — `neighborTouchCache` previously invalidated on every `writeIndex` bump, so reading a file then editing it would re-touch the same neighbor. The cache now keys on `turnSeq` only, so neighbors are touched once per turn regardless of how many files are edited.
|
|
46
|
+
- **Dead opportunistic LSP read expansion removed** — the `findSymbolAtLine` / `withTimeout` / `LSP_READ_EXPANSION_BUDGET_MS` code path was never triggered in production (zero `lsp_range_expanded` events outside tests) and added complexity/latency to every read tool call. Removed entirely. Read guard records now use `peekWriteIndex()` instead of `nextWriteIndex()`, fixing the cascade cache invalidation bug where reads incremented the write counter.
|
|
47
|
+
- **Test-mode guards for all loggers** — every logger that writes to `~/.pi-lens/` now skips disk I/O when `PI_LENS_TEST_MODE === "1"` or when running under `VITEST` (unless explicitly opted out with `PI_LENS_TEST_MODE=0`). Eliminates test pollution in `cascade.log`, `read-guard.log`, `latency.log`, `sessionstart.log`, `tree-sitter.log`, and diagnostic JSONL. The `dbg()` function already had this guard; it is now applied consistently across `logCascade`, `logReadGuardEvent`, `logLatency`, `logTreeSitter`, `logSessionStart`, and `DiagnosticLogger.log`.
|
|
48
|
+
- **`read-guard.log` included in automatic cleanup** — `runLogCleanup()` now covers `read-guard.log` alongside the existing `sessionstart.log`, `tree-sitter.log`, and `cascade.log`.
|
|
49
|
+
|
|
50
|
+
- **oxfmt `.oxfmtrc.json` detection** — `hasOxfmtConfig` now treats `.oxfmtrc.json` as an activation signal alongside `oxfmt.toml` and `@oxc-project/oxfmt` in package.json.
|
|
51
|
+
|
|
52
|
+
## [3.8.33] - 2026-04-27
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- **JSON/JSONC autofix skipped without biome config** — `getAutofixPolicyForFile` now returns `undefined` for `.json`/`.jsonc` files when no `biome.json`/`biome.jsonc` is present, matching the format policy's `defaultWhenUnconfigured: false` gate. Previously biome was always invoked for JSON edits (~688ms) even when it had no config and fixed nothing. `hasBiomeConfig` added to `AutofixPolicyContext` and wired into the autofix context in `runAutofix`.
|
|
57
|
+
|
|
58
|
+
### Added
|
|
59
|
+
|
|
60
|
+
- **Early-unblock diagnostic aggregation** — `getDiagnostics()` now races `Promise.all` against a first-client-done + grace window (`PI_LENS_LSP_EARLY_UNBLOCK_GRACE_MS`, default 400ms). Once the fastest client delivers results, remaining clients have the grace window before the call returns with whatever is ready. Eliminates the previous worst case where a slow push-only server forced the full 1500ms aggregate wait even when a faster server already had errors. `earlyUnblockedCount` is logged in `lsp_diagnostics_aggregate` latency records.
|
|
61
|
+
- **Dynamic LSP capability registration tracking** — `client/registerCapability` and `client/unregisterCapability` handlers now record live registrations (`id → method`) in `dynamicRegistrations`. `applyDynamicCapabilities()` upgrades `workspaceDiagnosticsSupport` to pull mode when `textDocument/diagnostic` or `workspace/diagnostic` is dynamically registered, and reverts when the last such registration is removed (unless statically advertised). Operation support flags are also upgraded for dynamically-registered nav methods. Servers that defer capability advertisement past `initialize` are now treated correctly.
|
|
62
|
+
- **Deno/TypeScript server disambiguation** — `TypeScriptServer.root` now returns `undefined` for any file with a `deno.json` or `deno.jsonc` ancestor, preventing TypeScript LSP from being spawned alongside Deno LSP for the same file. Eliminates false diagnostics for Deno-specific APIs and removes the wasted parallel spawn.
|
|
63
|
+
- **`CONDA_PREFIX` support in Python venv detection** — conda environments do not set `VIRTUAL_ENV`; venv detection now checks `CONDA_PREFIX` as a fallback between `VIRTUAL_ENV` and the local `.venv`/`venv` directories.
|
|
64
|
+
- **pylsp venv initialization** — `PythonPylspServer.spawn` now passes `{ pylsp: { plugins: { jedi: { environment: pythonPath } } } }` when a virtual environment is detected. Previously pylsp always used the system Python, so completions and diagnostics resolved against the wrong package set in virtualenv projects.
|
|
65
|
+
|
|
66
|
+
### Changed
|
|
67
|
+
|
|
68
|
+
- **Push/pull LSP diagnostic caches split** — `LSPClientState` now maintains separate `pushDiagnostics` and `documentPullDiagnostics` maps with independent timestamps. Public API (`getDiagnostics`, `getAllDiagnostics`, `pruneDiagnostics`) operates on a merged, deduplicated view. Clears and prunes invalidate both sources independently. Makes diagnostic freshness and source attribution inspectable without changing caller behavior.
|
|
69
|
+
- **Explicit LSP touch diagnostics modes** — `touchFile()` now takes `{ diagnostics: "none" | "document" | "full", clientScope: "primary" | "all", source, maxClientWaitMs }` instead of a boolean `waitForDiagnostics` flag. Read/tool-call warming uses `"none"`; write validation uses `"document"`. Latency records include `diagnosticsMode`, `clientScope`, and `source`.
|
|
70
|
+
- **Pipeline reordered around final content** — format → refresh → autofix → refresh → LSP sync once with final content → dispatch. LSP diagnostics and dispatch runners now always operate on the final post-format/post-fix on-disk state. Removed previously-dead `supportsAutofix` / deferred sync logic.
|
|
71
|
+
- **Python venv detection deduplicated** — `PythonServer.spawn` previously ran identical 20-line venv detection blocks in both the direct and managed code paths. Both now call the shared `detectPythonVenv(root)` helper.
|
|
72
|
+
|
|
73
|
+
### Fixed
|
|
74
|
+
|
|
75
|
+
- **Formatter failures now visible in output** — formatter crashes (missing binary, timeout, I/O error) now append `⚠️ Auto-format failed: <reason>` to pipeline output instead of silently writing to debug logs. Prevents misleading all-clear output when a required format phase failed.
|
|
76
|
+
- **Same-file same-turn pipeline dedupe keyed on content hash** — previously any later pipeline for a file already reported in the same turn was skipped by file path alone, suppressing legitimate second edits. Dedupe is now keyed on post-write content hash: concurrent duplicate events for the same final content are collapsed, but a later edit with changed content runs the full pipeline again.
|
|
77
|
+
- **Autofix side-effect files tracked in turn state** — `runAutofix()` now returns `changedFiles[]`. File-scoped fixers (ruff, biome, eslint, stylelint, sqlfluff, rubocop, ktlint) record the target file on a successful fix; project-wide fixers (cargo clippy --fix, dart fix --apply) snapshot the project tree before and after to detect side-effect changes. Non-target changed files are added to turn state via `cacheManager.addModifiedRange()` so cascade and read-guard see the full mutation set.
|
|
78
|
+
|
|
79
|
+
### Changed
|
|
80
|
+
|
|
81
|
+
- **Linter dispatch runners promoted to always-on for 11 languages** — runners that previously fired only when LSP failed (`mode: "fallback"`) now run alongside LSP unconditionally (`mode: "all"`): `pyright` (Python), `rust-clippy` (Rust), `go-vet` (Go), `shellcheck` (Shell), `tflint` (Terraform), `elixir-check` + `credo` (Elixir), `cpp-check` (C/C++), `dart-analyze` (Dart), `gleam-check` (Gleam), `psscriptanalyzer` (PowerShell), `prisma-validate` (Prisma). These tools provide orthogonal signal to the LSP that was previously invisible on healthy sessions.
|
|
82
|
+
|
|
83
|
+
### Added
|
|
84
|
+
|
|
85
|
+
- **Linter policy entries for 9 languages** — `getLinterPolicyForFile` now covers Rust (rust-clippy, smart-default), Shell (shellcheck, smart-default), Terraform (tflint, smart-default), Elixir (credo, smart-default), C/C++ (cpp-check, smart-default), Dart (dart-analyze, smart-default), Gleam (gleam-check, smart-default), PowerShell (psscriptanalyzer, smart-default), and Prisma (prisma-validate, smart-default). These linters now participate in the full policy layer rather than being dispatch-only.
|
|
86
|
+
- **`cargo clippy --fix` autofix for Rust** — `rust-clippy` is now a safe pipeline autofix tool for `.rs` files. After each edit, `cargo clippy --fix --allow-dirty --allow-staged` runs in the nearest `Cargo.toml` directory before dispatch lint, applying machine-fixable clippy suggestions. Gated `smart-default`; skips silently if `cargo` is unavailable or no `Cargo.toml` is found.
|
|
87
|
+
- **`dart fix --apply` autofix for Dart** — `dart-analyze` is now a safe pipeline autofix tool for `.dart` files. After each edit, `dart fix --apply` runs in the nearest `pubspec.yaml` directory before dispatch lint. Gated `smart-default`; skips silently if `dart` is unavailable or no `pubspec.yaml` is found.
|
|
88
|
+
|
|
89
|
+
### Fixed
|
|
90
|
+
|
|
91
|
+
- **Unknown/support files no longer trigger opportunistic LSP auto-touch** — `tool_call` LSP warming now defaults unknown file kinds to non-LSP-capable and explicitly skips internal/support artifacts such as `.pi-lens/*`, `.harness/*`, `stdout.jsonl`, `stderr.txt`, `prompt.txt`, and harness `case.json` files. This removes pointless `lsp_touch_file` `no_clients` waits on logs, prompts, and turn-state sidecars.
|
|
92
|
+
- **Spawn-heavy LSP capability checks removed from hot paths** — added a pure `supportsLSP(filePath)` check and a lightweight `hasWarmLSP(filePath)` helper so hot write/read paths no longer use `hasLSP()` merely to ask whether a file type is supported. `pipeline` sync/resync, the unified LSP runner, and `lsp_navigation` unsupported-file messaging now avoid accidental client spawns during simple capability checks.
|
|
93
|
+
- **`ktlint` autofix case missing `continue`** — the `ktlint` branch in `runAutofix` lacked a `continue` guard, causing fall-through into the next tool match on every ktlint run.
|
|
94
|
+
|
|
95
|
+
## [Unreleased — mypy + detekt]
|
|
96
|
+
|
|
97
|
+
### Added
|
|
98
|
+
|
|
99
|
+
- **`mypy` wired into Python dispatch** — runner already existed but was never included in the dispatch plan or linter policy. Added to Python `writeGroups` in `plan.ts` and to `getLinterPolicyForFile` for `.py`/`.pyi`. When `mypy.ini` or `[tool.mypy]` is present, mypy is appended to `preferredRunners` alongside ruff-lint (gate: `mixed`); unconfigured projects are unaffected.
|
|
100
|
+
- **`detekt` runner for Kotlin** — new runner (`detekt.ts`) that runs `detekt --input <file> --config <config>` for static analysis of `.kt`/`.kts` files. Config-first: activates only when `detekt.yml`, `.detekt.yml`, `config/detekt/detekt.yml`, or `detekt/detekt.yml` is found. Added `hasDetektConfig` helper, `"detekt"` to `LintRunnerName`, `hasDetektConfig` to `LinterPolicyContext`, and detekt to Kotlin's linter policy (appended to `preferredRunners` alongside ktlint when configured). Kotlin `plan.ts` `writeGroups` updated to include detekt.
|
|
6
101
|
|
|
7
102
|
## [3.8.32] - 2026-04-26
|
|
8
103
|
|
|
@@ -971,6 +1066,7 @@ All runtime-applicable TypeScript ast-grep rules now have JavaScript equivalents
|
|
|
971
1066
|
- **Rust performance core (`pi-lens-core`)** — Optional Rust binary for CPU-intensive operations.
|
|
972
1067
|
All features fall back to TypeScript automatically if the binary is not available (it is **not**
|
|
973
1068
|
built automatically on `npm install` — run `npm run rust:build` once if you have Rust installed).
|
|
1069
|
+
|
|
974
1070
|
- **File scanning** — ripgrep’s `ignore` crate for `.gitignore`-aware project scanning
|
|
975
1071
|
- **Similarity detection** — parallel 57×72 state-matrix index, persisted to
|
|
976
1072
|
`.pi-lens/rust-index.json` between invocations (fixes in-memory cache that reset on every
|
|
@@ -1024,6 +1120,7 @@ All runtime-applicable TypeScript ast-grep rules now have JavaScript equivalents
|
|
|
1024
1120
|
- Removed `clients/interviewer-templates.ts` (240 lines)
|
|
1025
1121
|
- Removed initialization from `index.ts`
|
|
1026
1122
|
- **Deleted deprecated commands** — All were superseded by `/lens-booboo`:
|
|
1123
|
+
|
|
1027
1124
|
- `/lens-booboo-fix` command (fix-from-booboo.ts, 430 lines) — showed warning to use `/lens-booboo`
|
|
1028
1125
|
- `/lens-fix-simplified` command (fix-simplified.ts, 770 lines) — never registered, unused
|
|
1029
1126
|
- `/lens-rate` command (rate.ts, 340 lines) — showed warning to use `/lens-booboo`
|
|
@@ -1042,6 +1139,7 @@ All runtime-applicable TypeScript ast-grep rules now have JavaScript equivalents
|
|
|
1042
1139
|
- Broken runner tests (7 files) — thin CLI wrappers with wrong imports
|
|
1043
1140
|
- Trivial utility tests (5 files) — file extension parsing, string sanitization
|
|
1044
1141
|
- **Added meaningful integration tests**:
|
|
1142
|
+
|
|
1045
1143
|
- `tests/clients/dispatch/dispatcher-flow.test.ts` — Runner registration, execution, delta mode, conditional runners
|
|
1046
1144
|
- `tests/extension-hooks.test.ts` — pi API: tool/command/flag registration, event handlers
|
|
1047
1145
|
- `tests/mocks/runner-factory.ts` — Mock runners for testing without real CLI tools
|
|
@@ -1377,6 +1475,7 @@ Migrated 20 critical security rules to NAPI (fast native execution):
|
|
|
1377
1475
|
Three new lint runners with full test coverage:
|
|
1378
1476
|
|
|
1379
1477
|
- **Spellcheck runner** (`clients/dispatch/runners/spellcheck.ts`): Markdown spellchecking
|
|
1478
|
+
|
|
1380
1479
|
- Uses `typos-cli` (Rust-based, fast, low false positives)
|
|
1381
1480
|
- Checks `.md` and `.mdx` files
|
|
1382
1481
|
- Priority 30, runs after code quality checks
|
|
@@ -1384,6 +1483,7 @@ Three new lint runners with full test coverage:
|
|
|
1384
1483
|
- Install: `cargo install typos-cli`
|
|
1385
1484
|
|
|
1386
1485
|
- **Oxlint runner** (`clients/dispatch/runners/oxlint.ts`): Fast JS/TS linting
|
|
1486
|
+
|
|
1387
1487
|
- Uses `oxlint` from Oxc project (Rust-based, ~100x faster than ESLint)
|
|
1388
1488
|
- Zero-config by default
|
|
1389
1489
|
- JSON output with fix suggestions
|
package/README.md
CHANGED
|
@@ -30,6 +30,7 @@ At `session_start`, pi-lens:
|
|
|
30
30
|
- warms caches and optional indexes (with overlap/session guardrails)
|
|
31
31
|
- emits missing-tool install hints for detected languages when relevant
|
|
32
32
|
- injects session guidance through internal context (non-user channel) to reduce acknowledgement-only first responses
|
|
33
|
+
- opens `warmFiles` (if configured in `.pi-lens/lsp.json`) to seed lazy-indexing language servers like clangd before the first symbol query
|
|
33
34
|
|
|
34
35
|
For one-shot print sessions (for example `pi --print ...`), pi-lens auto-uses a quick startup path that skips heavy bootstrap work to reduce startup latency. Override with `PI_LENS_STARTUP_MODE=full|minimal|quick`.
|
|
35
36
|
|
|
@@ -64,6 +65,12 @@ pi-lens includes **37 language server definitions**. LSP is **enabled by default
|
|
|
64
65
|
|
|
65
66
|
**LSP Idle Management:** LSP servers shut down after 240 seconds of inactivity (no files modified) to free resources. The timer resets when you resume editing, preventing cold-start penalties during active development.
|
|
66
67
|
|
|
68
|
+
**Warm files:** For language servers that index lazily (e.g. clangd), configure `warmFiles` in `.pi-lens/lsp.json` to open entry-point files at session start so the server has AST/index context before the first symbol query:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{ "warmFiles": ["src/main.cpp", "src/lib.cpp"] }
|
|
72
|
+
```
|
|
73
|
+
|
|
67
74
|
LSP servers for: TypeScript, Deno, Python (pyright + pylsp), Go, Rust, Ruby (ruby-lsp + solargraph), PHP, C# (omnisharp), F#, Java, Kotlin, Swift, Dart, Lua, C/C++, Zig, Haskell, Elixir, Gleam, OCaml, Clojure, Terraform, Nix, Bash, Docker, YAML, JSON, HTML, TOML, Prisma, Vue, Svelte, ESLint, CSS.
|
|
68
75
|
|
|
69
76
|
### Formatters
|
|
@@ -90,7 +97,7 @@ pi-lens enforces a **read-before-edit** policy on all file writes and edits. Bef
|
|
|
90
97
|
- **File-modified block** — blocks if the file changed on disk since the last read (auto-format, external tool, or a previous edit that was then reformatted)
|
|
91
98
|
- **Out-of-range block** — blocks if the edit target lines fall outside the ranges previously read, ensuring the agent cannot modify code it hasn't seen
|
|
92
99
|
|
|
93
|
-
Coverage is tracked across multiple reads: two reads of lines 1–100 and 101–200 together satisfy a full-file write.
|
|
100
|
+
Coverage is tracked across multiple reads: two reads of lines 1–100 and 101–200 together satisfy a full-file write. Symbol-expanded reads (small reads silently widened to the enclosing symbol via tree-sitter) count toward coverage at the symbol level. Markdown, text, and log files are exempt.
|
|
94
101
|
|
|
95
102
|
Override for a single edit: `/lens-allow-edit <path>`
|
|
96
103
|
|
|
@@ -98,7 +105,9 @@ Configure behavior with `--no-read-guard` to disable entirely, or set mode to `w
|
|
|
98
105
|
|
|
99
106
|
### Opportunistic Read Expansion
|
|
100
107
|
|
|
101
|
-
When the agent reads a
|
|
108
|
+
When the agent reads a small slice of a file (≤ 60 lines), pi-lens transparently expands the read to the full enclosing symbol (function, method, or class) using the tree-sitter AST. The agent receives the full symbol as context, and the read guard records symbol-level coverage so edits anywhere within that symbol pass without requiring the agent to have read every line individually. Expansion runs within a 200 ms budget and falls back silently on unsupported file types or parse failures.
|
|
109
|
+
|
|
110
|
+
Supported: TypeScript, TSX, JavaScript, JSX, Python, Go, Rust, Ruby.
|
|
102
111
|
|
|
103
112
|
### Fact Rules Pipeline
|
|
104
113
|
|
|
@@ -196,6 +205,18 @@ pi --no-delta # Disable delta mode (show all diagnostics, not just n
|
|
|
196
205
|
pi --lens-guard # Block git commit/push when unresolved blockers exist (experimental)
|
|
197
206
|
```
|
|
198
207
|
|
|
208
|
+
## Environment Variables
|
|
209
|
+
|
|
210
|
+
- `PILENS_DATA_DIR` — redirect per-project state (scanner caches,
|
|
211
|
+
turn-state.json) out of the project directory. By default pi-lens writes
|
|
212
|
+
`<cwd>/.pi-lens/`; if set, it writes to
|
|
213
|
+
`<PILENS_DATA_DIR>/<sanitized-cwd-slug>/` instead. Useful for keeping repos
|
|
214
|
+
clean or for mounted/ephemeral setups. Tool binaries always live in
|
|
215
|
+
`~/.pi-lens/bin/` regardless.
|
|
216
|
+
- `PI_LENS_STARTUP_MODE` — `full` | `minimal` | `quick`. Override the
|
|
217
|
+
auto-selected startup path. One-shot `pi --print` sessions auto-use `quick`
|
|
218
|
+
to reduce latency.
|
|
219
|
+
|
|
199
220
|
## Key Commands
|
|
200
221
|
|
|
201
222
|
- `/lens-booboo` — full quality report for current project state
|
package/clients/cache-manager.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import * as fs from "node:fs";
|
|
13
13
|
import * as path from "node:path";
|
|
14
|
+
import { getProjectDataDir } from "./file-utils.js";
|
|
14
15
|
import { normalizeMapKey } from "./path-utils.js";
|
|
15
16
|
|
|
16
17
|
// --- Types ---
|
|
@@ -57,7 +58,7 @@ const DEFAULT_TURN_STATE: TurnState = {
|
|
|
57
58
|
// --- Helpers ---
|
|
58
59
|
|
|
59
60
|
function getLensDir(cwd: string): string {
|
|
60
|
-
return
|
|
61
|
+
return getProjectDataDir(cwd);
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
function getCacheDir(cwd: string): string {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CascadeNeighborResult } from "./cascade-types.js";
|
|
2
|
+
import { toRunnerDisplayPath } from "./dispatch/runner-context.js";
|
|
3
|
+
|
|
4
|
+
export function formatCascadeNeighborDiagnostics(
|
|
5
|
+
cwd: string,
|
|
6
|
+
neighbors: CascadeNeighborResult[],
|
|
7
|
+
options: { noun?: string; includeReason?: boolean } = {},
|
|
8
|
+
): string {
|
|
9
|
+
const withErrors = neighbors.filter((n) => n.diagnostics.length > 0);
|
|
10
|
+
if (withErrors.length === 0) return "";
|
|
11
|
+
|
|
12
|
+
const noun = options.noun ?? "neighbor";
|
|
13
|
+
let out = `📐 Cascade errors in ${withErrors.length} ${noun} file(s) — fix before finishing turn:`;
|
|
14
|
+
for (const neighbor of withErrors) {
|
|
15
|
+
const display = toRunnerDisplayPath(cwd, neighbor.filePath);
|
|
16
|
+
const reason = options.includeReason ? ` reason="${neighbor.reason}"` : "";
|
|
17
|
+
out += `\n<diagnostics file="${display}"${reason}>`;
|
|
18
|
+
for (const d of neighbor.diagnostics) {
|
|
19
|
+
const line = d.line ?? 1;
|
|
20
|
+
const col = d.column ?? 1;
|
|
21
|
+
const rule = d.rule ? ` rule=${d.rule}` : "";
|
|
22
|
+
out += `\n line ${line}, col ${col}${rule}: ${d.message.split("\n")[0].slice(0, 100)}`;
|
|
23
|
+
}
|
|
24
|
+
out += "\n</diagnostics>";
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
|
|
5
|
+
const CASCADE_LOG_DIR = path.join(os.homedir(), ".pi-lens");
|
|
6
|
+
const CASCADE_LOG_FILE = path.join(CASCADE_LOG_DIR, "cascade.log");
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
if (!fs.existsSync(CASCADE_LOG_DIR)) {
|
|
10
|
+
fs.mkdirSync(CASCADE_LOG_DIR, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
} catch {}
|
|
13
|
+
|
|
14
|
+
export interface CascadeLogEntry {
|
|
15
|
+
ts?: string;
|
|
16
|
+
phase:
|
|
17
|
+
| "cascade_skip" // primary has blockers — cascade suppressed
|
|
18
|
+
| "graph_build" // graph built or reused
|
|
19
|
+
| "neighbors_computed" // impact cascade result ready
|
|
20
|
+
| "neighbor_touch" // single neighbor LSP active touch result
|
|
21
|
+
| "neighbor_snapshot" // neighbor read from passive snapshot (autoPropagate jsts)
|
|
22
|
+
| "neighbor_fallback" // neighbor fell back to getAllDiagnostics (error or degraded)
|
|
23
|
+
| "cascade_result" // final per-file cascade result
|
|
24
|
+
| "cascade_turn_end"; // merged result emitted at turn_end
|
|
25
|
+
filePath: string;
|
|
26
|
+
neighborFile?: string;
|
|
27
|
+
reason?: string;
|
|
28
|
+
|
|
29
|
+
// graph_build
|
|
30
|
+
graphBuiltMs?: number;
|
|
31
|
+
graphReused?: boolean; // true when FactStore cache was valid (future: incremental rebuild)
|
|
32
|
+
graphNodeCount?: number;
|
|
33
|
+
graphFileCount?: number;
|
|
34
|
+
graphChangedSymbolCount?: number;
|
|
35
|
+
|
|
36
|
+
// neighbors_computed
|
|
37
|
+
neighborCount?: number;
|
|
38
|
+
totalNeighborCount?: number; // before cap
|
|
39
|
+
importerCount?: number;
|
|
40
|
+
callerCount?: number;
|
|
41
|
+
referenceCount?: number;
|
|
42
|
+
riskFlags?: string[];
|
|
43
|
+
|
|
44
|
+
// neighbor_snapshot
|
|
45
|
+
snapshotMissing?: boolean; // true when file not found in allDiags
|
|
46
|
+
snapshotAgeSec?: number; // age of snapshot entry in seconds
|
|
47
|
+
|
|
48
|
+
// neighbor_touch
|
|
49
|
+
lspServerCount?: number; // number of LSP servers configured for this file type
|
|
50
|
+
touchedCount?: number;
|
|
51
|
+
snapshotCount?: number;
|
|
52
|
+
|
|
53
|
+
// shared
|
|
54
|
+
fallbackUsed?: boolean;
|
|
55
|
+
diagnosticCount?: number;
|
|
56
|
+
durationMs?: number;
|
|
57
|
+
autoPropagate?: boolean;
|
|
58
|
+
lspTouched?: boolean;
|
|
59
|
+
error?: string;
|
|
60
|
+
metadata?: Record<string, unknown>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function logCascade(entry: CascadeLogEntry): void {
|
|
64
|
+
if (
|
|
65
|
+
process.env.PI_LENS_TEST_MODE === "1" ||
|
|
66
|
+
(process.env.VITEST && process.env.PI_LENS_TEST_MODE !== "0")
|
|
67
|
+
) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const line = `${JSON.stringify({ ts: new Date().toISOString(), ...entry })}\n`;
|
|
71
|
+
try {
|
|
72
|
+
fs.appendFileSync(CASCADE_LOG_FILE, line);
|
|
73
|
+
} catch {}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function getCascadeLogPath(): string {
|
|
77
|
+
return CASCADE_LOG_FILE;
|
|
78
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Diagnostic } from "./dispatch/types.js";
|
|
2
|
+
import type { ImpactCascadeResult } from "./review-graph/types.js";
|
|
3
|
+
|
|
4
|
+
export interface CascadeNeighborResult {
|
|
5
|
+
filePath: string;
|
|
6
|
+
reason: "imports" | "calls" | "references" | "fallback";
|
|
7
|
+
diagnostics: Diagnostic[];
|
|
8
|
+
lspTouched: boolean;
|
|
9
|
+
durationMs?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface CascadeResult {
|
|
13
|
+
filePath: string;
|
|
14
|
+
impact: ImpactCascadeResult;
|
|
15
|
+
neighbors: CascadeNeighborResult[];
|
|
16
|
+
formatted: string;
|
|
17
|
+
}
|
|
@@ -108,6 +108,12 @@ export function createDiagnosticLogger(): DiagnosticLogger {
|
|
|
108
108
|
|
|
109
109
|
return {
|
|
110
110
|
log(entry: DiagnosticEntry) {
|
|
111
|
+
if (
|
|
112
|
+
process.env.PI_LENS_TEST_MODE === "1" ||
|
|
113
|
+
(process.env.VITEST && process.env.PI_LENS_TEST_MODE !== "0")
|
|
114
|
+
) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
111
117
|
pending.push(entry);
|
|
112
118
|
writePending(); // async, non-blocking
|
|
113
119
|
},
|