pi-lens 3.3.1 → 3.6.0
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 +83 -0
- package/README.md +175 -13
- package/clients/cache/rule-cache.js +72 -0
- package/clients/cache/rule-cache.ts +104 -0
- package/clients/dispatch/integration.js +48 -1
- package/clients/dispatch/integration.ts +60 -2
- package/clients/dispatch/plan.js +5 -2
- package/clients/dispatch/plan.ts +5 -2
- package/clients/dispatch/runners/ast-grep-napi.js +175 -56
- package/clients/dispatch/runners/ast-grep-napi.test.js +2 -1
- package/clients/dispatch/runners/ast-grep-napi.test.ts +2 -1
- package/clients/dispatch/runners/ast-grep-napi.ts +191 -79
- package/clients/dispatch/runners/similarity.js +1 -1
- package/clients/dispatch/runners/similarity.ts +2 -2
- package/clients/dispatch/runners/tree-sitter.js +137 -10
- package/clients/dispatch/runners/tree-sitter.ts +168 -13
- package/clients/dispatch/runners/ts-lsp.js +3 -2
- package/clients/dispatch/runners/ts-lsp.ts +3 -2
- package/clients/dispatch/runners/yaml-rule-parser.js +70 -2
- package/clients/dispatch/runners/yaml-rule-parser.ts +71 -2
- package/clients/dispatch/types.js +1 -1
- package/clients/dispatch/types.ts +1 -1
- package/clients/lsp/__tests__/service.test.js +3 -0
- package/clients/lsp/__tests__/service.test.ts +3 -0
- package/clients/lsp/client.js +42 -0
- package/clients/lsp/client.ts +79 -0
- package/clients/lsp/index.js +27 -0
- package/clients/lsp/index.ts +35 -0
- package/clients/metrics-client.js +3 -160
- package/clients/metrics-client.tdr.test.js +78 -0
- package/clients/metrics-client.test.js +30 -43
- package/clients/metrics-client.test.ts +30 -54
- package/clients/metrics-client.ts +5 -219
- package/clients/metrics-history.js +33 -7
- package/clients/metrics-history.ts +47 -10
- package/clients/pipeline.js +272 -0
- package/clients/pipeline.ts +371 -0
- package/clients/sg-runner.js +21 -3
- package/clients/sg-runner.ts +22 -3
- package/clients/tree-sitter-client.js +23 -2
- package/clients/tree-sitter-client.ts +27 -2
- package/index.ts +604 -771
- package/package.json +1 -1
- package/rules/ast-grep-rules/rules/no-architecture-violation.yml +7 -4
- package/rules/ast-grep-rules/rules/no-single-char-var.yml +3 -3
- package/rules/ast-grep-rules/slop-patterns.yml +85 -62
- package/skills/ast-grep/SKILL.md +42 -1
- package/skills/lsp-navigation/SKILL.md +62 -0
- package/tsconfig.json +1 -1
- package/rules/ast-grep-rules/rules/no-console-log.yml +0 -10
- package/rules/ast-grep-rules/rules/no-default-export.yml +0 -19
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,89 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to pi-lens will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [3.6.0] - 2026-04-02
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **LSP Call Hierarchy Support** — Added 3 new operations to `lsp_navigation` tool:
|
|
9
|
+
- `prepareCallHierarchy` — Get callable item at position
|
|
10
|
+
- `incomingCalls` — Find all functions/methods that CALL this function
|
|
11
|
+
- `outgoingCalls` — Find all functions/methods CALLED by this function
|
|
12
|
+
- Use case: "Who calls this function?" and "What does this function depend on?"
|
|
13
|
+
- **LSP Navigation Skill** — New built-in skill (`skills/lsp-navigation/SKILL.md`) that guides LLM on when to use LSP for code intelligence vs other tools
|
|
14
|
+
- **AST-Grep Skill Improvements** — Enhanced `skills/ast-grep/SKILL.md` with:
|
|
15
|
+
- Testing Tips section (Search → Dry-run → Apply workflow)
|
|
16
|
+
- Metavariable selection guide ($ vs $$$)
|
|
17
|
+
- Specific guidance for "Multiple AST nodes" error
|
|
18
|
+
- **Skills Registration** — Extension now registers `skills/` directory via `resources_discover` event, exposing both `ast-grep` and `lsp-navigation` skills to pi
|
|
19
|
+
- **Enhanced TDI (Technical Debt Index) with 5-factor formula** — Now captures "worst offender" functions and code unpredictability:
|
|
20
|
+
- **Max Cyclomatic (10%)**: Catches worst function complexity (avg hides bad apples)
|
|
21
|
+
- **Entropy (5%)**: Measures code unpredictability/vocabulary richness in bits
|
|
22
|
+
- Rebalanced weights: MI (45%), Cognitive (30%), Nesting (10%), MaxCyc (10%), Entropy (5%)
|
|
23
|
+
- New thresholds: MaxCyc >10 bad, >30 critical; Entropy >4.0 bits risky, >7.0 critical
|
|
24
|
+
|
|
25
|
+
### Removed
|
|
26
|
+
- **TDR (Technical Debt Ratio)** — Removed orphaned metric tracking system:
|
|
27
|
+
- Deleted `TDREntry`, `TDRCategory` types, `tdrFindings` Map, `updateTDR()` method
|
|
28
|
+
- Removed `convertDiagnosticsToTDREntries()` helper and all `tdrCategory` assignments
|
|
29
|
+
- Deleted TDR test file
|
|
30
|
+
- TDI is sufficient for code health tracking; inline diagnostics provide immediate feedback
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- **Updated `/lens-tdi` display** — Shows 5 category breakdown with descriptions:
|
|
34
|
+
```
|
|
35
|
+
Debt breakdown:
|
|
36
|
+
Maintainability: 45% (MI-based)
|
|
37
|
+
Cognitive: 30%
|
|
38
|
+
Nesting: 10%
|
|
39
|
+
Max Cyclomatic: 10% (worst function)
|
|
40
|
+
Entropy: 5% (code unpredictability)
|
|
41
|
+
```
|
|
42
|
+
- **Extended MetricSnapshot** — Added `maxCyclomatic` and `entropy` fields for historical tracking
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## [3.5.0] - 2026-04-02
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
- **Tree-sitter query compilation cache** — 10× performance improvement for structural analysis. Query files (`.yml`) are compiled to binary `.wasm-cache` format once and cached to disk. Subsequent loads use the compiled cache directly, reducing tree-sitter startup from ~50ms to ~5ms per query. Cache uses mtime-based invalidation — automatically recompiles when source `.yml` changes.
|
|
50
|
+
- **Rule cache infrastructure** (`clients/cache/`) — New disk-backed cache system with:
|
|
51
|
+
- `RuleCache` class for storing compiled artifacts
|
|
52
|
+
- mtime-based invalidation (auto-refresh when source files change)
|
|
53
|
+
- JSON metadata tracking for cache entries
|
|
54
|
+
- TTL and integrity validation
|
|
55
|
+
|
|
56
|
+
### Fixed
|
|
57
|
+
- **YAML parser colon truncation** — Fixed regex-based parser that incorrectly truncated values containing colons. Changed from `split(':', 2)` to `indexOf(':')` for proper value extraction.
|
|
58
|
+
- **Tree-sitter rules directory resolution** — Fixed path resolution to use `ctx.cwd` instead of hardcoded `.pi-lens/rules/` path. Rules now load correctly from the actual project root regardless of where pi is invoked.
|
|
59
|
+
- **Tree-sitter post_filter support** — Implemented missing `post_filter` functionality for tree-sitter queries. Rules with post-filters (e.g., semantic validation for `bare-except` vs specific exception handlers) now work correctly instead of being silently skipped.
|
|
60
|
+
- **Event handler silent crashes** — Wrapped all event handlers in try/catch to prevent unhandled exceptions from crashing the extension silently. Errors are now logged to stderr instead of terminating the process.
|
|
61
|
+
- **Latency logging restored** — Fixed missing latency logging in `tool_result` handler. Runner timing data now correctly flows to `~/.pi-lens/latency.log` again.
|
|
62
|
+
|
|
63
|
+
### Removed
|
|
64
|
+
- **Broken ast-grep rules** — Removed overlapping rules that were causing false positives or conflicts with tree-sitter coverage.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## [3.4.0] - 2026-04-02
|
|
69
|
+
|
|
70
|
+
### Fixed
|
|
71
|
+
- **Delta mode was broken** — `dispatchLint()` created a fresh empty baseline store on every call, making delta filtering a complete no-op. Every issue looked "new" every time. Now uses a persistent session-level baseline store. First write captures baseline, subsequent writes only show NEW issues.
|
|
72
|
+
- **Duplicate type-checking with `--lens-lsp`** — Both the `lsp` runner (priority 4) and `ts-lsp` runner (priority 5) were calling the same LSP service for TypeScript files. `ts-lsp` now skips when `--lens-lsp` is active.
|
|
73
|
+
|
|
74
|
+
### Added
|
|
75
|
+
- **Inline security rules via ast-grep-napi** — Re-enabled the ast-grep-napi runner for real-time blocking on security violations (`no-eval`, `jwt-no-verify`, `no-hardcoded-secrets`, `weak-rsa-key`, `no-open-redirect`, etc.). Only error-severity rules fire inline; warnings remain in `/lens-booboo`. Skips 5 rules already covered by tree-sitter to avoid duplicates. ~9ms execution time.
|
|
76
|
+
- **Pre-write duplicate detection (two layers):**
|
|
77
|
+
- **Exact name match** — Checks exported names in new content against the session’s cached export index. If a function/class/type already exists in another file, blocks the write: `🔴 STOP — function X already exists in utils.ts. Import instead.`
|
|
78
|
+
- **Structural similarity** — Parses new functions, builds AST state matrices, compares against the project index (built at session start). Functions with ≥80% structural similarity trigger a warning with the match location. Non-blocking.
|
|
79
|
+
- **Project similarity index at session start** — Builds 57×72 state matrices for all TS functions at session start (cached to `.pi-lens/index.json`). Makes pre-write similarity checks ~50ms instead of seconds.
|
|
80
|
+
|
|
81
|
+
### Changed
|
|
82
|
+
- **Extracted post-write pipeline** — Moved the entire post-write pipeline (secrets, format, autofix, dispatch, tests, cascade diagnostics) from `index.ts` into `clients/pipeline.ts`. `index.ts` reduced from 1764 to 1439 lines.
|
|
83
|
+
- **Removed inline complexity warnings** — `⚠️ Complexity increased: +4 cognitive` no longer shown on every write. No agent acts on this mid-task. Complexity data still captured for `/lens-booboo` and `/lens-tdi`.
|
|
84
|
+
- **Simplified pre-write handler** — Removed pre-write TypeScript and LSP diagnostics checks (checked old content before write landed — post-write catches everything). Kept only complexity baseline capture and duplicate detection.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
5
88
|
## [3.3.1] - 2026-04-02
|
|
6
89
|
|
|
7
90
|
### Fixed
|
package/README.md
CHANGED
|
@@ -202,12 +202,12 @@ pi-lens uses a **dispatcher-runner architecture** for extensible multi-language
|
|
|
202
202
|
| **ruff** | Python | 10 | Warning | Python linting (delta-tracked) |
|
|
203
203
|
| **oxlint** | TS/JS | 12 | Warning | Fast Rust-based JS/TS linter |
|
|
204
204
|
| **tree-sitter** | TS/JS, Python | 14 | Mixed | AST-based structural analysis (21 patterns) — **singleton WASM client** |
|
|
205
|
-
| **ast-grep-napi** | TS/JS | 15 |
|
|
205
|
+
| **ast-grep-napi** | TS/JS | 15 | Blocking | Security rules inline (no-eval, jwt-no-verify, no-hardcoded-secrets, etc.) |
|
|
206
206
|
| **type-safety** | TS | 20 | Mixed | Switch exhaustiveness (blocking), other (warning) |
|
|
207
207
|
| **shellcheck** | Shell | 20 | Warning | Bash/sh/zsh/fish linting |
|
|
208
208
|
| **python-slop** | Python | 25 | Warning | AI slop detection (~40 patterns) |
|
|
209
209
|
| **spellcheck** | Markdown | 30 | Warning | Typo detection in docs |
|
|
210
|
-
| **similarity** | TS | 35 |
|
|
210
|
+
| **similarity** | TS | 35 | Warning | Semantic duplicate detection (structural similarity) |
|
|
211
211
|
| **architect** | All | 40 | Warning | Architectural rule violations |
|
|
212
212
|
| **go-vet** | Go | 50 | Warning | Go static analysis |
|
|
213
213
|
| **rust-clippy** | Rust | 50 | Warning | Rust linting |
|
|
@@ -224,7 +224,7 @@ pi-lens uses a **dispatcher-runner architecture** for extensible multi-language
|
|
|
224
224
|
- **Warning** — Shown in `/lens-booboo`, not inline (noise reduction)
|
|
225
225
|
- **Silent** — Tracked in metrics only, never shown
|
|
226
226
|
|
|
227
|
-
**Consolidated runners:** `ts-slop` merged into `ast-grep-napi`
|
|
227
|
+
**Consolidated runners:** `ts-slop` merged into `ast-grep-napi` — CLI ast-grep used for full linter via `/lens-booboo`
|
|
228
228
|
|
|
229
229
|
**Tree-sitter runner patterns** (priority 14, AST-based structural analysis):
|
|
230
230
|
|
|
@@ -244,7 +244,7 @@ Python (6 patterns):
|
|
|
244
244
|
|
|
245
245
|
**AI Slop Detection:**
|
|
246
246
|
- `python-slop` runner (priority 25): ~40 patterns for Python code quality
|
|
247
|
-
- `ast-grep-napi` runner (priority 15):
|
|
247
|
+
- `ast-grep-napi` runner (priority 15): Security rules fire inline (blocking); slop/architecture warnings via `/lens-booboo` only. Skips 5 rules already covered by tree-sitter.
|
|
248
248
|
|
|
249
249
|
---
|
|
250
250
|
|
|
@@ -402,13 +402,112 @@ pi-lens calculates comprehensive code quality metrics for every source file:
|
|
|
402
402
|
| **Cyclomatic Complexity** | 1+ | Independent code paths (branch points + 1) | >10: 🟡 Complex function, >20: 🔴 Highly complex |
|
|
403
403
|
| **Max Cyclomatic** | 1+ | Worst function in file | >10 flagged |
|
|
404
404
|
| **Nesting Depth** | 0+ | Maximum block nesting level | >4: 🟡 Deep nesting, >6: 🔴 Excessive |
|
|
405
|
-
| **Code Entropy** | 0-8+ bits | Shannon entropy — unpredictability of code patterns | >
|
|
405
|
+
| **Code Entropy** | 0-8+ bits | Shannon entropy — unpredictability of code patterns | >4.0: 🟡 Risky, >7.0: 🔴 Very unpredictable |
|
|
406
406
|
| **Halstead Volume** | 0+ | Vocabulary × length — unique ops/operands | High = many different operations |
|
|
407
407
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
408
|
+
#### Metric Calculations
|
|
409
|
+
|
|
410
|
+
**Maintainability Index (Microsoft's Formula)**
|
|
411
|
+
```
|
|
412
|
+
MI = max(0, (171 - 5.2*ln(Halstead) - 0.23*Cyclomatic - 16.2*ln(LOC)) * 100/171) + commentBonus
|
|
413
|
+
|
|
414
|
+
Where:
|
|
415
|
+
- Halstead = Halstead Volume (vocabulary-based complexity)
|
|
416
|
+
- Cyclomatic = Average cyclomatic complexity across functions
|
|
417
|
+
- LOC = Lines of code
|
|
418
|
+
- commentBonus = up to +10% for well-commented code
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**Cognitive Complexity (SonarSource Spec)**
|
|
422
|
+
- +1 for each structural node (if, for, while, case, catch, switch)
|
|
423
|
+
- +1 for each level of nesting (nested structures add nesting penalty)
|
|
424
|
+
- +1 for each && and || in binary expressions (logical operators)
|
|
425
|
+
- Exception: else-if chains don't add nesting
|
|
426
|
+
|
|
427
|
+
**Cyclomatic Complexity (McCabe's Formula)**
|
|
428
|
+
```
|
|
429
|
+
M = E - N + 2P
|
|
430
|
+
|
|
431
|
+
Where:
|
|
432
|
+
- E = Number of edges in control flow graph
|
|
433
|
+
- N = Number of nodes
|
|
434
|
+
- P = Number of connected components (usually 1)
|
|
435
|
+
|
|
436
|
+
Simplified: Count branch points (if, while, for, case, &&, ||) + 1
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
**Max Cyclomatic**
|
|
440
|
+
- Single value: the highest cyclomatic complexity of any function in the file
|
|
441
|
+
- Catches "worst offender" functions that average metrics hide
|
|
442
|
+
|
|
443
|
+
**Code Entropy (Shannon Entropy in Bits)**
|
|
444
|
+
```
|
|
445
|
+
H = -Σ(p(i) * log2(p(i)))
|
|
446
|
+
|
|
447
|
+
Where:
|
|
448
|
+
- p(i) = frequency of token i / total tokens
|
|
449
|
+
- Measures unpredictability/vocabulary richness
|
|
450
|
+
|
|
451
|
+
Thresholds:
|
|
452
|
+
- ≤4.0 bits: Predictable, conventional code ✅
|
|
453
|
+
- 4.0-7.0 bits: Moderate complexity 🟡
|
|
454
|
+
- ≥7.0 bits: Unpredictable, hard to maintain 🔴
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
**Halstead Volume**
|
|
458
|
+
```
|
|
459
|
+
V = N * log2(n)
|
|
460
|
+
|
|
461
|
+
Where:
|
|
462
|
+
- N = total operators + operands (program length)
|
|
463
|
+
- n = unique operators + operands (vocabulary size)
|
|
464
|
+
|
|
465
|
+
Measures: How much information the reader must absorb
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
### Technical Debt Index (TDI)
|
|
471
|
+
|
|
472
|
+
The TDI provides a single score (0-100) representing overall codebase health. Lower is better.
|
|
473
|
+
|
|
474
|
+
**TDI Formula (5-Factor Weighted)**
|
|
475
|
+
```
|
|
476
|
+
TDI = MI-debt(45%) + cognitive(30%) + nesting(10%) + maxCyc(10%) + entropy(5%)
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
**Debt Calculation for Each Factor:**
|
|
480
|
+
|
|
481
|
+
| Factor | Debt Formula | Good | Bad |
|
|
482
|
+
|--------|---------------|------|-----|
|
|
483
|
+
| **MI** | `(100 - MI) / 100` | 100 | 0 |
|
|
484
|
+
| **Cognitive** | `min(1, cognitive / 200)` | 0 | ≥500 |
|
|
485
|
+
| **Nesting** | `max(0, nesting - 3) / 7` | ≤3 | ≥10 |
|
|
486
|
+
| **Max Cyclomatic** | `max(0, maxCyc - 10) / 20` | ≤10 | ≥30 |
|
|
487
|
+
| **Entropy** | `max(0, entropy - 4.0) / 3.0` | ≤4.0 | ≥7.0 |
|
|
488
|
+
|
|
489
|
+
**Grades:**
|
|
490
|
+
- **A** (0-15%): Excellent codebase health
|
|
491
|
+
- **B** (16-30%): Good, minor improvements possible
|
|
492
|
+
- **C** (31-50%): Moderate debt, consider refactoring
|
|
493
|
+
- **D** (51-70%): Significant debt, plan refactoring
|
|
494
|
+
- **F** (71%+): High debt, immediate attention needed
|
|
495
|
+
|
|
496
|
+
**Usage:**
|
|
497
|
+
```bash
|
|
498
|
+
/lens-tdi # Display TDI score with breakdown by category
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
### AI Slop Indicators
|
|
504
|
+
|
|
505
|
+
Metrics that suggest potentially AI-generated low-quality code:
|
|
506
|
+
|
|
507
|
+
- **Low MI + high cognitive + high entropy** = potential spaghetti code
|
|
508
|
+
- **Excessive comments (>40%) + low MI** = hand-holding anti-patterns
|
|
509
|
+
- **Single-use helpers with high entropy** = over-abstraction
|
|
510
|
+
- **Many small functions with high cyclomatic** = fragmented complexity
|
|
412
511
|
|
|
413
512
|
**Usage:**
|
|
414
513
|
- `/lens-booboo` — Shows complexity table for all files
|
|
@@ -476,7 +575,6 @@ pi-lens works out of the box for TypeScript/JavaScript. For full language suppor
|
|
|
476
575
|
|------|---------|--------------|
|
|
477
576
|
| **Standard** (default) | `pi` | Auto-formatting, TS/Python type-checking, sequential execution |
|
|
478
577
|
| **Full LSP** | `pi --lens-lsp` | Real LSP servers (31 languages), sequential execution |
|
|
479
|
-
| **Fastest** | `pi --lens-lsp` | Real LSP + full runner suite |
|
|
480
578
|
|
|
481
579
|
|
|
482
580
|
### Flag Reference
|
|
@@ -502,7 +600,6 @@ pi-lens works out of the box for TypeScript/JavaScript. For full language suppor
|
|
|
502
600
|
```bash
|
|
503
601
|
pi # Default: auto-format, auto-fix, built-in type-checking
|
|
504
602
|
pi --lens-lsp # LSP type-checking (31 languages)
|
|
505
|
-
pi --lens-lsp # LSP mode (recommended)
|
|
506
603
|
```
|
|
507
604
|
|
|
508
605
|
---
|
|
@@ -639,7 +736,8 @@ Tracks which files were edited in the current agent turn for:
|
|
|
639
736
|
|
|
640
737
|
| Runner | Cache | Notes |
|
|
641
738
|
|--------|-------|-------|
|
|
642
|
-
| `
|
|
739
|
+
| `tree-sitter` | Compiled query cache | `.wasm-cache` files with mtime-based invalidation. 10× faster startup. |
|
|
740
|
+
| `ast-grep-napi` | Rule descriptions | Loaded once per session |
|
|
643
741
|
| `biome` | Tool availability | Checked once, cached |
|
|
644
742
|
| `pyright` | Command path | Venv lookup cached |
|
|
645
743
|
| `ruff` | Command path | Venv lookup cached |
|
|
@@ -655,10 +753,12 @@ pi-lens/
|
|
|
655
753
|
│ │ ├── bus.ts
|
|
656
754
|
│ │ ├── events.ts
|
|
657
755
|
│ │ └── integration.ts
|
|
756
|
+
│ ├── cache/ # Rule compilation cache
|
|
757
|
+
│ │ └── rule-cache.ts # Disk-backed cache with mtime invalidation
|
|
658
758
|
│ ├── dispatch/ # Dispatcher and runners
|
|
659
759
|
│ │ ├── dispatcher.ts
|
|
660
760
|
│ │ └── runners/ # Individual runners
|
|
661
|
-
│ │ ├── ast-grep-napi.ts #
|
|
761
|
+
│ │ ├── ast-grep-napi.ts # Security rules inline, warnings in booboo
|
|
662
762
|
│ │ ├── python-slop.ts # Python slop detection
|
|
663
763
|
│ │ ├── ts-lsp.ts # TS type checking
|
|
664
764
|
│ │ ├── biome.ts
|
|
@@ -693,12 +793,74 @@ pi-lens/
|
|
|
693
793
|
|
|
694
794
|
---
|
|
695
795
|
|
|
796
|
+
## Skills
|
|
797
|
+
|
|
798
|
+
pi-lens includes two built-in skills that guide the LLM on when to use specific tools:
|
|
799
|
+
|
|
800
|
+
### ast-grep
|
|
801
|
+
|
|
802
|
+
**Purpose:** Guide AST-aware pattern matching for semantic code search/replace.
|
|
803
|
+
|
|
804
|
+
**When to load:** Use `/skill:ast-grep` when performing structural code searches (finding function calls, class methods, imports) or replacements across files.
|
|
805
|
+
|
|
806
|
+
**Key guidance:**
|
|
807
|
+
- Use `$VAR` for single nodes, `$$$` for multiple
|
|
808
|
+
- Patterns must be **complete valid code** (not fragments)
|
|
809
|
+
- **Workflow:** Search → Dry-run (`apply: false`) → Apply (`apply: true`)
|
|
810
|
+
- **Error "Multiple AST nodes":** Use metavariables like `it($TEST)` not raw text like `it"test"`
|
|
811
|
+
|
|
812
|
+
```typescript
|
|
813
|
+
// ✅ GOOD: Complete code with metavariables
|
|
814
|
+
ast_grep_search
|
|
815
|
+
pattern: "console.log($MSG)"
|
|
816
|
+
lang: typescript
|
|
817
|
+
paths: ["src/"]
|
|
818
|
+
|
|
819
|
+
// ❌ BAD: Incomplete pattern
|
|
820
|
+
pattern: "console.log(" // Missing args/body
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
### lsp-navigation
|
|
824
|
+
|
|
825
|
+
**Purpose:** Guide code intelligence via Language Server Protocol.
|
|
826
|
+
|
|
827
|
+
**When to load:** Use `/skill:lsp-navigation` for understanding code structure — definitions, references, types, call hierarchy.
|
|
828
|
+
|
|
829
|
+
**Key guidance:**
|
|
830
|
+
- **LSP is PRIMARY** for code intelligence — NOT grep/glob/ast-grep
|
|
831
|
+
- Requires `--lens-lsp` flag
|
|
832
|
+
- Call hierarchy: `prepareCallHierarchy` → `incomingCalls`/`outgoingCalls`
|
|
833
|
+
|
|
834
|
+
| Task | Use LSP | Use Other |
|
|
835
|
+
|------|---------|-----------|
|
|
836
|
+
| "Where is this defined?" | `definition` | — |
|
|
837
|
+
| "Find all usages" | `references` | — |
|
|
838
|
+
| "What type is this?" | `hover` | — |
|
|
839
|
+
| "Who calls this function?" | `prepareCallHierarchy` → `incomingCalls` | — |
|
|
840
|
+
| Find patterns (console.log) | — | `ast_grep_search` |
|
|
841
|
+
| Find TODO comments | — | `grep` |
|
|
842
|
+
|
|
843
|
+
```typescript
|
|
844
|
+
// ✅ Code intelligence → LSP
|
|
845
|
+
lsp_navigation
|
|
846
|
+
operation: "references"
|
|
847
|
+
filePath: "src/utils.ts"
|
|
848
|
+
line: 42
|
|
849
|
+
character: 10
|
|
850
|
+
|
|
851
|
+
// ❌ Don't use LSP for text patterns
|
|
852
|
+
pattern: "TODO" // Use grep instead
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
---
|
|
856
|
+
|
|
696
857
|
## Changelog
|
|
697
858
|
|
|
698
859
|
See [CHANGELOG.md](CHANGELOG.md) for full history.
|
|
699
860
|
|
|
700
861
|
### Latest Highlights
|
|
701
862
|
|
|
863
|
+
- **Tree-sitter Query Cache:** Compiled query cache with mtime-based invalidation — 10× faster structural analysis startup
|
|
702
864
|
- **LSP Support:** 31 Language Server Protocol clients (4 core auto-installed, others via npx or manual)
|
|
703
865
|
- **NAPI Runner:** 100x faster TypeScript/JavaScript structural analysis (~9ms vs ~1200ms) — currently disabled in realtime due to stability
|
|
704
866
|
- **Slop Detection:** 33+ TypeScript and 40+ Python patterns for AI-generated code quality issues
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule Cache for pi-lens
|
|
3
|
+
*
|
|
4
|
+
* Provides disk-based caching for parsed tree-sitter rules with
|
|
5
|
+
* automatic invalidation based on rule file modification times.
|
|
6
|
+
*/
|
|
7
|
+
import * as crypto from "node:crypto";
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
const CACHE_DIR = path.join(process.cwd(), ".pi-lens", "cache");
|
|
11
|
+
const CACHE_VERSION = "v1";
|
|
12
|
+
export class RuleCache {
|
|
13
|
+
constructor(language) {
|
|
14
|
+
this.cacheFile = path.join(CACHE_DIR, `${language}-rules-${CACHE_VERSION}.json`);
|
|
15
|
+
}
|
|
16
|
+
ensureCacheDir() {
|
|
17
|
+
if (!fs.existsSync(CACHE_DIR)) {
|
|
18
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
computeRuleHash(ruleFiles) {
|
|
22
|
+
const hash = crypto.createHash("sha256");
|
|
23
|
+
for (const file of ruleFiles.sort()) {
|
|
24
|
+
if (fs.existsSync(file)) {
|
|
25
|
+
const stat = fs.statSync(file);
|
|
26
|
+
hash.update(`${file}:${stat.mtimeMs}:${stat.size}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return hash.digest("hex").slice(0, 16);
|
|
30
|
+
}
|
|
31
|
+
get(ruleFiles) {
|
|
32
|
+
try {
|
|
33
|
+
this.ensureCacheDir();
|
|
34
|
+
if (!fs.existsSync(this.cacheFile))
|
|
35
|
+
return null;
|
|
36
|
+
const cached = JSON.parse(fs.readFileSync(this.cacheFile, "utf-8"));
|
|
37
|
+
const currentHash = this.computeRuleHash(ruleFiles);
|
|
38
|
+
if (cached.version !== CACHE_VERSION || cached.ruleHash !== currentHash) {
|
|
39
|
+
return null; // Cache invalid
|
|
40
|
+
}
|
|
41
|
+
return cached;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
set(ruleFiles, queries) {
|
|
48
|
+
try {
|
|
49
|
+
this.ensureCacheDir();
|
|
50
|
+
const entry = {
|
|
51
|
+
version: CACHE_VERSION,
|
|
52
|
+
timestamp: Date.now(),
|
|
53
|
+
ruleHash: this.computeRuleHash(ruleFiles),
|
|
54
|
+
queries,
|
|
55
|
+
};
|
|
56
|
+
fs.writeFileSync(this.cacheFile, JSON.stringify(entry, null, 2));
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Cache write failure is non-fatal
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
clear() {
|
|
63
|
+
try {
|
|
64
|
+
if (fs.existsSync(this.cacheFile)) {
|
|
65
|
+
fs.unlinkSync(this.cacheFile);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Ignore
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule Cache for pi-lens
|
|
3
|
+
*
|
|
4
|
+
* Provides disk-based caching for parsed tree-sitter rules with
|
|
5
|
+
* automatic invalidation based on rule file modification times.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as crypto from "node:crypto";
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
|
|
12
|
+
const CACHE_DIR = path.join(process.cwd(), ".pi-lens", "cache");
|
|
13
|
+
const CACHE_VERSION = "v1";
|
|
14
|
+
|
|
15
|
+
export interface QueryCacheEntry {
|
|
16
|
+
version: string;
|
|
17
|
+
timestamp: number;
|
|
18
|
+
ruleHash: string;
|
|
19
|
+
queries: Array<{
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
severity: string;
|
|
23
|
+
language: string;
|
|
24
|
+
message: string;
|
|
25
|
+
query: string;
|
|
26
|
+
metavars: string[];
|
|
27
|
+
post_filter?: string;
|
|
28
|
+
// biome-ignore lint/suspicious/noExplicitAny: Flexible filter params
|
|
29
|
+
post_filter_params?: Record<string, any>;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class RuleCache {
|
|
34
|
+
private cacheFile: string;
|
|
35
|
+
|
|
36
|
+
constructor(language: string) {
|
|
37
|
+
this.cacheFile = path.join(
|
|
38
|
+
CACHE_DIR,
|
|
39
|
+
`${language}-rules-${CACHE_VERSION}.json`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private ensureCacheDir(): void {
|
|
44
|
+
if (!fs.existsSync(CACHE_DIR)) {
|
|
45
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private computeRuleHash(ruleFiles: string[]): string {
|
|
50
|
+
const hash = crypto.createHash("sha256");
|
|
51
|
+
for (const file of ruleFiles.sort()) {
|
|
52
|
+
if (fs.existsSync(file)) {
|
|
53
|
+
const stat = fs.statSync(file);
|
|
54
|
+
hash.update(`${file}:${stat.mtimeMs}:${stat.size}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return hash.digest("hex").slice(0, 16);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get(ruleFiles: string[]): QueryCacheEntry | null {
|
|
61
|
+
try {
|
|
62
|
+
this.ensureCacheDir();
|
|
63
|
+
if (!fs.existsSync(this.cacheFile)) return null;
|
|
64
|
+
|
|
65
|
+
const cached = JSON.parse(
|
|
66
|
+
fs.readFileSync(this.cacheFile, "utf-8"),
|
|
67
|
+
) as QueryCacheEntry;
|
|
68
|
+
const currentHash = this.computeRuleHash(ruleFiles);
|
|
69
|
+
|
|
70
|
+
if (cached.version !== CACHE_VERSION || cached.ruleHash !== currentHash) {
|
|
71
|
+
return null; // Cache invalid
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return cached;
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
set(ruleFiles: string[], queries: QueryCacheEntry["queries"]): void {
|
|
81
|
+
try {
|
|
82
|
+
this.ensureCacheDir();
|
|
83
|
+
const entry: QueryCacheEntry = {
|
|
84
|
+
version: CACHE_VERSION,
|
|
85
|
+
timestamp: Date.now(),
|
|
86
|
+
ruleHash: this.computeRuleHash(ruleFiles),
|
|
87
|
+
queries,
|
|
88
|
+
};
|
|
89
|
+
fs.writeFileSync(this.cacheFile, JSON.stringify(entry, null, 2));
|
|
90
|
+
} catch {
|
|
91
|
+
// Cache write failure is non-fatal
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
clear(): void {
|
|
96
|
+
try {
|
|
97
|
+
if (fs.existsSync(this.cacheFile)) {
|
|
98
|
+
fs.unlinkSync(this.cacheFile);
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
// Ignore
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -10,6 +10,19 @@ import { clearLatencyReports, createBaselineStore, createDispatchContext, format
|
|
|
10
10
|
export { clearLatencyReports, formatLatencyReport, getLatencyReports };
|
|
11
11
|
// Import runners to register them
|
|
12
12
|
import "./runners/index.js";
|
|
13
|
+
// --- Persistent Baseline Store ---
|
|
14
|
+
// Survives across dispatchLint calls within a session.
|
|
15
|
+
// Without this, delta mode is a no-op: every call creates a fresh empty
|
|
16
|
+
// store, so baselines.get() always returns undefined and every issue
|
|
17
|
+
// looks "new" every time.
|
|
18
|
+
const sessionBaselines = createBaselineStore();
|
|
19
|
+
/**
|
|
20
|
+
* Reset baselines — call on session_start so a new session
|
|
21
|
+
* starts with a clean slate.
|
|
22
|
+
*/
|
|
23
|
+
export function resetDispatchBaselines() {
|
|
24
|
+
sessionBaselines.clear();
|
|
25
|
+
}
|
|
13
26
|
/**
|
|
14
27
|
* Run linting for a file using the declarative dispatch system
|
|
15
28
|
*
|
|
@@ -20,7 +33,9 @@ import "./runners/index.js";
|
|
|
20
33
|
*/
|
|
21
34
|
export async function dispatchLint(filePath, cwd, pi) {
|
|
22
35
|
// By default, only run BLOCKING rules for fast feedback on file write
|
|
23
|
-
|
|
36
|
+
// Uses persistent sessionBaselines so delta mode actually filters
|
|
37
|
+
// pre-existing issues after the first write.
|
|
38
|
+
const ctx = createDispatchContext(filePath, cwd, pi, sessionBaselines, true);
|
|
24
39
|
// Import dispatchForFile dynamically to avoid circular deps
|
|
25
40
|
const { dispatchForFile } = await import("./dispatcher.js");
|
|
26
41
|
const { getRunnersForKind } = await import("./dispatcher.js");
|
|
@@ -34,6 +49,38 @@ export async function dispatchLint(filePath, cwd, pi) {
|
|
|
34
49
|
const result = await dispatchForFile(ctx, plan.groups);
|
|
35
50
|
return result.output;
|
|
36
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Run linting and return full result (including diagnostics)
|
|
54
|
+
*/
|
|
55
|
+
export async function dispatchLintWithResult(filePath, cwd, pi) {
|
|
56
|
+
const ctx = createDispatchContext(filePath, cwd, pi, sessionBaselines, true);
|
|
57
|
+
const { dispatchForFile } = await import("./dispatcher.js");
|
|
58
|
+
const { getRunnersForKind } = await import("./dispatcher.js");
|
|
59
|
+
const { TOOL_PLANS } = await import("./plan.js");
|
|
60
|
+
const kind = ctx.kind;
|
|
61
|
+
if (!kind) {
|
|
62
|
+
return {
|
|
63
|
+
diagnostics: [],
|
|
64
|
+
blockers: [],
|
|
65
|
+
warnings: [],
|
|
66
|
+
fixed: [],
|
|
67
|
+
output: "",
|
|
68
|
+
hasBlockers: false,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const plan = TOOL_PLANS[kind];
|
|
72
|
+
if (!plan) {
|
|
73
|
+
return {
|
|
74
|
+
diagnostics: [],
|
|
75
|
+
blockers: [],
|
|
76
|
+
warnings: [],
|
|
77
|
+
fixed: [],
|
|
78
|
+
output: "",
|
|
79
|
+
hasBlockers: false,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return await dispatchForFile(ctx, plan.groups);
|
|
83
|
+
}
|
|
37
84
|
/**
|
|
38
85
|
* Create a baseline store for delta mode tracking
|
|
39
86
|
*/
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
getLatencyReports,
|
|
16
16
|
type RunnerLatency,
|
|
17
17
|
} from "./dispatcher.js";
|
|
18
|
-
import type { PiAgentAPI } from "./types.js";
|
|
18
|
+
import type { BaselineStore, DispatchResult, PiAgentAPI } from "./types.js";
|
|
19
19
|
|
|
20
20
|
export type { DispatchLatencyReport, RunnerLatency };
|
|
21
21
|
// Re-export latency tracking types and functions
|
|
@@ -24,6 +24,21 @@ export { clearLatencyReports, formatLatencyReport, getLatencyReports };
|
|
|
24
24
|
// Import runners to register them
|
|
25
25
|
import "./runners/index.js";
|
|
26
26
|
|
|
27
|
+
// --- Persistent Baseline Store ---
|
|
28
|
+
// Survives across dispatchLint calls within a session.
|
|
29
|
+
// Without this, delta mode is a no-op: every call creates a fresh empty
|
|
30
|
+
// store, so baselines.get() always returns undefined and every issue
|
|
31
|
+
// looks "new" every time.
|
|
32
|
+
const sessionBaselines: BaselineStore = createBaselineStore();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Reset baselines — call on session_start so a new session
|
|
36
|
+
* starts with a clean slate.
|
|
37
|
+
*/
|
|
38
|
+
export function resetDispatchBaselines(): void {
|
|
39
|
+
sessionBaselines.clear();
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
/**
|
|
28
43
|
* Run linting for a file using the declarative dispatch system
|
|
29
44
|
*
|
|
@@ -38,7 +53,9 @@ export async function dispatchLint(
|
|
|
38
53
|
pi: PiAgentAPI,
|
|
39
54
|
): Promise<string> {
|
|
40
55
|
// By default, only run BLOCKING rules for fast feedback on file write
|
|
41
|
-
|
|
56
|
+
// Uses persistent sessionBaselines so delta mode actually filters
|
|
57
|
+
// pre-existing issues after the first write.
|
|
58
|
+
const ctx = createDispatchContext(filePath, cwd, pi, sessionBaselines, true);
|
|
42
59
|
|
|
43
60
|
// Import dispatchForFile dynamically to avoid circular deps
|
|
44
61
|
const { dispatchForFile } = await import("./dispatcher.js");
|
|
@@ -55,6 +72,47 @@ export async function dispatchLint(
|
|
|
55
72
|
return result.output;
|
|
56
73
|
}
|
|
57
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Run linting and return full result (including diagnostics)
|
|
77
|
+
*/
|
|
78
|
+
export async function dispatchLintWithResult(
|
|
79
|
+
filePath: string,
|
|
80
|
+
cwd: string,
|
|
81
|
+
pi: PiAgentAPI,
|
|
82
|
+
): Promise<DispatchResult> {
|
|
83
|
+
const ctx = createDispatchContext(filePath, cwd, pi, sessionBaselines, true);
|
|
84
|
+
|
|
85
|
+
const { dispatchForFile } = await import("./dispatcher.js");
|
|
86
|
+
const { getRunnersForKind } = await import("./dispatcher.js");
|
|
87
|
+
const { TOOL_PLANS } = await import("./plan.js");
|
|
88
|
+
|
|
89
|
+
const kind = ctx.kind;
|
|
90
|
+
if (!kind) {
|
|
91
|
+
return {
|
|
92
|
+
diagnostics: [],
|
|
93
|
+
blockers: [],
|
|
94
|
+
warnings: [],
|
|
95
|
+
fixed: [],
|
|
96
|
+
output: "",
|
|
97
|
+
hasBlockers: false,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const plan = TOOL_PLANS[kind];
|
|
102
|
+
if (!plan) {
|
|
103
|
+
return {
|
|
104
|
+
diagnostics: [],
|
|
105
|
+
blockers: [],
|
|
106
|
+
warnings: [],
|
|
107
|
+
fixed: [],
|
|
108
|
+
output: "",
|
|
109
|
+
hasBlockers: false,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return await dispatchForFile(ctx, plan.groups);
|
|
114
|
+
}
|
|
115
|
+
|
|
58
116
|
/**
|
|
59
117
|
* Create a baseline store for delta mode tracking
|
|
60
118
|
*/
|