pi-lens 3.8.35 → 3.8.37
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 +40 -0
- package/README.md +90 -76
- package/banner.svg +73 -0
- package/clients/cascade-logger.ts +1 -0
- package/clients/dispatch/integration.ts +57 -14
- package/clients/formatters.ts +70 -0
- package/clients/installer/index.ts +28 -21
- package/clients/language-policy.ts +4 -4
- package/clients/lsp/index.ts +2 -2
- package/clients/pipeline.ts +72 -35
- package/clients/project-metadata.ts +188 -3
- package/clients/read-expansion.ts +9 -5
- package/clients/runtime-agent-end.ts +170 -0
- package/clients/runtime-coordinator.ts +74 -0
- package/clients/runtime-session.ts +71 -63
- package/clients/runtime-tool-result.ts +18 -0
- package/clients/tool-policy.ts +99 -0
- package/commands/booboo.ts +245 -3
- package/i18n.ts +66 -0
- package/index.ts +103 -13
- package/package.json +4 -22
- package/skills/ast-grep/SKILL.md +13 -19
- package/default-architect.yaml +0 -130
- package/scripts/audit-tree-sitter-rules.mjs +0 -58
- package/scripts/check-extensions.mjs +0 -87
- package/scripts/validate-rule-catalog.mjs +0 -227
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to pi-lens will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [3.8.37] - 2026-05-02
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **ReDoS: 3 compiler output parsers in `/lens-booboo`** — `csRe` trailing optional group `(?:\s+\[[^\]]+\])?` dropped (message capture already stops at `[`); `gleamRe` narrowed `[^:]+` → `[^:\n]+` to prevent cross-line backtracking; `zigRe` replaced `(.+)$` with `([^\n]+)` and dropped the redundant end anchor. All three flagged by SonarCloud S5852.
|
|
12
|
+
|
|
13
|
+
## [3.8.36] - 2026-05-02
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **`agent_end` deferred format notification now lists filenames** — the notification now reads `pi-lens deferred format applied to N file(s): foo.ts, bar.ts` instead of just the count, making it immediately clear which files were reformatted without needing to check logs.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **Deferred formatting by default** — files touched by `write` and `edit` are now queued and formatted once at `agent_end` instead of immediately after each edit. This prevents mid-task formatting mutations from invalidating read-guard context and interrupting multi-edit flows. Formatting still runs in real time when `--immediate-format` is passed.
|
|
22
|
+
- **`agent_end` lifecycle handler** — new `clients/runtime-agent-end.ts` drains the deferred format queue at the end of each agent turn, runs the formatter once per file, syncs formatted content to LSP, and emits a concise notification.
|
|
23
|
+
- **`--immediate-format` flag** — opt-in flag to restore the legacy per-edit formatting behavior.
|
|
24
|
+
- **`/lens-health` session timestamp** — output now opens with `Session started: HH:MM (Xh Ym ago)` so all session-scoped counters have clear time context.
|
|
25
|
+
- **`/lens-health` LSP status section** — shows each currently running language server with a `✓`/`✗` connected indicator and workspace root. Makes dead servers immediately visible to the agent without needing to check logs. Also fixes `LSPService.getStatus()` which previously hardcoded `connected: true` instead of calling `isAlive()`.
|
|
26
|
+
- **`/lens-health` cascade summary** — shows session-total cascade runs, diagnostics surfaced, and cold-snapshot touches (the new active-touch fallback for TypeScript neighbors with no snapshot).
|
|
27
|
+
- **`/lens-health` i18n** — localizes status labels with English fallback; es, fr, and pt-BR strings included (PR #45 by @jerryfan).
|
|
28
|
+
- **`/lens-booboo` language gates** — Knip (dead code), Madge (circular deps), and type coverage now skip on non-JS/TS projects. Compiler checks extended with Java (mvn/gradle), C# (dotnet build), Dart, Gleam, Zig, and Elixir alongside the existing TypeScript, Go, Rust, Ruby, and Python checks.
|
|
29
|
+
- **`project-metadata` detects 8 new languages** — Java, Kotlin, C#, Dart, Gleam, Zig, Elixir, and C++ are now detected from their project markers (pom.xml, build.gradle.kts, \*.sln, pubspec.yaml, gleam.toml, build.zig, mix.exs, CMakeLists.txt). All runners and booboo language gates now work correctly for these languages.
|
|
30
|
+
- **4 new formatters** — `google-java-format` (config-gated via `.editorconfig` or `.google-java-format`), `cljfmt` (config-gated via `.cljfmt.edn`), `cmake-format` (config-gated via `.cmake-format`), and `PSScriptAnalyzer` formatter for PowerShell (smart-default when PSScriptAnalyzer module is available).
|
|
31
|
+
- **Startup pre-install defaults for shell, Ruby, Kotlin, TOML** — `shellcheck`, `rubocop`, `ktlint`, and `taplo` are now pre-installed fire-and-forget at session start for matching projects, consistent with the existing pattern for `typescript-language-server`, `biome`, `pyright`, `ruff`, `yamllint`, and `sqlfluff`. No latency impact — all installs are fire-and-forget and no-ops when already cached.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **Installer race condition** — coalesced the entire `ensureTool()` operation (not just the install phase) to prevent duplicate concurrent "auto-install ensure X: start" probes when multiple tools race to resolve the same binary.
|
|
36
|
+
- **Read-expansion union bug** — tree-sitter read expansion now returns the union of the requested range and the enclosing symbol range, instead of silently dropping originally requested prefix/suffix lines. Fixes false "Edit outside read range" blocks when an agent reads a partial range inside a large symbol.
|
|
37
|
+
- **Startup probe deduplication** — removed broad eager probes for biome, ast-grep, ruff, knip, jscpd, and madge at session start. Replaced with `scheduleDeferredToolProbes()` which only probes tools not already covered by preinstall or startup scans, scoped to the project's actual language profile.
|
|
38
|
+
- **ReDoS-safe compiler output parsers in `/lens-booboo`** — five regex patterns in the compiler checks (Maven, Gradle, .NET, Gleam, Elixir) flagged by SonarCloud as vulnerable to super-linear backtracking (S5852). Fixed: `mvnRe` and `gradleRe` replaced greedy `(.+)$` with `([^\n]+)` and dropped the end anchor; `csRe` replaced lazy `([^[]+?)` with greedy `([^[]+)`; `gleamRe` replaced `(.+?)` with `([^:]+)`; `elixirRe` replaced the multiline regex entirely with a line-by-line parser to eliminate the flagged pattern.
|
|
39
|
+
- **Cascade diagnostics now surface for TypeScript neighbors on cold sessions** — previously cascade silently returned zero diagnostics for TypeScript/Deno neighbors when no passive snapshot existed (i.e. the agent had not yet opened the file). Cold-snapshot neighbors now fall through into the parallel `touchFile` pool with a 1000ms budget (tighter than the 2000ms used for non-jsts neighbors, since the TypeScript server is expected to be warm). Valid snapshots still use the fast read path with no touch. New `coldSnapshot: true` field on `neighbor_touch` log entries tracks these in `cascade.log`.
|
|
40
|
+
|
|
41
|
+
### Improved
|
|
42
|
+
|
|
43
|
+
- **`ast-grep` skill clarifies string literal behaviour** — exact string literals in patterns (e.g. `from "./utils"`) work correctly; only metavariables inside string literals (e.g. `from "$PATH"`) are not supported and should use grep instead. Previously the skill incorrectly implied import path matching was unsupported entirely, causing unnecessary grep fallbacks.
|
|
44
|
+
|
|
5
45
|
## [3.8.35] - 2026-05-02
|
|
6
46
|
|
|
7
47
|
### Fixed
|
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/apmantza/pi-lens/master/banner.svg" alt="pi-lens" width="1100">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# pi-lens
|
|
2
6
|
|
|
3
7
|
pi-lens focuses on real-time inline code feedback for AI agents.
|
|
@@ -9,17 +13,25 @@ pi-lens focuses on real-time inline code feedback for AI agents.
|
|
|
9
13
|
On every `write` and `edit`, pi-lens runs a fast, language-aware pipeline (checks depend on file language, project config, and installed tools):
|
|
10
14
|
|
|
11
15
|
1. **Secrets scan** — blocking; aborts the write if credentials are detected
|
|
12
|
-
2. **Auto-format** —
|
|
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
|
|
13
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
|
|
14
18
|
4. **LSP file sync** — opens/updates the file in active language servers
|
|
15
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
|
|
16
20
|
6. **Cascade diagnostics** — review-graph impact cascade showing which other files were affected and how diagnostics propagated
|
|
17
21
|
|
|
18
22
|
Results are inline and actionable:
|
|
23
|
+
|
|
19
24
|
- **Blocking issues** — stop progress until fixed
|
|
20
25
|
- **Warnings** — summarized inline, detail in `/lens-booboo`
|
|
21
26
|
- **Health/telemetry** — available in `/lens-health`
|
|
22
27
|
|
|
28
|
+
### Agent End
|
|
29
|
+
|
|
30
|
+
At `agent_end` (once per user prompt, after all agent tool calls complete):
|
|
31
|
+
|
|
32
|
+
- **Deferred formatting** — any files queued during the turn are formatted once, synced to LSP, and tracked for read-guard coverage
|
|
33
|
+
- **Summary notification** — concise status: how many files were formatted, which changed, and whether any formatter failed
|
|
34
|
+
|
|
23
35
|
### Session Start
|
|
24
36
|
|
|
25
37
|
At `session_start`, pi-lens:
|
|
@@ -80,6 +92,7 @@ pi-lens auto-detects and runs **26 formatters** based on project config:
|
|
|
80
92
|
biome, prettier, ruff, black, sqlfluff, gofmt, rustfmt, zig fmt, dart format, shfmt, nixfmt, mix format, ocamlformat, clang-format, ktlint, rubocop, standardrb, gleam format, terraform fmt, php-cs-fixer, csharpier, fantomas, swiftformat, stylua, ormolu, taplo
|
|
81
93
|
|
|
82
94
|
Detection rules:
|
|
95
|
+
|
|
83
96
|
- **Config-gated**: only runs when project config indicates usage (e.g. `biome.json`, `.prettierrc`, `ruff.toml`)
|
|
84
97
|
- **Nearest-wins**: when multiple formatter configs exist at different directory levels, the one closest to the edited file wins
|
|
85
98
|
- **Biome-default**: for JS/TS files without Prettier or Biome config, Biome is used as the default formatter
|
|
@@ -149,44 +162,44 @@ Auto-install behavior depends on gate type:
|
|
|
149
162
|
- **Operational prewarm**: installs during session warm scans / turn-end analysis paths
|
|
150
163
|
- **GitHub release**: platform-specific binary downloaded from GitHub releases to `~/.pi-lens/bin/`
|
|
151
164
|
|
|
152
|
-
| Tool
|
|
153
|
-
|
|
154
|
-
| `@biomejs/biome`
|
|
155
|
-
| `prettier`
|
|
156
|
-
| `yamllint`
|
|
157
|
-
| `sqlfluff`
|
|
158
|
-
| `ruff`
|
|
159
|
-
| `typescript-language-server`
|
|
160
|
-
| `typescript`
|
|
161
|
-
| `pyright`
|
|
162
|
-
| `@ast-grep/cli` (sg)
|
|
163
|
-
| `knip`
|
|
164
|
-
| `jscpd`
|
|
165
|
-
| `madge`
|
|
166
|
-
| `mypy`
|
|
167
|
-
| `stylelint`
|
|
168
|
-
| `markdownlint-cli2`
|
|
169
|
-
| `shellcheck`
|
|
170
|
-
| `shfmt`
|
|
171
|
-
| `rust-analyzer`
|
|
172
|
-
| `golangci-lint`
|
|
173
|
-
| `hadolint`
|
|
174
|
-
| `ktlint`
|
|
175
|
-
| `tflint`
|
|
176
|
-
| `taplo`
|
|
177
|
-
| `terraform-ls`
|
|
178
|
-
| `htmlhint`
|
|
179
|
-
| `@prisma/language-server`
|
|
180
|
-
| `dockerfile-language-server-nodejs` | Dockerfile LSP
|
|
181
|
-
| `intelephense`
|
|
182
|
-
| `bash-language-server`
|
|
183
|
-
| `yaml-language-server`
|
|
184
|
-
| `vscode-langservers-extracted`
|
|
185
|
-
| `vscode-css-languageserver`
|
|
186
|
-
| `vscode-html-languageserver-bin`
|
|
187
|
-
| `svelte-language-server`
|
|
188
|
-
| `@vue/language-server`
|
|
189
|
-
| `psscriptanalyzer`
|
|
165
|
+
| Tool | Purpose | Auto-installed | Gate |
|
|
166
|
+
| ----------------------------------- | -------------------------------- | -------------- | ---------------------------------- |
|
|
167
|
+
| `@biomejs/biome` | JS/TS lint/format/autofix | Yes | Config-gated |
|
|
168
|
+
| `prettier` | Formatting fallback | Yes | Config-gated |
|
|
169
|
+
| `yamllint` | YAML linting | Yes | Config-gated |
|
|
170
|
+
| `sqlfluff` | SQL linting/formatting | Yes | Config-gated |
|
|
171
|
+
| `ruff` | Python lint/format/autofix | Yes | Language-default + flow-gated |
|
|
172
|
+
| `typescript-language-server` | Unified LSP diagnostics | Yes | Language-default |
|
|
173
|
+
| `typescript` | TypeScript compiler | Yes | Language-default |
|
|
174
|
+
| `pyright` | Python type diagnostics fallback | Yes | Flow/language-gated |
|
|
175
|
+
| `@ast-grep/cli` (sg) | AST scans/search/replace | Yes | Operational prewarm |
|
|
176
|
+
| `knip` | Dead code analysis | Yes | Operational prewarm + config-gated |
|
|
177
|
+
| `jscpd` | Duplicate code detection | Yes | Operational prewarm + config-gated |
|
|
178
|
+
| `madge` | Circular dependency analysis | Yes | Turn-end analysis flow |
|
|
179
|
+
| `mypy` | Python type checking | Yes | Flow-gated |
|
|
180
|
+
| `stylelint` | CSS/SCSS/Less linting | Yes | Config-gated |
|
|
181
|
+
| `markdownlint-cli2` | Markdown linting | Yes | Config-gated |
|
|
182
|
+
| `shellcheck` | Shell script linting | Yes | GitHub release |
|
|
183
|
+
| `shfmt` | Shell script formatting | Yes | GitHub release |
|
|
184
|
+
| `rust-analyzer` | Rust LSP | Yes | GitHub release |
|
|
185
|
+
| `golangci-lint` | Go linting | Yes | GitHub release |
|
|
186
|
+
| `hadolint` | Dockerfile linting | Yes | GitHub release |
|
|
187
|
+
| `ktlint` | Kotlin linting | Yes | GitHub release |
|
|
188
|
+
| `tflint` | Terraform linting | Yes | GitHub release |
|
|
189
|
+
| `taplo` | TOML linting/formatting | Yes | GitHub release |
|
|
190
|
+
| `terraform-ls` | Terraform LSP | Yes | GitHub release |
|
|
191
|
+
| `htmlhint` | HTML linting | Yes | Config-gated |
|
|
192
|
+
| `@prisma/language-server` | Prisma LSP | Yes | Flow-gated |
|
|
193
|
+
| `dockerfile-language-server-nodejs` | Dockerfile LSP | Yes | Flow-gated |
|
|
194
|
+
| `intelephense` | PHP LSP | Yes | Flow-gated |
|
|
195
|
+
| `bash-language-server` | Bash LSP | Yes | Language-default |
|
|
196
|
+
| `yaml-language-server` | YAML LSP | Yes | Language-default |
|
|
197
|
+
| `vscode-langservers-extracted` | JSON/ESLint/CSS/HTML LSP | Yes | Language-default |
|
|
198
|
+
| `vscode-css-languageserver` | CSS LSP | Yes | Language-default |
|
|
199
|
+
| `vscode-html-languageserver-bin` | HTML LSP | Yes | Language-default |
|
|
200
|
+
| `svelte-language-server` | Svelte LSP | Yes | Flow-gated |
|
|
201
|
+
| `@vue/language-server` | Vue LSP | Yes | Flow-gated |
|
|
202
|
+
| `psscriptanalyzer` | PowerShell linting | Manual | — |
|
|
190
203
|
|
|
191
204
|
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.
|
|
192
205
|
|
|
@@ -198,7 +211,8 @@ pi
|
|
|
198
211
|
|
|
199
212
|
# Optional switches
|
|
200
213
|
pi --no-lsp # Disable unified LSP diagnostics
|
|
201
|
-
pi --no-autoformat # Skip auto-formatting
|
|
214
|
+
pi --no-autoformat # Skip auto-formatting entirely
|
|
215
|
+
pi --immediate-format # Format immediately after each edit instead of deferring to agent_end
|
|
202
216
|
pi --no-autofix # Skip auto-fix (Biome, Ruff, ESLint, stylelint, sqlfluff, RuboCop)
|
|
203
217
|
pi --no-tests # Skip test runner
|
|
204
218
|
pi --no-delta # Disable delta mode (show all diagnostics, not just new ones)
|
|
@@ -232,39 +246,39 @@ Formatting uses a single selected formatter per file: explicit project config wi
|
|
|
232
246
|
|
|
233
247
|
Dispatch is diagnostics-oriented: automatic formatting and safe autofix happen in the post-write pipeline rather than through dispatch format-check runners.
|
|
234
248
|
|
|
235
|
-
| Language
|
|
236
|
-
|
|
237
|
-
| JavaScript/TypeScript | ✓
|
|
238
|
-
| Python
|
|
239
|
-
| Go
|
|
240
|
-
| Rust
|
|
241
|
-
| Ruby
|
|
242
|
-
| C/C++
|
|
243
|
-
| Shell
|
|
244
|
-
| CSS/SCSS/Less
|
|
245
|
-
| HTML
|
|
246
|
-
| YAML
|
|
247
|
-
| JSON
|
|
248
|
-
| SQL
|
|
249
|
-
| Markdown
|
|
250
|
-
| Docker
|
|
251
|
-
| PHP
|
|
252
|
-
| PowerShell
|
|
253
|
-
| Prisma
|
|
254
|
-
| C#
|
|
255
|
-
| F#
|
|
256
|
-
| Java
|
|
257
|
-
| Kotlin
|
|
258
|
-
| Swift
|
|
259
|
-
| Dart
|
|
260
|
-
| Lua
|
|
261
|
-
| Zig
|
|
262
|
-
| Haskell
|
|
263
|
-
| Elixir
|
|
264
|
-
| Gleam
|
|
265
|
-
| OCaml
|
|
266
|
-
| Clojure
|
|
267
|
-
| Terraform
|
|
268
|
-
| Nix
|
|
269
|
-
| TOML
|
|
270
|
-
| CMake
|
|
249
|
+
| Language | LSP | Dispatch Runners | Formatter |
|
|
250
|
+
| --------------------- | --- | -------------------------------------------------------------------------------------------------------------- | ------------------- |
|
|
251
|
+
| JavaScript/TypeScript | ✓ | lsp, ts-lsp, biome-check-json, tree-sitter, ast-grep-napi, type-safety, similarity, fact-rules, eslint, oxlint | biome, prettier |
|
|
252
|
+
| Python | ✓ | lsp, pyright, ruff-lint, tree-sitter, python-slop | ruff, black |
|
|
253
|
+
| Go | ✓ | lsp, go-vet, golangci-lint, tree-sitter | gofmt |
|
|
254
|
+
| Rust | ✓ | lsp, rust-clippy, tree-sitter | rustfmt |
|
|
255
|
+
| Ruby | ✓ | lsp, rubocop, tree-sitter | rubocop, standardrb |
|
|
256
|
+
| C/C++ | ✓ | lsp, cpp-check | clang-format |
|
|
257
|
+
| Shell | ✓ | lsp, shellcheck | shfmt |
|
|
258
|
+
| CSS/SCSS/Less | ✓ | lsp, stylelint | biome, prettier |
|
|
259
|
+
| HTML | ✓ | lsp, htmlhint | prettier |
|
|
260
|
+
| YAML | ✓ | lsp, yamllint | prettier |
|
|
261
|
+
| JSON | ✓ | lsp | biome, prettier |
|
|
262
|
+
| SQL | — | sqlfluff | sqlfluff |
|
|
263
|
+
| Markdown | — | spellcheck, markdownlint | prettier |
|
|
264
|
+
| Docker | ✓ | lsp, hadolint | — |
|
|
265
|
+
| PHP | ✓ | lsp, php-lint, phpstan | php-cs-fixer |
|
|
266
|
+
| PowerShell | ✓ | lsp, psscriptanalyzer | — |
|
|
267
|
+
| Prisma | ✓ | lsp, prisma-validate | — |
|
|
268
|
+
| C# | ✓ | lsp, dotnet-build | csharpier |
|
|
269
|
+
| F# | ✓ | lsp | fantomas |
|
|
270
|
+
| Java | ✓ | lsp, javac | — |
|
|
271
|
+
| Kotlin | ✓ | lsp, ktlint | ktlint |
|
|
272
|
+
| Swift | ✓ | lsp | swiftformat |
|
|
273
|
+
| Dart | ✓ | lsp, dart-analyze | dart format |
|
|
274
|
+
| Lua | ✓ | lsp | stylua |
|
|
275
|
+
| Zig | ✓ | lsp, zig-check | zig fmt |
|
|
276
|
+
| Haskell | ✓ | lsp | ormolu |
|
|
277
|
+
| Elixir | ✓ | lsp, elixir-check, credo | mix format |
|
|
278
|
+
| Gleam | ✓ | lsp, gleam-check | gleam format |
|
|
279
|
+
| OCaml | ✓ | lsp | ocamlformat |
|
|
280
|
+
| Clojure | ✓ | lsp | — |
|
|
281
|
+
| Terraform | ✓ | lsp, tflint | terraform fmt |
|
|
282
|
+
| Nix | ✓ | lsp | nixfmt |
|
|
283
|
+
| TOML | ✓ | lsp, taplo | taplo |
|
|
284
|
+
| CMake | ✓ | lsp | — |
|
package/banner.svg
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<svg width="1100" height="280" viewBox="0 0 1100 280" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" stop-color="#0d1117"/>
|
|
5
|
+
<stop offset="100%" stop-color="#161b22"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<linearGradient id="accent" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
8
|
+
<stop offset="0%" stop-color="#2f81f7"/>
|
|
9
|
+
<stop offset="100%" stop-color="#58a6ff"/>
|
|
10
|
+
</linearGradient>
|
|
11
|
+
<linearGradient id="accentFade" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
12
|
+
<stop offset="0%" stop-color="#2f81f7"/>
|
|
13
|
+
<stop offset="100%" stop-color="#2f81f700"/>
|
|
14
|
+
</linearGradient>
|
|
15
|
+
<clipPath id="bounds">
|
|
16
|
+
<rect width="1100" height="280" rx="12"/>
|
|
17
|
+
</clipPath>
|
|
18
|
+
</defs>
|
|
19
|
+
|
|
20
|
+
<!-- Background -->
|
|
21
|
+
<rect width="1100" height="280" fill="url(#bg)" rx="12"/>
|
|
22
|
+
|
|
23
|
+
<!-- Top accent line -->
|
|
24
|
+
<rect x="0" y="0" width="1100" height="3" fill="url(#accent)" rx="1.5" clip-path="url(#bounds)"/>
|
|
25
|
+
|
|
26
|
+
<!-- Decorative aperture rings (right side) -->
|
|
27
|
+
<g opacity="0.07" clip-path="url(#bounds)">
|
|
28
|
+
<circle cx="920" cy="140" r="180" fill="none" stroke="#2f81f7" stroke-width="1.5"/>
|
|
29
|
+
<circle cx="920" cy="140" r="145" fill="none" stroke="#2f81f7" stroke-width="1"/>
|
|
30
|
+
<circle cx="920" cy="140" r="110" fill="none" stroke="#2f81f7" stroke-width="1"/>
|
|
31
|
+
<circle cx="920" cy="140" r="75" fill="none" stroke="#2f81f7" stroke-width="1"/>
|
|
32
|
+
<line x1="740" y1="140" x2="1100" y2="140" stroke="#2f81f7" stroke-width="0.8"/>
|
|
33
|
+
<line x1="920" y1="-40" x2="920" y2="320" stroke="#2f81f7" stroke-width="0.8"/>
|
|
34
|
+
<line x1="793" y1="13" x2="1047" y2="267" stroke="#2f81f7" stroke-width="0.6"/>
|
|
35
|
+
<line x1="1047" y1="13" x2="793" y2="267" stroke="#2f81f7" stroke-width="0.6"/>
|
|
36
|
+
</g>
|
|
37
|
+
|
|
38
|
+
<!-- Lens icon -->
|
|
39
|
+
<!-- Outer ring glow -->
|
|
40
|
+
<circle cx="142" cy="130" r="70" fill="#2f81f708"/>
|
|
41
|
+
<!-- Lens circle -->
|
|
42
|
+
<circle cx="142" cy="130" r="63" fill="#2f81f710" stroke="url(#accent)" stroke-width="4.5"/>
|
|
43
|
+
<!-- Inner ring -->
|
|
44
|
+
<circle cx="142" cy="130" r="48" fill="none" stroke="#2f81f740" stroke-width="1.5"/>
|
|
45
|
+
<!-- Handle -->
|
|
46
|
+
<line x1="191" y1="179" x2="222" y2="214" stroke="url(#accent)" stroke-width="8" stroke-linecap="round"/>
|
|
47
|
+
<!-- Code brackets inside lens -->
|
|
48
|
+
<text x="110" y="145" font-family="'Courier New', Courier, monospace" font-size="34" font-weight="700" fill="#58a6ff"></></text>
|
|
49
|
+
|
|
50
|
+
<!-- pi-lens wordmark -->
|
|
51
|
+
<text x="258" y="122" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, sans-serif" font-size="74" font-weight="700" fill="#e6edf3" letter-spacing="-2">pi-lens</text>
|
|
52
|
+
|
|
53
|
+
<!-- Tagline -->
|
|
54
|
+
<text x="261" y="164" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, sans-serif" font-size="21" font-weight="400" fill="#8b949e" letter-spacing="0.3">Real-time code intelligence for AI agents</text>
|
|
55
|
+
|
|
56
|
+
<!-- Feature pills -->
|
|
57
|
+
<g font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, sans-serif" font-size="12" font-weight="500">
|
|
58
|
+
<rect x="261" y="196" width="56" height="24" rx="12" fill="#2f81f715" stroke="#2f81f740" stroke-width="1"/>
|
|
59
|
+
<text x="289" y="213" fill="#58a6ff" text-anchor="middle">LSP</text>
|
|
60
|
+
|
|
61
|
+
<rect x="327" y="196" width="68" height="24" rx="12" fill="#2f81f715" stroke="#2f81f740" stroke-width="1"/>
|
|
62
|
+
<text x="361" y="213" fill="#58a6ff" text-anchor="middle">Linters</text>
|
|
63
|
+
|
|
64
|
+
<rect x="405" y="196" width="90" height="24" rx="12" fill="#2f81f715" stroke="#2f81f740" stroke-width="1"/>
|
|
65
|
+
<text x="450" y="213" fill="#58a6ff" text-anchor="middle">Formatters</text>
|
|
66
|
+
|
|
67
|
+
<rect x="505" y="196" width="152" height="24" rx="12" fill="#2f81f715" stroke="#2f81f740" stroke-width="1"/>
|
|
68
|
+
<text x="581" y="213" fill="#58a6ff" text-anchor="middle">Structural Analysis</text>
|
|
69
|
+
|
|
70
|
+
<rect x="667" y="196" width="96" height="24" rx="12" fill="#2f81f715" stroke="#2f81f740" stroke-width="1"/>
|
|
71
|
+
<text x="715" y="213" fill="#58a6ff" text-anchor="middle">Read-Guard</text>
|
|
72
|
+
</g>
|
|
73
|
+
</svg>
|
|
@@ -49,6 +49,7 @@ export interface CascadeLogEntry {
|
|
|
49
49
|
lspServerCount?: number; // number of LSP servers configured for this file type
|
|
50
50
|
touchedCount?: number;
|
|
51
51
|
snapshotCount?: number;
|
|
52
|
+
coldSnapshot?: boolean; // true when touch was triggered because autoPropagate snapshot was missing
|
|
52
53
|
|
|
53
54
|
// shared
|
|
54
55
|
fallbackUsed?: boolean;
|
|
@@ -357,6 +357,13 @@ export function resetDispatchBaselines(): void {
|
|
|
357
357
|
neighborTouchCache.clear();
|
|
358
358
|
primaryFilesThisTurn.clear();
|
|
359
359
|
cascadeDiagnosticBaselines.clear();
|
|
360
|
+
cascadeSessionStats = { runs: 0, diagnosticsSurfaced: 0, coldSnapshotTouches: 0 };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
let cascadeSessionStats = { runs: 0, diagnosticsSurfaced: 0, coldSnapshotTouches: 0 };
|
|
364
|
+
|
|
365
|
+
export function getCascadeSessionStats(): { runs: number; diagnosticsSurfaced: number; coldSnapshotTouches: number } {
|
|
366
|
+
return { ...cascadeSessionStats };
|
|
360
367
|
}
|
|
361
368
|
|
|
362
369
|
// A5: per-turn neighbor-touch cache keyed by normalized path.
|
|
@@ -496,6 +503,7 @@ export async function computeCascadeForFile(
|
|
|
496
503
|
|
|
497
504
|
const neighbors: CascadeResult["neighbors"] = [];
|
|
498
505
|
let producedLspData = false;
|
|
506
|
+
let coldSnapshotPaths: string[] = [];
|
|
499
507
|
|
|
500
508
|
if (sortedNeighbors.length > 0) {
|
|
501
509
|
const snapshotPaths = sortedNeighbors.filter(shouldReadCascadeFromSnapshot);
|
|
@@ -504,6 +512,10 @@ export async function computeCascadeForFile(
|
|
|
504
512
|
);
|
|
505
513
|
|
|
506
514
|
// Auto-propagating LSPs (TypeScript/Deno) — read passive snapshot with normalized key.
|
|
515
|
+
// When the snapshot is valid, use it immediately (no touch needed — server already has
|
|
516
|
+
// fresh data from auto-propagation). When missing or stale, fall through to the active
|
|
517
|
+
// touch pool below so we get real diagnostics instead of silently returning zero.
|
|
518
|
+
coldSnapshotPaths = [];
|
|
507
519
|
for (const neighborPath of snapshotPaths) {
|
|
508
520
|
const neighborStart = Date.now();
|
|
509
521
|
const entry = allDiags.get(normalizeMapKey(neighborPath));
|
|
@@ -512,14 +524,30 @@ export async function computeCascadeForFile(
|
|
|
512
524
|
: undefined;
|
|
513
525
|
const snapshotValid =
|
|
514
526
|
entry != null && Date.now() - entry.ts < CASCADE_TTL_MS;
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
527
|
+
|
|
528
|
+
if (!snapshotValid) {
|
|
529
|
+
// No usable snapshot — queue for active touch alongside non-jsts neighbors.
|
|
530
|
+
logCascade({
|
|
531
|
+
phase: "neighbor_snapshot",
|
|
532
|
+
filePath,
|
|
533
|
+
neighborFile: neighborPath,
|
|
534
|
+
diagnosticCount: 0,
|
|
535
|
+
durationMs: Date.now() - neighborStart,
|
|
536
|
+
autoPropagate: true,
|
|
537
|
+
snapshotMissing: entry == null,
|
|
538
|
+
snapshotAgeSec,
|
|
539
|
+
coldSnapshot: true,
|
|
540
|
+
});
|
|
541
|
+
coldSnapshotPaths.push(neighborPath);
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const diags = convertLspDiagnostics(
|
|
546
|
+
entry.diags.filter((d) => d.severity === 1).slice(0, MAX_PER_FILE),
|
|
547
|
+
neighborPath,
|
|
548
|
+
{ source: "cascade" },
|
|
549
|
+
);
|
|
550
|
+
producedLspData = true;
|
|
523
551
|
const durationMs = Date.now() - neighborStart;
|
|
524
552
|
|
|
525
553
|
logCascade({
|
|
@@ -529,7 +557,7 @@ export async function computeCascadeForFile(
|
|
|
529
557
|
diagnosticCount: diags.length,
|
|
530
558
|
durationMs,
|
|
531
559
|
autoPropagate: true,
|
|
532
|
-
snapshotMissing:
|
|
560
|
+
snapshotMissing: false,
|
|
533
561
|
snapshotAgeSec,
|
|
534
562
|
});
|
|
535
563
|
|
|
@@ -542,10 +570,13 @@ export async function computeCascadeForFile(
|
|
|
542
570
|
});
|
|
543
571
|
}
|
|
544
572
|
|
|
545
|
-
//
|
|
546
|
-
//
|
|
573
|
+
// fan-out active touches in parallel (A3):
|
|
574
|
+
// - non-jsts neighbors (always touched)
|
|
575
|
+
// - autoPropagate neighbors whose snapshot was missing/stale (coldSnapshotPaths)
|
|
576
|
+
// use a tighter 1000ms budget since the server is expected to be warm already.
|
|
547
577
|
const touchResults = await Promise.allSettled(
|
|
548
|
-
activePaths.map(async (neighborPath) => {
|
|
578
|
+
[...activePaths, ...coldSnapshotPaths].map(async (neighborPath) => {
|
|
579
|
+
const isColdSnapshot = coldSnapshotPaths.includes(neighborPath);
|
|
549
580
|
const neighborStart = Date.now();
|
|
550
581
|
const cacheKey = normalizeMapKey(neighborPath);
|
|
551
582
|
|
|
@@ -595,10 +626,13 @@ export async function computeCascadeForFile(
|
|
|
595
626
|
const content = await nodeFs.promises.readFile(neighborPath, "utf8");
|
|
596
627
|
// Open with silent=true (suppresses didChangeWatchedFiles rechecks, C2)
|
|
597
628
|
// and collect diagnostics from the same touched clients.
|
|
629
|
+
// Cold-snapshot neighbors (autoPropagate LSP, server warm) use a tighter
|
|
630
|
+
// 1000ms budget — they should respond quickly; we'd rather return zero
|
|
631
|
+
// than block cascade for 2s on a slow open.
|
|
598
632
|
const rawDiags = await lspService.touchFile(neighborPath, content, {
|
|
599
633
|
diagnostics: "document",
|
|
600
634
|
collectDiagnostics: true,
|
|
601
|
-
maxClientWaitMs: 2000,
|
|
635
|
+
maxClientWaitMs: isColdSnapshot ? 1000 : 2000,
|
|
602
636
|
silent: true,
|
|
603
637
|
source: "cascade",
|
|
604
638
|
clientScope: "all",
|
|
@@ -629,6 +663,7 @@ export async function computeCascadeForFile(
|
|
|
629
663
|
durationMs,
|
|
630
664
|
lspTouched: true,
|
|
631
665
|
lspServerCount: configuredServerCount,
|
|
666
|
+
coldSnapshot: isColdSnapshot,
|
|
632
667
|
});
|
|
633
668
|
|
|
634
669
|
return {
|
|
@@ -641,9 +676,10 @@ export async function computeCascadeForFile(
|
|
|
641
676
|
}),
|
|
642
677
|
);
|
|
643
678
|
|
|
679
|
+
const allTouchPaths = [...activePaths, ...coldSnapshotPaths];
|
|
644
680
|
for (let i = 0; i < touchResults.length; i++) {
|
|
645
681
|
const result = touchResults[i];
|
|
646
|
-
const neighborPath =
|
|
682
|
+
const neighborPath = allTouchPaths[i];
|
|
647
683
|
if (result.status === "fulfilled") {
|
|
648
684
|
if (result.value) neighbors.push(result.value);
|
|
649
685
|
} else {
|
|
@@ -722,6 +758,13 @@ export async function computeCascadeForFile(
|
|
|
722
758
|
},
|
|
723
759
|
});
|
|
724
760
|
|
|
761
|
+
cascadeSessionStats.runs += 1;
|
|
762
|
+
cascadeSessionStats.diagnosticsSurfaced += visibleNeighbors.reduce(
|
|
763
|
+
(sum, n) => sum + n.diagnostics.length,
|
|
764
|
+
0,
|
|
765
|
+
);
|
|
766
|
+
cascadeSessionStats.coldSnapshotTouches += coldSnapshotPaths.length;
|
|
767
|
+
|
|
725
768
|
if (!formatted) return undefined;
|
|
726
769
|
|
|
727
770
|
getDiagnosticTracker().trackShown(
|
package/clients/formatters.ts
CHANGED
|
@@ -20,6 +20,9 @@ import {
|
|
|
20
20
|
hasBiomeConfig,
|
|
21
21
|
hasBlackConfig,
|
|
22
22
|
hasClangFormatConfig,
|
|
23
|
+
hasCljfmtConfig,
|
|
24
|
+
hasCmakeFormatConfig,
|
|
25
|
+
hasGoogleJavaFormatConfig,
|
|
23
26
|
hasNearestPackageJsonDependency,
|
|
24
27
|
hasNearestPackageJsonField,
|
|
25
28
|
hasOcamlformatConfig,
|
|
@@ -296,6 +299,12 @@ function hasExplicitFormatterConfig(
|
|
|
296
299
|
return hasStyluaConfig(cwd);
|
|
297
300
|
case "ocamlformat":
|
|
298
301
|
return hasOcamlformatConfig(cwd);
|
|
302
|
+
case "google-java-format":
|
|
303
|
+
return hasGoogleJavaFormatConfig(cwd);
|
|
304
|
+
case "cljfmt":
|
|
305
|
+
return hasCljfmtConfig(cwd);
|
|
306
|
+
case "cmake-format":
|
|
307
|
+
return hasCmakeFormatConfig(cwd);
|
|
299
308
|
default:
|
|
300
309
|
return false;
|
|
301
310
|
}
|
|
@@ -737,6 +746,63 @@ export const taploFormatter: FormatterInfo = {
|
|
|
737
746
|
},
|
|
738
747
|
};
|
|
739
748
|
|
|
749
|
+
export const googleJavaFormatFormatter: FormatterInfo = {
|
|
750
|
+
name: "google-java-format",
|
|
751
|
+
command: ["google-java-format", "--replace", "$FILE"],
|
|
752
|
+
extensions: [".java"],
|
|
753
|
+
async detect(cwd: string) {
|
|
754
|
+
if ((await which("google-java-format")) === null) return false;
|
|
755
|
+
return hasGoogleJavaFormatConfig(cwd);
|
|
756
|
+
},
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
export const cljfmtFormatter: FormatterInfo = {
|
|
760
|
+
name: "cljfmt",
|
|
761
|
+
command: ["cljfmt", "fix", "$FILE"],
|
|
762
|
+
extensions: [".clj", ".cljc", ".cljs"],
|
|
763
|
+
async detect(cwd: string) {
|
|
764
|
+
if ((await which("cljfmt")) === null) return false;
|
|
765
|
+
return hasCljfmtConfig(cwd);
|
|
766
|
+
},
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
export const cmakeFormatFormatter: FormatterInfo = {
|
|
770
|
+
name: "cmake-format",
|
|
771
|
+
command: ["cmake-format", "-i", "$FILE"],
|
|
772
|
+
extensions: [".cmake"],
|
|
773
|
+
async detect(cwd: string) {
|
|
774
|
+
if ((await which("cmake-format")) === null) return false;
|
|
775
|
+
return hasCmakeFormatConfig(cwd);
|
|
776
|
+
},
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
export const psscriptanalyzerFormatFormatter: FormatterInfo = {
|
|
780
|
+
name: "psscriptanalyzer-format",
|
|
781
|
+
command: ["pwsh", "-Command", "Invoke-Formatter -ScriptDefinition (Get-Content -Raw '$FILE') | Set-Content '$FILE'"],
|
|
782
|
+
extensions: [".ps1", ".psm1", ".psd1"],
|
|
783
|
+
async resolveCommand(filePath, _cwd) {
|
|
784
|
+
const pwsh = (await which("pwsh")) ?? (await which("powershell"));
|
|
785
|
+
if (!pwsh) return null;
|
|
786
|
+
return [
|
|
787
|
+
pwsh,
|
|
788
|
+
"-NoProfile",
|
|
789
|
+
"-Command",
|
|
790
|
+
`$content = Get-Content -Raw '${filePath}'; $formatted = Invoke-Formatter -ScriptDefinition $content; Set-Content -Path '${filePath}' -Value $formatted`,
|
|
791
|
+
];
|
|
792
|
+
},
|
|
793
|
+
async detect(_cwd: string) {
|
|
794
|
+
const pwsh = (await which("pwsh")) ?? (await which("powershell"));
|
|
795
|
+
if (!pwsh) return false;
|
|
796
|
+
// Check PSScriptAnalyzer module is available
|
|
797
|
+
const result = safeSpawn(pwsh, [
|
|
798
|
+
"-NoProfile",
|
|
799
|
+
"-Command",
|
|
800
|
+
"Get-Module -ListAvailable PSScriptAnalyzer | Select-Object -First 1 -ExpandProperty Name",
|
|
801
|
+
], { timeout: 5_000 });
|
|
802
|
+
return (result.stdout ?? "").includes("PSScriptAnalyzer");
|
|
803
|
+
},
|
|
804
|
+
};
|
|
805
|
+
|
|
740
806
|
// --- Registry ---
|
|
741
807
|
|
|
742
808
|
const ALL_FORMATTERS: FormatterInfo[] = [
|
|
@@ -767,6 +833,10 @@ const ALL_FORMATTERS: FormatterInfo[] = [
|
|
|
767
833
|
standardrbFormatter,
|
|
768
834
|
gleamFormatter,
|
|
769
835
|
taploFormatter,
|
|
836
|
+
googleJavaFormatFormatter,
|
|
837
|
+
cljfmtFormatter,
|
|
838
|
+
cmakeFormatFormatter,
|
|
839
|
+
psscriptanalyzerFormatFormatter,
|
|
770
840
|
];
|
|
771
841
|
|
|
772
842
|
// Cache for detection results - stores array of enabled formatter names per cwd+ext
|