pi-lens 3.6.0 → 3.6.1
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/README.md +26 -309
- package/clients/biome-client.js +28 -0
- package/clients/biome-client.ts +32 -0
- package/clients/dispatch/runners/biome.js +1 -2
- package/clients/dispatch/runners/biome.ts +1 -2
- package/clients/installer/index.js +0 -21
- package/clients/installer/index.ts +0 -22
- package/clients/lsp/server.js +68 -5
- package/clients/lsp/server.ts +75 -7
- package/clients/pipeline.js +1 -1
- package/clients/pipeline.ts +1 -1
- package/index.ts +1 -54
- package/package.json +2 -2
- package/clients/metrics-client.tdr.test.js +0 -78
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
3. **Scans for secrets** — Blocks on hardcoded API keys, tokens, passwords
|
|
11
11
|
4. **Runs linters** — Biome (TS/JS), Ruff (Python), plus structural analysis
|
|
12
12
|
5. **Tree-sitter analysis** — Deep structural patterns (empty catch, eval, deep nesting, mixed async styles)
|
|
13
|
-
6. **Auto-installs** — TypeScript, Python, Biome, Ruff tools install
|
|
13
|
+
6. **Auto-installs** — TypeScript, Python, Biome, Ruff, and analysis tools auto-install on first use
|
|
14
14
|
7. **Only shows NEW issues** — Delta-mode tracks baselines and filters pre-existing problems
|
|
15
15
|
|
|
16
16
|
**🔴 Blockers** (type errors, secrets, empty catches) appear inline and stop the agent until fixed.
|
|
@@ -28,10 +28,7 @@ pi
|
|
|
28
28
|
# Disable auto-formatting if needed
|
|
29
29
|
pi --no-autoformat
|
|
30
30
|
|
|
31
|
-
# Full LSP mode (31 language servers)
|
|
32
|
-
pi --lens-lsp
|
|
33
|
-
|
|
34
|
-
# LSP mode (recommended for large projects)
|
|
31
|
+
# Full LSP mode (31 language servers) — recommended for large/multi-language projects
|
|
35
32
|
pi --lens-lsp
|
|
36
33
|
```
|
|
37
34
|
|
|
@@ -131,7 +128,7 @@ Enable full Language Server Protocol support with `--lens-lsp`:
|
|
|
131
128
|
| **Config** | YAML, JSON, Prisma |
|
|
132
129
|
| **Web** | Vue, Svelte, CSS/SCSS/Sass/Less |
|
|
133
130
|
|
|
134
|
-
**Auto-installation (
|
|
131
|
+
**Auto-installation (8 tools):** TypeScript, Python, Biome, Ruff, and analysis tools (Madge, jscpd, ast-grep, Knip) auto-install on first use to `.pi-lens/tools/`. Other LSP servers are launched via `npx` when available or require manual installation.
|
|
135
132
|
|
|
136
133
|
**Usage:**
|
|
137
134
|
```bash
|
|
@@ -156,14 +153,6 @@ See [docs/LSP_CONFIG.md](docs/LSP_CONFIG.md) for configuration options.
|
|
|
156
153
|
|
|
157
154
|
---
|
|
158
155
|
|
|
159
|
-
### Execution Modes
|
|
160
|
-
|
|
161
|
-
| Mode | Flag | Description |
|
|
162
|
-
|------|------|-------------|
|
|
163
|
-
| **Sequential** | (default) | Runners execute one at a time |
|
|
164
|
-
|
|
165
|
-
---
|
|
166
|
-
|
|
167
156
|
### On every write / edit
|
|
168
157
|
|
|
169
158
|
Every file write/edit triggers multiple analysis phases:
|
|
@@ -294,7 +283,7 @@ message: "Remove console statements before production"
|
|
|
294
283
|
severity: warning
|
|
295
284
|
```
|
|
296
285
|
|
|
297
|
-
See [
|
|
286
|
+
See [AST_GREP_RULES.md](AST_GREP_RULES.md) for full guide.
|
|
298
287
|
|
|
299
288
|
---
|
|
300
289
|
|
|
@@ -393,125 +382,20 @@ Running the full suite on every edit would be too slow. Targeted testing gives i
|
|
|
393
382
|
|
|
394
383
|
### Complexity Metrics
|
|
395
384
|
|
|
396
|
-
pi-lens
|
|
397
|
-
|
|
398
|
-
| Metric | Range | Description | Thresholds |
|
|
399
|
-
|--------|-------|-------------|------------|
|
|
400
|
-
| **Maintainability Index (MI)** | 0-100 | Composite score combining complexity, size, and structure | <20: 🔴 Unmaintainable, 20-40: 🟡 Poor, >60: ✅ Good |
|
|
401
|
-
| **Cognitive Complexity** | 0+ | Human mental effort to understand code (nesting penalties) | >20: 🟡 Hard to understand, >50: 🔴 Very complex |
|
|
402
|
-
| **Cyclomatic Complexity** | 1+ | Independent code paths (branch points + 1) | >10: 🟡 Complex function, >20: 🔴 Highly complex |
|
|
403
|
-
| **Max Cyclomatic** | 1+ | Worst function in file | >10 flagged |
|
|
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 | >4.0: 🟡 Risky, >7.0: 🔴 Very unpredictable |
|
|
406
|
-
| **Halstead Volume** | 0+ | Vocabulary × length — unique ops/operands | High = many different operations |
|
|
385
|
+
pi-lens tracks code quality metrics for every file:
|
|
407
386
|
|
|
408
|
-
|
|
387
|
+
| Metric | Description | Threshold |
|
|
388
|
+
|--------|-------------|-----------|
|
|
389
|
+
| **Maintainability Index** | 0-100 composite score | >60 ✅ <20 🔴 |
|
|
390
|
+
| **Cognitive Complexity** | Mental effort to understand | >20 🟡 >50 🔴 |
|
|
391
|
+
| **Cyclomatic Complexity** | Independent code paths | >10 🟡 >20 🔴 |
|
|
392
|
+
| **Code Entropy** | Shannon entropy in bits | >4.0 🟡 >7.0 🔴 |
|
|
409
393
|
|
|
410
|
-
**
|
|
411
|
-
|
|
412
|
-
|
|
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
|
|
394
|
+
**Commands:**
|
|
395
|
+
- `/lens-tdi` — Technical Debt Index (0-100) with grades A-F
|
|
396
|
+
- `/lens-booboo` — Full complexity table for all files
|
|
430
397
|
|
|
431
|
-
|
|
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
|
|
511
|
-
|
|
512
|
-
**Usage:**
|
|
513
|
-
- `/lens-booboo` — Shows complexity table for all files
|
|
514
|
-
- `tool_result` — Complexity tracked per file, AI slop warnings inline
|
|
398
|
+
See [docs/COMPLEXITY_METRICS.md](docs/COMPLEXITY_METRICS.md) for formulas and detailed calculations.
|
|
515
399
|
|
|
516
400
|
---
|
|
517
401
|
|
|
@@ -528,7 +412,7 @@ pi-lens works out of the box for TypeScript/JavaScript. For full language suppor
|
|
|
528
412
|
| `knip` | `npm i -D knip` | Dead code / unused exports |
|
|
529
413
|
| `jscpd` | `npm i -D jscpd` | Copy-paste detection |
|
|
530
414
|
| `type-coverage` | `npm i -D type-coverage` | TypeScript `any` coverage % |
|
|
531
|
-
| `@ast-grep/napi` | `npm i -D @ast-grep/napi` | Fast structural analysis (TS/JS) —
|
|
415
|
+
| `@ast-grep/napi` | `npm i -D @ast-grep/napi` | Fast structural analysis (TS/JS) — security rules inline, slop in booboo |
|
|
532
416
|
| `@ast-grep/cli` | `npm i -D @ast-grep/cli` | Structural pattern matching (all languages) |
|
|
533
417
|
| `typos-cli` | `cargo install typos-cli` | Spellcheck for Markdown |
|
|
534
418
|
|
|
@@ -564,7 +448,6 @@ pi-lens works out of the box for TypeScript/JavaScript. For full language suppor
|
|
|
564
448
|
| Command | Description |
|
|
565
449
|
|---------|-------------|
|
|
566
450
|
| `/lens-booboo` | Full codebase review (10 analysis runners) |
|
|
567
|
-
| `/lens-format` | Apply Biome formatting |
|
|
568
451
|
| `/lens-tdi` | Technical Debt Index and trends |
|
|
569
452
|
|
|
570
453
|
---
|
|
@@ -606,141 +489,7 @@ pi --lens-lsp # LSP type-checking (31 languages)
|
|
|
606
489
|
|
|
607
490
|
## TypeScript LSP — tsconfig detection
|
|
608
491
|
|
|
609
|
-
The LSP walks up from
|
|
610
|
-
|
|
611
|
-
- `target: ES2020`
|
|
612
|
-
- `lib: ["es2020", "dom", "dom.iterable"]`
|
|
613
|
-
- `moduleResolution: bundler`
|
|
614
|
-
- `strict: true`
|
|
615
|
-
|
|
616
|
-
The compiler options are refreshed automatically when you switch between projects within a session.
|
|
617
|
-
|
|
618
|
-
---
|
|
619
|
-
|
|
620
|
-
## Exclusion Criteria
|
|
621
|
-
|
|
622
|
-
pi-lens automatically excludes certain files from analysis to reduce noise and focus on production code.
|
|
623
|
-
|
|
624
|
-
### Test Files
|
|
625
|
-
|
|
626
|
-
All runners respect test file exclusions — both in the dispatch system (`skipTestFiles: true`) and the `/lens-booboo` command.
|
|
627
|
-
|
|
628
|
-
**Excluded patterns:**
|
|
629
|
-
```
|
|
630
|
-
**/*.test.ts **/*.test.tsx **/*.test.js **/*.test.jsx
|
|
631
|
-
**/*.spec.ts **/*.spec.tsx **/*.spec.js **/*.spec.jsx
|
|
632
|
-
**/*.poc.test.ts **/*.poc.test.tsx
|
|
633
|
-
**/test-utils.ts **/test-*.ts
|
|
634
|
-
**/__tests__/** **/tests/** **/test/**
|
|
635
|
-
```
|
|
636
|
-
|
|
637
|
-
**Why:** Test files intentionally duplicate patterns (test fixtures, mock setups) and have different complexity standards. Including them creates false positives.
|
|
638
|
-
|
|
639
|
-
### Build Artifacts (TypeScript Projects)
|
|
640
|
-
|
|
641
|
-
In TypeScript projects (detected by `tsconfig.json` presence), compiled `.js` files are excluded:
|
|
642
|
-
|
|
643
|
-
```
|
|
644
|
-
**/*.js **/*.jsx (when corresponding .ts/.tsx exists)
|
|
645
|
-
```
|
|
646
|
-
|
|
647
|
-
**Why:** In TS projects, `.js` files are build artifacts. Analyzing them duplicates every issue (once in source `.ts`, once in compiled `.js`).
|
|
648
|
-
|
|
649
|
-
**Note:** In pure JavaScript projects (no `tsconfig.json`), `.js` files are **included** as they are the source files.
|
|
650
|
-
|
|
651
|
-
### Excluded Directories
|
|
652
|
-
|
|
653
|
-
| Directory | Reason |
|
|
654
|
-
|-----------|--------|
|
|
655
|
-
| `node_modules/` | Third-party dependencies |
|
|
656
|
-
| `.git/` | Version control metadata |
|
|
657
|
-
| `dist/`, `build/` | Build outputs |
|
|
658
|
-
| `.pi-lens/`, `.pi/` | pi agent internal files |
|
|
659
|
-
| `.next/`, `.ruff_cache/` | Framework/build caches |
|
|
660
|
-
| `coverage/` | Test coverage reports |
|
|
661
|
-
|
|
662
|
-
### Per-Runner Exclusion Summary
|
|
663
|
-
|
|
664
|
-
| Runner | Test Files | Build Artifacts | Directories |
|
|
665
|
-
|--------|-----------|-----------------|-------------|
|
|
666
|
-
| **dispatch runners** | ✅ `skipTestFiles` | ✅ `.js` excluded in TS | ✅ `EXCLUDED_DIRS` |
|
|
667
|
-
| **booboo /lens-booboo** | ✅ `shouldIncludeFile()` | ✅ `isTsProject` check | ✅ `EXCLUDED_DIRS` |
|
|
668
|
-
| **Secrets scan** | ❌ No exclusion (security) | ❌ No exclusion | ✅ Dirs excluded |
|
|
669
|
-
|
|
670
|
-
---
|
|
671
|
-
|
|
672
|
-
## Caching Architecture
|
|
673
|
-
|
|
674
|
-
pi-lens uses a multi-layer caching strategy to avoid redundant work:
|
|
675
|
-
|
|
676
|
-
### 1. Tool Availability Cache
|
|
677
|
-
|
|
678
|
-
**Location:** `clients/tool-availability.ts`
|
|
679
|
-
|
|
680
|
-
```
|
|
681
|
-
┌─────────────────────────────────────────┐
|
|
682
|
-
│ TOOL AVAILABILITY CACHE │
|
|
683
|
-
│ Map<toolName, {available, version}> │
|
|
684
|
-
│ • Persisted for session lifetime │
|
|
685
|
-
│ • Refreshed on extension restart │
|
|
686
|
-
└─────────────────────────────────────────┘
|
|
687
|
-
```
|
|
688
|
-
|
|
689
|
-
Avoids repeated `which`/`where` calls to check if `biome`, `ruff`, `pyright`, etc. are installed.
|
|
690
|
-
|
|
691
|
-
### 2. Dispatch Baselines (Delta Mode)
|
|
692
|
-
|
|
693
|
-
**Location:** `clients/dispatch/dispatcher.ts`
|
|
694
|
-
|
|
695
|
-
```
|
|
696
|
-
┌─────────────────────────────────────────┐
|
|
697
|
-
│ DISPATCH BASELINES │
|
|
698
|
-
│ Map<filePath, Diagnostic[]> │
|
|
699
|
-
│ • Cleared at turn start │
|
|
700
|
-
│ • Updated after each runner execution │
|
|
701
|
-
│ • Filters: only NEW issues shown │
|
|
702
|
-
└─────────────────────────────────────────┘
|
|
703
|
-
```
|
|
704
|
-
|
|
705
|
-
Delta mode tracking: first edit shows all issues, subsequent edits only show issues that weren't there before.
|
|
706
|
-
|
|
707
|
-
### 3. Client-Level Caches
|
|
708
|
-
|
|
709
|
-
| Client | Cache | TTL | Purpose |
|
|
710
|
-
|--------|-------|-----|---------|
|
|
711
|
-
| **Knip** | `clients/cache-manager.ts` | 5 min | Dead code analysis (slow) |
|
|
712
|
-
| **jscpd** | `clients/cache-manager.ts` | 5 min | Duplicate detection (slow) |
|
|
713
|
-
| **Type Coverage** | In-memory | Session | `any` type percentage |
|
|
714
|
-
| **Complexity** | In-memory | File-level | MI, cognitive complexity per file |
|
|
715
|
-
|
|
716
|
-
### 4. Session Turn State
|
|
717
|
-
|
|
718
|
-
**Location:** `clients/cache-manager.ts`
|
|
719
|
-
|
|
720
|
-
```
|
|
721
|
-
┌─────────────────────────────────────────┐
|
|
722
|
-
│ TURN STATE TRACKING │
|
|
723
|
-
│ • Modified files this turn │
|
|
724
|
-
│ • Modified line ranges per file │
|
|
725
|
-
│ • Import changes detected │
|
|
726
|
-
│ • Turn cycle counter (max 10) │
|
|
727
|
-
└─────────────────────────────────────────┘
|
|
728
|
-
```
|
|
729
|
-
|
|
730
|
-
Tracks which files were edited in the current agent turn for:
|
|
731
|
-
- jscpd: Only re-scan modified files
|
|
732
|
-
- Madge: Only check deps if imports changed
|
|
733
|
-
- Cycle detection: Prevents infinite fix loops
|
|
734
|
-
|
|
735
|
-
### 5. Runner Internal Caches
|
|
736
|
-
|
|
737
|
-
| Runner | Cache | Notes |
|
|
738
|
-
|--------|-------|-------|
|
|
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 |
|
|
741
|
-
| `biome` | Tool availability | Checked once, cached |
|
|
742
|
-
| `pyright` | Command path | Venv lookup cached |
|
|
743
|
-
| `ruff` | Command path | Venv lookup cached |
|
|
492
|
+
The LSP walks up from edited files to find `tsconfig.json`, using its `compilerOptions` (paths, strict settings, etc.). Falls back to sensible defaults if not found.
|
|
744
493
|
|
|
745
494
|
---
|
|
746
495
|
|
|
@@ -748,49 +497,17 @@ Tracks which files were edited in the current agent turn for:
|
|
|
748
497
|
|
|
749
498
|
```
|
|
750
499
|
pi-lens/
|
|
751
|
-
├── clients/
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
│ │ └── rule-cache.ts # Disk-backed cache with mtime invalidation
|
|
758
|
-
│ ├── dispatch/ # Dispatcher and runners
|
|
759
|
-
│ │ ├── dispatcher.ts
|
|
760
|
-
│ │ └── runners/ # Individual runners
|
|
761
|
-
│ │ ├── ast-grep-napi.ts # Security rules inline, warnings in booboo
|
|
762
|
-
│ │ ├── python-slop.ts # Python slop detection
|
|
763
|
-
│ │ ├── ts-lsp.ts # TS type checking
|
|
764
|
-
│ │ ├── biome.ts
|
|
765
|
-
│ │ ├── ruff.ts
|
|
766
|
-
│ │ ├── pyright.ts
|
|
767
|
-
│ │ ├── go-vet.ts
|
|
768
|
-
│ │ └── rust-clippy.ts
|
|
769
|
-
│ ├── lsp/ # LSP client system (Phase 3)
|
|
770
|
-
│ │ ├── client.ts
|
|
771
|
-
│ │ ├── server.ts # 31 LSP server definitions
|
|
772
|
-
│ │ ├── language.ts
|
|
773
|
-
│ │ ├── launch.ts
|
|
774
|
-
│ │ └── config.ts # Custom LSP configuration
|
|
775
|
-
│ ├── installer/ # Auto-installation (Phase 4)
|
|
776
|
-
│ │ └── index.ts
|
|
777
|
-
│ ├── services/ # Effect-TS services (Phase 2)
|
|
778
|
-
│ │ ├── runner-service.ts
|
|
779
|
-
│ │ └── effect-integration.ts
|
|
780
|
-
│ ├── complexity-client.ts
|
|
781
|
-
│ ├── type-safety-client.ts
|
|
782
|
-
│ └── secrets-scanner.ts
|
|
783
|
-
├── commands/ # pi commands
|
|
784
|
-
│ ├── booboo.ts
|
|
785
|
-
│ └── fix-simplified.ts
|
|
786
|
-
├── docs/ # Documentation
|
|
787
|
-
│ └── LSP_CONFIG.md # LSP configuration guide
|
|
788
|
-
├── rules/ # AST-grep rules
|
|
789
|
-
│ └── ast-grep-rules/ # General structural rules
|
|
790
|
-
├── index.ts # Main entry point
|
|
500
|
+
├── clients/ # Lint tools, LSP clients, formatters
|
|
501
|
+
├── commands/ # /lens-booboo, /lens-format commands
|
|
502
|
+
├── docs/ # Documentation
|
|
503
|
+
├── rules/ # AST-grep rules
|
|
504
|
+
├── skills/ # Built-in pi skills
|
|
505
|
+
├── index.ts # Main extension entry point
|
|
791
506
|
└── package.json
|
|
792
507
|
```
|
|
793
508
|
|
|
509
|
+
See source for detailed structure.
|
|
510
|
+
|
|
794
511
|
---
|
|
795
512
|
|
|
796
513
|
## Skills
|
|
@@ -862,7 +579,7 @@ See [CHANGELOG.md](CHANGELOG.md) for full history.
|
|
|
862
579
|
|
|
863
580
|
- **Tree-sitter Query Cache:** Compiled query cache with mtime-based invalidation — 10× faster structural analysis startup
|
|
864
581
|
- **LSP Support:** 31 Language Server Protocol clients (4 core auto-installed, others via npx or manual)
|
|
865
|
-
- **NAPI Runner:** 100x faster TypeScript/JavaScript structural analysis (~9ms vs ~1200ms) —
|
|
582
|
+
- **NAPI Runner:** 100x faster TypeScript/JavaScript structural analysis (~9ms vs ~1200ms) — security rules fire inline
|
|
866
583
|
- **Slop Detection:** 33+ TypeScript and 40+ Python patterns for AI-generated code quality issues
|
|
867
584
|
|
|
868
585
|
---
|
package/clients/biome-client.js
CHANGED
|
@@ -73,6 +73,34 @@ export class BiomeClient {
|
|
|
73
73
|
}
|
|
74
74
|
return this.biomeAvailable;
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Ensure Biome is available, auto-installing if necessary.
|
|
78
|
+
* Prefer this over isAvailable() for auto-install behavior.
|
|
79
|
+
*/
|
|
80
|
+
async ensureAvailable() {
|
|
81
|
+
if (this.biomeAvailable !== null)
|
|
82
|
+
return this.biomeAvailable;
|
|
83
|
+
// Check if already available
|
|
84
|
+
const result = this.spawnBiome(["--version"], 10000);
|
|
85
|
+
if (!result.error && result.status === 0) {
|
|
86
|
+
this.biomeAvailable = true;
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
// Auto-install via pi-lens installer
|
|
90
|
+
this.log("Biome not found, attempting auto-install...");
|
|
91
|
+
const { ensureTool } = await import("./installer/index.js");
|
|
92
|
+
const installedPath = await ensureTool("biome");
|
|
93
|
+
if (installedPath) {
|
|
94
|
+
this.log(`Biome auto-installed: ${installedPath}`);
|
|
95
|
+
// Set the installed path as local binary to avoid npx overhead
|
|
96
|
+
this.localBinaryPath = installedPath;
|
|
97
|
+
this.biomeAvailable = true;
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
this.log("Biome auto-install failed");
|
|
101
|
+
this.biomeAvailable = false;
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
76
104
|
/**
|
|
77
105
|
* Check if a file is supported by Biome
|
|
78
106
|
*/
|
package/clients/biome-client.ts
CHANGED
|
@@ -110,6 +110,38 @@ export class BiomeClient {
|
|
|
110
110
|
return this.biomeAvailable;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Ensure Biome is available, auto-installing if necessary.
|
|
115
|
+
* Prefer this over isAvailable() for auto-install behavior.
|
|
116
|
+
*/
|
|
117
|
+
async ensureAvailable(): Promise<boolean> {
|
|
118
|
+
if (this.biomeAvailable !== null) return this.biomeAvailable;
|
|
119
|
+
|
|
120
|
+
// Check if already available
|
|
121
|
+
const result = this.spawnBiome(["--version"], 10000);
|
|
122
|
+
if (!result.error && result.status === 0) {
|
|
123
|
+
this.biomeAvailable = true;
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Auto-install via pi-lens installer
|
|
128
|
+
this.log("Biome not found, attempting auto-install...");
|
|
129
|
+
const { ensureTool } = await import("./installer/index.js");
|
|
130
|
+
const installedPath = await ensureTool("biome");
|
|
131
|
+
|
|
132
|
+
if (installedPath) {
|
|
133
|
+
this.log(`Biome auto-installed: ${installedPath}`);
|
|
134
|
+
// Set the installed path as local binary to avoid npx overhead
|
|
135
|
+
this.localBinaryPath = installedPath;
|
|
136
|
+
this.biomeAvailable = true;
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
this.log("Biome auto-install failed");
|
|
141
|
+
this.biomeAvailable = false;
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
113
145
|
/**
|
|
114
146
|
* Check if a file is supported by Biome
|
|
115
147
|
*/
|
|
@@ -31,8 +31,7 @@ const biomeRunner = {
|
|
|
31
31
|
// IMPORTANT: Never use --write in dispatch runner to prevent infinite loops.
|
|
32
32
|
// Writing to the file would trigger another tool_result event, which would
|
|
33
33
|
// call dispatchLint again, creating a feedback loop.
|
|
34
|
-
//
|
|
35
|
-
// the write/edit tools directly.
|
|
34
|
+
// Auto-format handles formatting on write; this runner only checks.
|
|
36
35
|
const args = useNpx
|
|
37
36
|
? ["biome", "check", ctx.filePath]
|
|
38
37
|
: ["check", ctx.filePath];
|
|
@@ -40,8 +40,7 @@ const biomeRunner: RunnerDefinition = {
|
|
|
40
40
|
// IMPORTANT: Never use --write in dispatch runner to prevent infinite loops.
|
|
41
41
|
// Writing to the file would trigger another tool_result event, which would
|
|
42
42
|
// call dispatchLint again, creating a feedback loop.
|
|
43
|
-
//
|
|
44
|
-
// the write/edit tools directly.
|
|
43
|
+
// Auto-format handles formatting on write; this runner only checks.
|
|
45
44
|
const args = useNpx
|
|
46
45
|
? ["biome", "check", ctx.filePath]
|
|
47
46
|
: ["check", ctx.filePath];
|
|
@@ -113,27 +113,6 @@ const TOOLS = [
|
|
|
113
113
|
packageName: "knip",
|
|
114
114
|
binaryName: "knip",
|
|
115
115
|
},
|
|
116
|
-
// GitHub release LSP servers
|
|
117
|
-
{
|
|
118
|
-
id: "clangd",
|
|
119
|
-
name: "clangd",
|
|
120
|
-
checkCommand: "clangd",
|
|
121
|
-
checkArgs: ["--version"],
|
|
122
|
-
installStrategy: "github",
|
|
123
|
-
binaryName: process.platform === "win32" ? "clangd.exe" : "clangd",
|
|
124
|
-
githubRepo: "clangd/clangd",
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
id: "lua-language-server",
|
|
128
|
-
name: "Lua Language Server",
|
|
129
|
-
checkCommand: "lua-language-server",
|
|
130
|
-
checkArgs: ["--version"],
|
|
131
|
-
installStrategy: "github",
|
|
132
|
-
binaryName: process.platform === "win32"
|
|
133
|
-
? "bin/lua-language-server.exe"
|
|
134
|
-
: "bin/lua-language-server",
|
|
135
|
-
githubRepo: "LuaLS/lua-language-server",
|
|
136
|
-
},
|
|
137
116
|
];
|
|
138
117
|
// --- Check Functions ---
|
|
139
118
|
/**
|
|
@@ -131,28 +131,6 @@ const TOOLS: ToolDefinition[] = [
|
|
|
131
131
|
packageName: "knip",
|
|
132
132
|
binaryName: "knip",
|
|
133
133
|
},
|
|
134
|
-
// GitHub release LSP servers
|
|
135
|
-
{
|
|
136
|
-
id: "clangd",
|
|
137
|
-
name: "clangd",
|
|
138
|
-
checkCommand: "clangd",
|
|
139
|
-
checkArgs: ["--version"],
|
|
140
|
-
installStrategy: "github",
|
|
141
|
-
binaryName: process.platform === "win32" ? "clangd.exe" : "clangd",
|
|
142
|
-
githubRepo: "clangd/clangd",
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
id: "lua-language-server",
|
|
146
|
-
name: "Lua Language Server",
|
|
147
|
-
checkCommand: "lua-language-server",
|
|
148
|
-
checkArgs: ["--version"],
|
|
149
|
-
installStrategy: "github",
|
|
150
|
-
binaryName:
|
|
151
|
-
process.platform === "win32"
|
|
152
|
-
? "bin/lua-language-server.exe"
|
|
153
|
-
: "bin/lua-language-server",
|
|
154
|
-
githubRepo: "LuaLS/lua-language-server",
|
|
155
|
-
},
|
|
156
134
|
];
|
|
157
135
|
|
|
158
136
|
// --- Check Functions ---
|
package/clients/lsp/server.js
CHANGED
|
@@ -195,11 +195,74 @@ export const PythonServer = {
|
|
|
195
195
|
"poetry.lock",
|
|
196
196
|
]),
|
|
197
197
|
async spawn(root) {
|
|
198
|
+
const path = await import("node:path");
|
|
199
|
+
const fs = await import("node:fs/promises");
|
|
198
200
|
const env = await getToolEnvironment();
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
// Strategy 1: Find pyright - prefer local project version
|
|
202
|
+
let pyrightPath;
|
|
203
|
+
const localPyright = path.join(root, "node_modules", ".bin", "pyright");
|
|
204
|
+
const localPyrightCmd = path.join(root, "node_modules", ".bin", "pyright.cmd");
|
|
205
|
+
// Check for local version first (Windows .cmd first, then Unix)
|
|
206
|
+
for (const checkPath of [localPyrightCmd, localPyright]) {
|
|
207
|
+
try {
|
|
208
|
+
await fs.access(checkPath);
|
|
209
|
+
pyrightPath = checkPath;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
/* not found */
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Strategy 2: Fall back to auto-installed version
|
|
217
|
+
if (!pyrightPath) {
|
|
218
|
+
pyrightPath = await ensureTool("pyright");
|
|
219
|
+
if (!pyrightPath) {
|
|
220
|
+
console.error("[lsp] pyright not found, falling back to npx");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Strategy 3: Use found pyright to derive pyright-langserver path
|
|
224
|
+
let langserverPath;
|
|
225
|
+
if (pyrightPath) {
|
|
226
|
+
// Derive langserver from pyright binary location
|
|
227
|
+
// Both are in the same .bin directory
|
|
228
|
+
const binDir = path.dirname(pyrightPath);
|
|
229
|
+
const isWindows = process.platform === "win32";
|
|
230
|
+
const candidates = isWindows
|
|
231
|
+
? [
|
|
232
|
+
path.join(binDir, "pyright-langserver.cmd"),
|
|
233
|
+
path.join(binDir, "pyright-langserver.ps1"),
|
|
234
|
+
path.join(binDir, "pyright-langserver"),
|
|
235
|
+
]
|
|
236
|
+
: [path.join(binDir, "pyright-langserver")];
|
|
237
|
+
for (const candidate of candidates) {
|
|
238
|
+
try {
|
|
239
|
+
await fs.access(candidate);
|
|
240
|
+
langserverPath = candidate;
|
|
241
|
+
console.error(`[lsp] Found pyright-langserver: ${candidate}`);
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
/* not found */
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Spawn the LSP server
|
|
250
|
+
let proc;
|
|
251
|
+
if (langserverPath) {
|
|
252
|
+
// Use resolved langserver path
|
|
253
|
+
proc = await launchLSP(langserverPath, ["--stdio"], {
|
|
254
|
+
cwd: root,
|
|
255
|
+
env,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
// Fallback to npx for auto-download
|
|
260
|
+
console.error("[lsp] Falling back to npx for pyright-langserver");
|
|
261
|
+
proc = await launchViaPackageManager("pyright-langserver", ["--stdio"], {
|
|
262
|
+
cwd: root,
|
|
263
|
+
env,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
203
266
|
// Detect virtual environment
|
|
204
267
|
const initialization = {};
|
|
205
268
|
const venvPaths = [
|
|
@@ -214,7 +277,7 @@ export const PythonServer = {
|
|
|
214
277
|
const pythonPath = process.platform === "win32"
|
|
215
278
|
? path.join(venv, "Scripts", "python.exe")
|
|
216
279
|
: path.join(venv, "bin", "python");
|
|
217
|
-
await
|
|
280
|
+
await fs.access(pythonPath);
|
|
218
281
|
// Pyright expects pythonPath at top level, not nested
|
|
219
282
|
initialization.pythonPath = pythonPath;
|
|
220
283
|
break;
|
package/clients/lsp/server.ts
CHANGED
|
@@ -274,15 +274,83 @@ export const PythonServer: LSPServerInfo = {
|
|
|
274
274
|
"poetry.lock",
|
|
275
275
|
]),
|
|
276
276
|
async spawn(root) {
|
|
277
|
+
const path = await import("node:path");
|
|
278
|
+
const fs = await import("node:fs/promises");
|
|
277
279
|
const env = await getToolEnvironment();
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
280
|
+
|
|
281
|
+
// Strategy 1: Find pyright - prefer local project version
|
|
282
|
+
let pyrightPath: string | undefined;
|
|
283
|
+
const localPyright = path.join(root, "node_modules", ".bin", "pyright");
|
|
284
|
+
const localPyrightCmd = path.join(
|
|
285
|
+
root,
|
|
286
|
+
"node_modules",
|
|
287
|
+
".bin",
|
|
288
|
+
"pyright.cmd",
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
// Check for local version first (Windows .cmd first, then Unix)
|
|
292
|
+
for (const checkPath of [localPyrightCmd, localPyright]) {
|
|
293
|
+
try {
|
|
294
|
+
await fs.access(checkPath);
|
|
295
|
+
pyrightPath = checkPath;
|
|
296
|
+
break;
|
|
297
|
+
} catch {
|
|
298
|
+
/* not found */
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Strategy 2: Fall back to auto-installed version
|
|
303
|
+
if (!pyrightPath) {
|
|
304
|
+
pyrightPath = await ensureTool("pyright");
|
|
305
|
+
if (!pyrightPath) {
|
|
306
|
+
console.error("[lsp] pyright not found, falling back to npx");
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Strategy 3: Use found pyright to derive pyright-langserver path
|
|
311
|
+
let langserverPath: string | undefined;
|
|
312
|
+
if (pyrightPath) {
|
|
313
|
+
// Derive langserver from pyright binary location
|
|
314
|
+
// Both are in the same .bin directory
|
|
315
|
+
const binDir = path.dirname(pyrightPath);
|
|
316
|
+
const isWindows = process.platform === "win32";
|
|
317
|
+
|
|
318
|
+
const candidates = isWindows
|
|
319
|
+
? [
|
|
320
|
+
path.join(binDir, "pyright-langserver.cmd"),
|
|
321
|
+
path.join(binDir, "pyright-langserver.ps1"),
|
|
322
|
+
path.join(binDir, "pyright-langserver"),
|
|
323
|
+
]
|
|
324
|
+
: [path.join(binDir, "pyright-langserver")];
|
|
325
|
+
|
|
326
|
+
for (const candidate of candidates) {
|
|
327
|
+
try {
|
|
328
|
+
await fs.access(candidate);
|
|
329
|
+
langserverPath = candidate;
|
|
330
|
+
console.error(`[lsp] Found pyright-langserver: ${candidate}`);
|
|
331
|
+
break;
|
|
332
|
+
} catch {
|
|
333
|
+
/* not found */
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Spawn the LSP server
|
|
339
|
+
let proc;
|
|
340
|
+
if (langserverPath) {
|
|
341
|
+
// Use resolved langserver path
|
|
342
|
+
proc = await launchLSP(langserverPath, ["--stdio"], {
|
|
282
343
|
cwd: root,
|
|
283
344
|
env,
|
|
284
|
-
}
|
|
285
|
-
|
|
345
|
+
});
|
|
346
|
+
} else {
|
|
347
|
+
// Fallback to npx for auto-download
|
|
348
|
+
console.error("[lsp] Falling back to npx for pyright-langserver");
|
|
349
|
+
proc = await launchViaPackageManager("pyright-langserver", ["--stdio"], {
|
|
350
|
+
cwd: root,
|
|
351
|
+
env,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
286
354
|
|
|
287
355
|
// Detect virtual environment
|
|
288
356
|
const initialization: Record<string, unknown> = {};
|
|
@@ -300,7 +368,7 @@ export const PythonServer: LSPServerInfo = {
|
|
|
300
368
|
? path.join(venv, "Scripts", "python.exe")
|
|
301
369
|
: path.join(venv, "bin", "python");
|
|
302
370
|
|
|
303
|
-
await
|
|
371
|
+
await fs.access(pythonPath);
|
|
304
372
|
// Pyright expects pythonPath at top level, not nested
|
|
305
373
|
initialization.pythonPath = pythonPath;
|
|
306
374
|
break;
|
package/clients/pipeline.js
CHANGED
|
@@ -135,7 +135,7 @@ export async function runPipeline(ctx, deps) {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
if (!noAutofixBiome &&
|
|
138
|
-
biomeClient.
|
|
138
|
+
(await biomeClient.ensureAvailable()) &&
|
|
139
139
|
biomeClient.isSupportedFile(filePath)) {
|
|
140
140
|
const result = biomeClient.fixFile(filePath);
|
|
141
141
|
if (result.success && result.fixed > 0) {
|
package/clients/pipeline.ts
CHANGED
package/index.ts
CHANGED
|
@@ -374,59 +374,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
374
374
|
},
|
|
375
375
|
});
|
|
376
376
|
|
|
377
|
-
pi.registerCommand("lens-format", {
|
|
378
|
-
description:
|
|
379
|
-
"Apply Biome formatting to files. Usage: /lens-format [file-path] or /lens-format --all",
|
|
380
|
-
handler: async (args, ctx) => {
|
|
381
|
-
if (!biomeClient.isAvailable()) {
|
|
382
|
-
ctx.ui.notify(
|
|
383
|
-
"Biome not installed. Run: npm install -D @biomejs/biome",
|
|
384
|
-
"error",
|
|
385
|
-
);
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const arg = args.trim();
|
|
390
|
-
|
|
391
|
-
if (!arg || arg === "--all") {
|
|
392
|
-
ctx.ui.notify("🔍 Formatting all files...", "info");
|
|
393
|
-
|
|
394
|
-
let formatted = 0;
|
|
395
|
-
let skipped = 0;
|
|
396
|
-
|
|
397
|
-
const targetPath = ctx.cwd || process.cwd();
|
|
398
|
-
const isTsProject = nodeFs.existsSync(
|
|
399
|
-
path.join(targetPath, "tsconfig.json"),
|
|
400
|
-
);
|
|
401
|
-
const files = getSourceFiles(targetPath, isTsProject);
|
|
402
|
-
|
|
403
|
-
for (const fullPath of files) {
|
|
404
|
-
if (/\.(ts|tsx|js|jsx|json|css)$/.test(fullPath)) {
|
|
405
|
-
const result = biomeClient.formatFile(fullPath);
|
|
406
|
-
if (result.changed) formatted++;
|
|
407
|
-
else if (result.success) skipped++;
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
ctx.ui.notify(
|
|
411
|
-
`✓ Formatted ${formatted} file(s), ${skipped} already clean`,
|
|
412
|
-
"info",
|
|
413
|
-
);
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
const filePath = path.resolve(arg);
|
|
418
|
-
const result = biomeClient.formatFile(filePath);
|
|
419
|
-
|
|
420
|
-
if (result.success && result.changed) {
|
|
421
|
-
ctx.ui.notify(`✓ Formatted ${path.basename(filePath)}`, "info");
|
|
422
|
-
} else if (result.success) {
|
|
423
|
-
ctx.ui.notify(`✓ ${path.basename(filePath)} already clean`, "info");
|
|
424
|
-
} else {
|
|
425
|
-
ctx.ui.notify(`⚠️ Format failed: ${result.error}`, "error");
|
|
426
|
-
}
|
|
427
|
-
},
|
|
428
|
-
});
|
|
429
|
-
|
|
430
377
|
// --- Tools ---
|
|
431
378
|
|
|
432
379
|
const LANGUAGES = [
|
|
@@ -933,7 +880,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
933
880
|
// Log available tools
|
|
934
881
|
const tools: string[] = [];
|
|
935
882
|
tools.push("TypeScript LSP"); // Always available
|
|
936
|
-
if (biomeClient.
|
|
883
|
+
if (await biomeClient.ensureAvailable()) tools.push("Biome");
|
|
937
884
|
if (astGrepClient.isAvailable()) tools.push("ast-grep");
|
|
938
885
|
if (ruffClient.isAvailable()) tools.push("Ruff");
|
|
939
886
|
if (knipClient.isAvailable()) tools.push("Knip");
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-lens",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/apmantza/pi-lens.git"
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "vitest";
|
|
2
|
-
import { convertDiagnosticsToTDREntries, } from "./metrics-client.js";
|
|
3
|
-
describe("TDR conversion", () => {
|
|
4
|
-
test("converts type errors to TDR entries", () => {
|
|
5
|
-
const diagnostics = [
|
|
6
|
-
{
|
|
7
|
-
id: "ts-lsp:TS2345:10",
|
|
8
|
-
message: "Argument of type 'string' is not assignable",
|
|
9
|
-
filePath: "/test/file.ts",
|
|
10
|
-
line: 10,
|
|
11
|
-
column: 5,
|
|
12
|
-
severity: "error",
|
|
13
|
-
semantic: "blocking",
|
|
14
|
-
tool: "ts-lsp",
|
|
15
|
-
rule: "TS2345",
|
|
16
|
-
tdrCategory: "type_errors",
|
|
17
|
-
},
|
|
18
|
-
];
|
|
19
|
-
const entries = convertDiagnosticsToTDREntries(diagnostics);
|
|
20
|
-
expect(entries).toHaveLength(1);
|
|
21
|
-
expect(entries[0]).toEqual({
|
|
22
|
-
category: "type_errors",
|
|
23
|
-
count: 1,
|
|
24
|
-
severity: "error",
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
test("groups multiple diagnostics by category", () => {
|
|
28
|
-
const diagnostics = [
|
|
29
|
-
{
|
|
30
|
-
id: "1",
|
|
31
|
-
message: "Type error 1",
|
|
32
|
-
filePath: "/test.ts",
|
|
33
|
-
severity: "error",
|
|
34
|
-
semantic: "blocking",
|
|
35
|
-
tool: "ts-lsp",
|
|
36
|
-
tdrCategory: "type_errors",
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
id: "2",
|
|
40
|
-
message: "Type error 2",
|
|
41
|
-
filePath: "/test.ts",
|
|
42
|
-
severity: "error",
|
|
43
|
-
semantic: "blocking",
|
|
44
|
-
tool: "ts-lsp",
|
|
45
|
-
tdrCategory: "type_errors",
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
id: "3",
|
|
49
|
-
message: "Security issue",
|
|
50
|
-
filePath: "/test.ts",
|
|
51
|
-
severity: "error",
|
|
52
|
-
semantic: "blocking",
|
|
53
|
-
tool: "ast-grep-napi",
|
|
54
|
-
tdrCategory: "security",
|
|
55
|
-
},
|
|
56
|
-
];
|
|
57
|
-
const entries = convertDiagnosticsToTDREntries(diagnostics);
|
|
58
|
-
expect(entries).toHaveLength(2);
|
|
59
|
-
expect(entries.find((e) => e.category === "type_errors")?.count).toBe(2);
|
|
60
|
-
expect(entries.find((e) => e.category === "security")?.count).toBe(1);
|
|
61
|
-
});
|
|
62
|
-
test("auto-categorizes diagnostics without tdrCategory", () => {
|
|
63
|
-
const diagnostics = [
|
|
64
|
-
{
|
|
65
|
-
id: "1",
|
|
66
|
-
message: "Unused variable",
|
|
67
|
-
filePath: "/test.ts",
|
|
68
|
-
severity: "warning",
|
|
69
|
-
semantic: "warning",
|
|
70
|
-
tool: "biome",
|
|
71
|
-
rule: "no-unused",
|
|
72
|
-
},
|
|
73
|
-
];
|
|
74
|
-
const entries = convertDiagnosticsToTDREntries(diagnostics);
|
|
75
|
-
expect(entries).toHaveLength(1);
|
|
76
|
-
expect(entries[0].category).toBe("dead_code");
|
|
77
|
-
});
|
|
78
|
-
});
|