pi-lens 3.8.28 → 3.8.29
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 +92 -0
- package/README.md +21 -3
- package/clients/architect-client.ts +2 -2
- package/clients/ast-grep-client.ts +1 -1
- package/clients/ast-grep-parser.ts +1 -1
- package/clients/ast-grep-rule-manager.ts +2 -2
- package/clients/biome-client.ts +3 -3
- package/clients/complexity-client.ts +5 -4
- package/clients/dependency-checker.ts +2 -2
- package/clients/dispatch/diagnostic-taxonomy.ts +30 -16
- package/clients/dispatch/dispatcher.ts +48 -20
- package/clients/dispatch/facts/try-catch-facts.ts +4 -3
- package/clients/dispatch/integration.ts +59 -34
- package/clients/dispatch/rules/error-swallowing.ts +1 -1
- package/clients/dispatch/rules/unsafe-boundary.ts +13 -7
- package/clients/dispatch/runners/ast-grep-napi.ts +14 -12
- package/clients/dispatch/runners/biome-check.ts +65 -31
- package/clients/dispatch/runners/eslint.ts +4 -29
- package/clients/dispatch/runners/lsp.ts +20 -22
- package/clients/dispatch/runners/pyright.ts +6 -3
- package/clients/dispatch/runners/shellcheck.ts +3 -13
- package/clients/dispatch/runners/tree-sitter.ts +144 -91
- package/clients/dispatch/runners/ts-lsp.ts +4 -6
- package/clients/dispatch/runners/utils/runner-helpers.ts +53 -11
- package/clients/dispatch/types.ts +10 -1
- package/clients/file-role.ts +74 -35
- package/clients/fix-worklog.ts +5 -2
- package/clients/formatters.ts +7 -2
- package/clients/installer/index.ts +458 -169
- package/clients/jscpd-client.ts +2 -2
- package/clients/knip-client.ts +12 -5
- package/clients/language-policy.ts +55 -32
- package/clients/language-profile.ts +38 -12
- package/clients/latency-logger.ts +1 -1
- package/clients/log-cleanup.ts +253 -0
- package/clients/lsp/client.ts +38 -3
- package/clients/lsp/index.ts +97 -61
- package/clients/lsp/interactive-install.ts +2 -2
- package/clients/lsp/launch.ts +170 -63
- package/clients/lsp/server.ts +35 -36
- package/clients/native-rust-client.ts +32 -17
- package/clients/pipeline.ts +33 -20
- package/clients/production-readiness.ts +0 -4
- package/clients/project-index.ts +11 -2
- package/clients/ruff-client.ts +1 -1
- package/clients/runtime-session.ts +42 -164
- package/clients/runtime-tool-result.ts +1 -2
- package/clients/runtime-turn.ts +51 -11
- package/clients/rust-client.ts +2 -2
- package/clients/secrets-scanner.ts +33 -2
- package/clients/sg-runner.ts +46 -22
- package/clients/tree-sitter-client.ts +17 -0
- package/clients/tree-sitter-symbol-extractor.ts +12 -3
- package/commands/booboo.ts +138 -65
- package/config/biome/core.jsonc +1 -3
- package/index.ts +502 -487
- package/package.json +1 -1
- package/rules/ast-grep-rules/rules/unchecked-sync-fs.yml +27 -22
- package/rules/tree-sitter-queries/go/go-command-injection.yml +5 -5
- package/rules/tree-sitter-queries/go/go-direct-panic.yml +2 -2
- package/rules/tree-sitter-queries/go/go-empty-if-err.yml +2 -2
- package/rules/tree-sitter-queries/go/go-ignored-call-result.yml +4 -3
- package/rules/tree-sitter-queries/go/go-insecure-random.yml +3 -3
- package/rules/tree-sitter-queries/go/go-log-fatal.yml +3 -3
- package/rules/tree-sitter-queries/go/go-path-traversal.yml +3 -3
- package/rules/tree-sitter-queries/go/go-sql-injection.yml +4 -4
- package/rules/tree-sitter-queries/go/go-weak-hash.yml +3 -3
- package/rules/tree-sitter-queries/python/python-command-injection.yml +7 -7
- package/rules/tree-sitter-queries/python/python-cross-language-method.yml +2 -2
- package/rules/tree-sitter-queries/python/python-insecure-deserialization.yml +3 -3
- package/rules/tree-sitter-queries/python/python-insecure-random.yml +3 -3
- package/rules/tree-sitter-queries/python/python-sql-injection.yml +2 -2
- package/rules/tree-sitter-queries/python/python-ssrf.yml +3 -3
- package/rules/tree-sitter-queries/python/python-subprocess-shell.yml +4 -4
- package/rules/tree-sitter-queries/python/python-weak-hash.yml +3 -3
- package/rules/tree-sitter-queries/ruby/ruby-insecure-deserialization.yml +3 -3
- package/rules/tree-sitter-queries/typescript/console-statement.yml +1 -2
- package/rules/tree-sitter-queries/typescript/deep-promise-chain.yml +5 -5
- package/rules/tree-sitter-queries/typescript/ts-command-injection.yml +6 -4
- package/rules/tree-sitter-queries/typescript/ts-detached-async-call.yml +2 -2
- package/rules/tree-sitter-queries/typescript/ts-insecure-random.yml +4 -4
- package/rules/tree-sitter-queries/typescript/ts-react-antipatterns.yml +6 -4
- package/rules/tree-sitter-queries/typescript/ts-ssrf.yml +4 -3
- package/rules/tree-sitter-queries/typescript/ts-weak-hash.yml +3 -3
- package/skills/ast-grep/SKILL.md +38 -251
- package/clients/auto-loop.ts +0 -200
- package/clients/config-validator.ts +0 -558
- package/clients/fix-scanners.ts +0 -303
- package/clients/scan-architectural-debt.ts +0 -203
- package/config/eslint/core.mjs +0 -28
- package/rules/ast-grep-rules/rules/empty-catch-js.yml +0 -48
- package/rules/ast-grep-rules/rules/empty-catch.yml +0 -48
- package/rules/ast-grep-rules/rules/getter-return-js.yml +0 -59
- package/rules/ast-grep-rules/rules/getter-return.yml +0 -59
- package/rules/ast-grep-rules/rules/in-correct-optional-input-type.yml +0 -63
- package/rules/ast-grep-rules/rules/long-method.yml +0 -15
- package/rules/ast-grep-rules/rules/missed-concurrency-js.yml +0 -25
- package/rules/ast-grep-rules/rules/missed-concurrency.yml +0 -25
- package/rules/ast-grep-rules/rules/missing-component-decorator.yml +0 -30
- package/rules/ast-grep-rules/rules/no-array-sort-without-comparator-js.yml +0 -8
- package/rules/ast-grep-rules/rules/no-array-sort-without-comparator.yml +0 -8
- package/rules/ast-grep-rules/rules/no-await-in-loop-js.yml +0 -30
- package/rules/ast-grep-rules/rules/no-await-in-loop.yml +0 -46
- package/rules/ast-grep-rules/rules/no-constructor-return-js.yml +0 -28
- package/rules/ast-grep-rules/rules/no-constructor-return.yml +0 -28
- package/rules/ast-grep-rules/rules/no-delete-operator.yml +0 -9
- package/rules/ast-grep-rules/rules/no-dupe-args-js.yml +0 -15
- package/rules/ast-grep-rules/rules/no-dupe-args.yml +0 -15
- package/rules/ast-grep-rules/rules/no-single-char-var.yml +0 -12
- package/rules/ast-grep-rules/rules/prefer-async-await-js.yml +0 -13
- package/rules/ast-grep-rules/rules/prefer-async-await.yml +0 -13
- package/rules/ast-grep-rules/rules/toctou-js.yml +0 -112
- package/rules/ast-grep-rules/rules/toctou.yml +0 -112
- package/rules/ast-grep-rules/rules/unchecked-throwing-call-ruby.yml +0 -47
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,98 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to pi-lens will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [3.8.29] - 2026-04-21
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **New diagnostic commands** — added `/lens-tools` and `/lens-health` for system visibility:
|
|
11
|
+
- `/lens-tools` — shows tool installation status: globally installed, pi-lens auto-installed, or npx fallback
|
|
12
|
+
- `/lens-health` — shows runtime health: pipeline crashes, slow runners, diagnostic stats
|
|
13
|
+
- Both provide actionable visibility into the pi-lens toolchain
|
|
14
|
+
- **Streamlined ast-grep skill** — reduced skill from 7,759 bytes to 2,313 bytes (~70% reduction):
|
|
15
|
+
- Removed verbose CLI tips and YAML rule authoring sections (agent uses tools, not CLI)
|
|
16
|
+
- Removed redundant testing documentation
|
|
17
|
+
- Kept essential: Golden Rules, Quick Reference, Common Gotchas
|
|
18
|
+
- **Configurable log cleanup** — automatic retention and rotation for `~/.pi-lens/*.log` files:
|
|
19
|
+
- Environment variable `PI_LENS_LOG_RETENTION_DAYS` (default: 7) — days to keep log files
|
|
20
|
+
- Environment variable `PI_LENS_MAX_LOG_SIZE_MB` (default: 10) — max size before rotation
|
|
21
|
+
- Runs automatically on session start, notifies when cleanup occurs
|
|
22
|
+
- Rotated backups (`.log.*`) cleaned after retention period
|
|
23
|
+
- Project-level logs (`{cwd}/.pi-lens/*`) intentionally excluded from cleanup
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- **`/lens-tools` output improved** — added explanatory note when GitHub-release tools are shown as missing: "GitHub-release tools auto-install when you open files of those languages"
|
|
27
|
+
- **Simplified agent prompts** — removed verbose prompt sections to reduce token burn:
|
|
28
|
+
- Removed startup notes about project rules count (now just logged, not shown)
|
|
29
|
+
- Removed tooling hints for missing language tools (Go/Rust/Ruby install suggestions)
|
|
30
|
+
- Removed project rules section from system prompt (no longer injects `## Project Rules` block)
|
|
31
|
+
- Updated core guidance to clarify: automated checks run on edits/writes, blocking errors shown inline must be fixed
|
|
32
|
+
- **Simplified CLI flags** — removed 16 flags to reduce surface area and cognitive load:
|
|
33
|
+
- Removed per-tool disable flags: `--no-biome`, `--no-ast-grep`, `--no-shellcheck`, `--no-madge`, `--no-oxlint`, `--no-ruff`, `--no-go`, `--no-rust`
|
|
34
|
+
- Removed per-tool autofix flags: `--no-autofix-biome`, `--no-autofix-ruff`
|
|
35
|
+
- Removed feature flags: `--lens-verbose`, `--error-debt`, `--auto-install`, `--lens-eslint-core`
|
|
36
|
+
- Removed redundant `--lens-lsp` flag (LSP is default-on; use `--no-lsp` to disable)
|
|
37
|
+
- Removed internal dead flag: `--lens-blocking-only`
|
|
38
|
+
- **Removed `--no-lsp-install` flag** — LSP servers now always auto-install when needed (no manual opt-out)
|
|
39
|
+
- New minimal flag set: `--no-lsp`, `--no-autoformat`, `--no-autofix`, `--no-tests`, `--no-delta`, `--lens-guard`
|
|
40
|
+
- **Cross-platform line ending handling** — all `.split("\n")` changed to `.split(/\r?\n/)` for Windows CRLF compatibility (11 files updated)
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
- **Biome VCS/ignore file errors eliminated** — disabled VCS integration in biome config to prevent "ignore file not found" errors:
|
|
44
|
+
- Changed `vcs.enabled: true` → `vcs.enabled: false` in `config/biome/core.jsonc`
|
|
45
|
+
- Biome was searching for `.gitignore` files that don't exist when running on arbitrary projects via pi-lens
|
|
46
|
+
- Eliminates biome:parse-error spam in logs when biome runs outside its config directory
|
|
47
|
+
- **LSP server thrashing eliminated** — added 240s idle timeout to prevent repeated LSP shutdown/startup cycles:
|
|
48
|
+
- New `scheduleLSPIdleReset()` in `runtime-turn.ts` defers server reset when no files modified
|
|
49
|
+
- Cancel pending reset when active editing resumes (avoids interrupting workflows)
|
|
50
|
+
- Eliminates ~1-2s cold-start penalty during active development sessions
|
|
51
|
+
- Debug logging added for scheduling and cancellation events
|
|
52
|
+
- **Biome check runner JSON parsing** — fixed error where biome's stderr warnings broke JSON parsing:
|
|
53
|
+
- Changed from parsing `stdout || stderr` to parsing `stdout` only
|
|
54
|
+
- Biome outputs text warnings (e.g., "couldn't find ignore file") to stderr which broke the JSON parser
|
|
55
|
+
- Fixes biome-check-json runner failing with parse errors instead of providing lint diagnostics
|
|
56
|
+
- **Auto-install verification gap** — `getToolPath()` now verifies tool binaries actually work before using them:
|
|
57
|
+
- Runs `--version` check on local npm tools (not just file existence)
|
|
58
|
+
- Detects broken/corrupted installations (e.g., wrapper exists but package missing)
|
|
59
|
+
- Triggers automatic reinstall when binary verification fails
|
|
60
|
+
- Fixes case where `@biomejs/biome` package deleted but `.cmd` wrapper remained
|
|
61
|
+
- **Error swallowing in tool availability checks** — `runtime-session.ts` now logs errors when biome/ast-grep/ruff/knip/dep/jscpd availability checks fail (was silently returning `false`)
|
|
62
|
+
- **Biome check runner reliability** — fixed path resolution and configuration issues causing "skipped" status and parse errors:
|
|
63
|
+
- Fixed biome flag: `--output-format=json` → `--reporter=json`
|
|
64
|
+
- Fixed `findBiome()` to check `~/.pi-lens/tools/` directory (was falling back to bare "biome" not in PATH)
|
|
65
|
+
- Fixed `findBiome()` to return `{cmd, argsPrefix}` object for proper npx fallback with `@biomejs/biome` prefix
|
|
66
|
+
- Added `vcs.root: "."` to `config/biome/core.jsonc` to respect project `.gitignore`
|
|
67
|
+
- **LSP error messaging** — improved error messages for Windows .cmd shim failures to distinguish "npm .cmd shim failed (underlying binary not installed)" from "may be missing or corrupted"
|
|
68
|
+
- **Windows installer improvements** — multiple fixes for Windows tool discovery and LSP stability:
|
|
69
|
+
- Prefer `.cmd` over extensionless in local TOOLS_DIR path lookup on Windows
|
|
70
|
+
- Bypass PS1 hangs in LSP initialization with hard-kill on timeout
|
|
71
|
+
- Remove `.ps1` from pyright managed candidates and ast-grep discovery on Windows
|
|
72
|
+
- Use `SYSTEMDRIVE` env var instead of hardcoded `C:` for cargo fallback path
|
|
73
|
+
- **Rust LSP** — exponential backoff circuit breaker for failing LSP connections
|
|
74
|
+
- **Installer reliability** — remove `console.error` verbosity, route all events to `sessionstart.log`
|
|
75
|
+
- **Circular dependencies** — fixed circular dependencies identified in code review
|
|
76
|
+
- **Knip race condition** — fixed race condition in knip tool discovery
|
|
77
|
+
- **Non-blocking tool availability checks** — changed all `ensureAvailable()` methods to use async `safeSpawnAsync` instead of sync `safeSpawn`, completing the startup unblocking work:
|
|
78
|
+
- `ruff-client.ts`, `biome-client.ts`, `sg-runner.ts` (first batch)
|
|
79
|
+
- `knip-client.ts`, `dependency-checker.ts`, `jscpd-client.ts` (second batch)
|
|
80
|
+
- `sg-runner.ts` — added missing `safeSpawnAsync` import
|
|
81
|
+
- **Secrets scanner false positives** — fixed incorrect flagging of environment variable name references (e.g., `"FIREWORKS_API_KEY"`, `"AWS_ACCESS_KEY_ID"`) as hardcoded secrets:
|
|
82
|
+
- Added word boundaries to `hardcoded-secret` regex pattern
|
|
83
|
+
- Added `looksLikeEnvVarName()` filter to skip UPPERCASE_SNAKE_CASE values
|
|
84
|
+
- Prevents false positives when env var names are used as placeholder strings
|
|
85
|
+
|
|
86
|
+
### Changed
|
|
87
|
+
- **Biome check performance** — reduced lint latency from ~1.4s to ~100ms per file (92% improvement):
|
|
88
|
+
- Removed redundant `--version` pre-check spawn (~200ms saved)
|
|
89
|
+
- Switched from `biome check` to `biome lint` command (skip format validation)
|
|
90
|
+
- Added binary path caching per cwd to avoid repeated fs checks
|
|
91
|
+
- Benchmark: 107ms average vs 1400ms baseline
|
|
92
|
+
- **Tree-sitter performance** — reduced structural analysis latency by 30-50%:
|
|
93
|
+
- Execute queries in parallel with concurrency limit of 6 (was sequential)
|
|
94
|
+
- Skip entity snapshot extraction for changes under 5 lines (~500-800ms saved for trivial edits)
|
|
95
|
+
- Reduces tree-sitter latency from ~3s to ~1-2s for typical files
|
|
96
|
+
|
|
5
97
|
## [3.8.28] - 2026-04-19
|
|
6
98
|
|
|
7
99
|
### Fixed
|
package/README.md
CHANGED
|
@@ -42,6 +42,7 @@ At `turn_end`, pi-lens:
|
|
|
42
42
|
- persists turn findings for next context injection
|
|
43
43
|
- updates debt/diagnostic tracking and cleans transient state
|
|
44
44
|
- renders a review-graph impact cascade showing affected files and diagnostic propagation
|
|
45
|
+
- manages LSP server lifecycle with a 240s idle timeout (resets when editing resumes)
|
|
45
46
|
|
|
46
47
|
## Install
|
|
47
48
|
|
|
@@ -62,17 +63,22 @@ pi install git:github.com/apmantza/pi-lens
|
|
|
62
63
|
pi
|
|
63
64
|
|
|
64
65
|
# Optional switches
|
|
65
|
-
pi --no-lsp # Disable unified LSP
|
|
66
|
+
pi --no-lsp # Disable unified LSP diagnostics
|
|
66
67
|
pi --no-autoformat # Skip auto-formatting
|
|
67
68
|
pi --no-autofix # Skip auto-fix (Biome, Ruff, ESLint, stylelint, sqlfluff, RuboCop)
|
|
68
69
|
pi --no-tests # Skip test runner
|
|
69
|
-
pi --no-
|
|
70
|
+
pi --no-delta # Disable delta mode (show all diagnostics, not just new ones)
|
|
71
|
+
pi --lens-guard # Block git commit/push when unresolved blockers exist (experimental)
|
|
70
72
|
```
|
|
71
73
|
|
|
74
|
+
LSP is enabled by default. Use `--no-lsp` to use language-specific fallbacks (ts-lsp, pyright) instead of the unified LSP service.
|
|
75
|
+
|
|
72
76
|
## Key Commands
|
|
73
77
|
|
|
74
78
|
- `/lens-booboo` — full quality report for current project state
|
|
75
79
|
- `/lens-health` — runtime health, latency, and diagnostic telemetry
|
|
80
|
+
- `/lens-tools` — tool installation status: globally installed, auto-installed, or npx fallback
|
|
81
|
+
- `/lens-tdi` — Technical Debt Index (TDI) and project health trend
|
|
76
82
|
|
|
77
83
|
## Language Coverage
|
|
78
84
|
|
|
@@ -147,6 +153,8 @@ pi-lens builds a review graph (`file → symbol → dependency`) during session
|
|
|
147
153
|
|
|
148
154
|
pi-lens includes **37 language server definitions**. LSP is **enabled by default** (`--lsp` or no flag). Servers are auto-discovered from PATH, project `node_modules`, and managed installs. When a server is not installed, pi-lens offers an interactive install prompt.
|
|
149
155
|
|
|
156
|
+
**LSP Idle Management:** LSP servers shut down after 240 seconds of inactivity (no files modified) to free resources. The timer resets when you resume editing, preventing cold-start penalties during active development.
|
|
157
|
+
|
|
150
158
|
LSP servers for: TypeScript, Deno, Python (pyright + pylsp), Go, Rust, Ruby (ruby-lsp + solargraph), PHP, C# (omnisharp), F#, Java, Kotlin, Swift, Dart, Lua, C/C++, Zig, Haskell, Elixir, Gleam, OCaml, Clojure, Terraform, Nix, Bash, Docker, YAML, JSON, HTML, TOML, Prisma, Vue, Svelte, ESLint, CSS.
|
|
151
159
|
|
|
152
160
|
## Runners
|
|
@@ -251,4 +259,14 @@ Additional language servers (gopls, ruby-lsp, solargraph, etc.) are auto-detecte
|
|
|
251
259
|
|
|
252
260
|
- Not every auto-install runs in every project: gate type decides when install is attempted.
|
|
253
261
|
- Rule packs are customizable via project-level rule directories.
|
|
254
|
-
- Inline suppression: `// pi-lens-ignore` or `# pi-lens-ignore` comments suppress diagnostic output for that line.
|
|
262
|
+
- Inline suppression: `// pi-lens-ignore` or `# pi-lens-ignore` comments suppress diagnostic output for that line.
|
|
263
|
+
|
|
264
|
+
## Environment Variables
|
|
265
|
+
|
|
266
|
+
| Variable | Default | Description |
|
|
267
|
+
|----------|---------|-------------|
|
|
268
|
+
| `PI_LENS_STARTUP_MODE` | auto | `full`, `minimal`, or `quick` — override session startup behavior |
|
|
269
|
+
| `PI_LENS_LOG_RETENTION_DAYS` | 7 | Days to retain log files before automatic cleanup |
|
|
270
|
+
| `PI_LENS_MAX_LOG_SIZE_MB` | 10 | Max size in MB before rotating active log files |
|
|
271
|
+
|
|
272
|
+
Logs are stored in `~/.pi-lens/` and automatically cleaned up at session start based on these settings.
|
|
@@ -187,7 +187,7 @@ export class ArchitectClient {
|
|
|
187
187
|
// biome-ignore lint/suspicious/noAssignInExpressions: RegExp.exec iteration
|
|
188
188
|
while ((match = regex.exec(content)) !== null) {
|
|
189
189
|
// Convert index to line number
|
|
190
|
-
const lineNum = content.slice(0, match.index).split(
|
|
190
|
+
const lineNum = content.slice(0, match.index).split(/\r?\n/).length;
|
|
191
191
|
violations.push({
|
|
192
192
|
pattern: rule.pattern,
|
|
193
193
|
message: check.message,
|
|
@@ -263,7 +263,7 @@ export class ArchitectClient {
|
|
|
263
263
|
const ruleBlocks = content.split(/(?=^ {2}- pattern:)/m);
|
|
264
264
|
|
|
265
265
|
for (const block of ruleBlocks) {
|
|
266
|
-
const lines = block.split(
|
|
266
|
+
const lines = block.split(/\r?\n/);
|
|
267
267
|
let rule: ArchitectRule | null = null;
|
|
268
268
|
let section: "must_not" | "must" | null = null;
|
|
269
269
|
let violation: {
|
|
@@ -345,7 +345,7 @@ message: found
|
|
|
345
345
|
output += ` ${ruleInfo} (${loc})${fix}\n`;
|
|
346
346
|
|
|
347
347
|
if (d.ruleDescription?.note) {
|
|
348
|
-
const shortNote = d.ruleDescription.note.split(
|
|
348
|
+
const shortNote = d.ruleDescription.note.split(/\r?\n/)[0];
|
|
349
349
|
output += ` → ${shortNote}\n`;
|
|
350
350
|
}
|
|
351
351
|
}
|
|
@@ -67,7 +67,7 @@ export class AstGrepRuleManager {
|
|
|
67
67
|
);
|
|
68
68
|
if (noteMatch) {
|
|
69
69
|
result.note = noteMatch[1]
|
|
70
|
-
.split(
|
|
70
|
+
.split(/\r?\n/)
|
|
71
71
|
.map((line) => line.trim())
|
|
72
72
|
.filter((line) => line.length > 0)
|
|
73
73
|
.join(" ");
|
|
@@ -82,7 +82,7 @@ export class AstGrepRuleManager {
|
|
|
82
82
|
const fixMatch = content.match(/^fix:\s*\|?([\s\S]*?)(?=^\w|^rule:|Z)/m);
|
|
83
83
|
if (fixMatch) {
|
|
84
84
|
result.fix = fixMatch[1]
|
|
85
|
-
.split(
|
|
85
|
+
.split(/\r?\n/)
|
|
86
86
|
.map((line) => line.replace(/^\s*\|?\s*/, ""))
|
|
87
87
|
.filter((line) => line.length > 0)
|
|
88
88
|
.join("\n");
|
package/clients/biome-client.ts
CHANGED
|
@@ -137,7 +137,7 @@ export class BiomeClient {
|
|
|
137
137
|
if (this.biomeAvailable !== null) return this.biomeAvailable;
|
|
138
138
|
|
|
139
139
|
// Check if already available
|
|
140
|
-
const result = this.
|
|
140
|
+
const result = await this.spawnBiomeAsync(["--version"], 10000);
|
|
141
141
|
if (!result.error && result.status === 0) {
|
|
142
142
|
this.biomeAvailable = true;
|
|
143
143
|
return true;
|
|
@@ -568,8 +568,8 @@ export class BiomeClient {
|
|
|
568
568
|
}
|
|
569
569
|
|
|
570
570
|
private computeDiff(original: string, formatted: string): string {
|
|
571
|
-
const origLines = original.split(
|
|
572
|
-
const formLines = formatted.split(
|
|
571
|
+
const origLines = original.split(/\r?\n/);
|
|
572
|
+
const formLines = formatted.split(/\r?\n/);
|
|
573
573
|
|
|
574
574
|
let changedLines = 0;
|
|
575
575
|
const changes: string[] = [];
|
|
@@ -213,7 +213,7 @@ export class ComplexityClient {
|
|
|
213
213
|
sourceFile: ts.SourceFile;
|
|
214
214
|
}): FileComplexity {
|
|
215
215
|
const { absolutePath, content, sourceFile } = parsed;
|
|
216
|
-
const lines = content.split(
|
|
216
|
+
const lines = content.split(/\r?\n/);
|
|
217
217
|
|
|
218
218
|
// Line counts and function collection
|
|
219
219
|
const { codeLines, commentLines } = this.countLines(sourceFile, lines);
|
|
@@ -393,7 +393,7 @@ export class ComplexityClient {
|
|
|
393
393
|
/\/\*\*?\s*(Overview|Summary|Description|Example|Usage)\s*\*?\//i,
|
|
394
394
|
];
|
|
395
395
|
|
|
396
|
-
const lines = sourceText.split(
|
|
396
|
+
const lines = sourceText.split(/\r?\n/);
|
|
397
397
|
for (const line of lines) {
|
|
398
398
|
// Only check comment lines
|
|
399
399
|
const trimmed = line.trim();
|
|
@@ -583,9 +583,10 @@ export class ComplexityClient {
|
|
|
583
583
|
let match;
|
|
584
584
|
while ((match = commentRegex.exec(text)) !== null) {
|
|
585
585
|
const lineStart = text.lastIndexOf("\n", match.index) + 1;
|
|
586
|
-
const startLine = text.substring(0, lineStart).split(
|
|
586
|
+
const startLine = text.substring(0, lineStart).split(/\r?\n/).length - 1;
|
|
587
587
|
const endLine =
|
|
588
|
-
text.substring(0, match.index + match[0].length).split(
|
|
588
|
+
text.substring(0, match.index + match[0].length).split(/\r?\n/).length -
|
|
589
|
+
1;
|
|
589
590
|
for (let i = startLine; i <= endLine; i++) {
|
|
590
591
|
commentPositions.add(i);
|
|
591
592
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import * as fs from "node:fs";
|
|
13
13
|
import * as path from "node:path";
|
|
14
|
-
import { safeSpawn } from "./safe-spawn.js";
|
|
14
|
+
import { safeSpawn, safeSpawnAsync } from "./safe-spawn.js";
|
|
15
15
|
|
|
16
16
|
// --- Types ---
|
|
17
17
|
|
|
@@ -63,7 +63,7 @@ export class DependencyChecker {
|
|
|
63
63
|
if (this.available !== null) return this.available;
|
|
64
64
|
|
|
65
65
|
// Check if available in PATH
|
|
66
|
-
const result =
|
|
66
|
+
const result = await safeSpawnAsync("madge", ["--version"], {
|
|
67
67
|
timeout: 5000,
|
|
68
68
|
});
|
|
69
69
|
this.available = !result.error && result.status === 0;
|
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
import type { Diagnostic } from "./types.js";
|
|
2
|
-
|
|
3
|
-
export type DefectClass =
|
|
4
|
-
| "silent-error"
|
|
5
|
-
| "injection"
|
|
6
|
-
| "secrets"
|
|
7
|
-
| "async-misuse"
|
|
8
|
-
| "correctness"
|
|
9
|
-
| "safety"
|
|
10
|
-
| "style"
|
|
11
|
-
| "unknown";
|
|
1
|
+
import type { DefectClass, Diagnostic } from "./types.js";
|
|
12
2
|
|
|
13
3
|
const SILENT_ERROR_HINTS = [
|
|
14
4
|
"empty-catch",
|
|
@@ -20,9 +10,27 @@ const SILENT_ERROR_HINTS = [
|
|
|
20
10
|
"silent",
|
|
21
11
|
];
|
|
22
12
|
|
|
23
|
-
const INJECTION_HINTS = [
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
const INJECTION_HINTS = [
|
|
14
|
+
"sql-injection",
|
|
15
|
+
"eval",
|
|
16
|
+
"exec",
|
|
17
|
+
"inner-html",
|
|
18
|
+
"javascript-url",
|
|
19
|
+
];
|
|
20
|
+
const SECRET_HINTS = [
|
|
21
|
+
"secret",
|
|
22
|
+
"token",
|
|
23
|
+
"password",
|
|
24
|
+
"api-key",
|
|
25
|
+
"hardcoded-secrets",
|
|
26
|
+
];
|
|
27
|
+
const ASYNC_HINTS = [
|
|
28
|
+
"await-in-loop",
|
|
29
|
+
"promise",
|
|
30
|
+
"concurrency",
|
|
31
|
+
"async",
|
|
32
|
+
"then-catch",
|
|
33
|
+
];
|
|
26
34
|
|
|
27
35
|
function hasAny(haystack: string, hints: string[]): boolean {
|
|
28
36
|
return hints.some((h) => haystack.includes(h));
|
|
@@ -40,7 +48,11 @@ export function classifyDefect(
|
|
|
40
48
|
if (hasAny(text, SECRET_HINTS)) return "secrets";
|
|
41
49
|
if (hasAny(text, ASYNC_HINTS)) return "async-misuse";
|
|
42
50
|
|
|
43
|
-
if (
|
|
51
|
+
if (
|
|
52
|
+
text.includes("no-") ||
|
|
53
|
+
text.includes("return") ||
|
|
54
|
+
text.includes("constructor")
|
|
55
|
+
) {
|
|
44
56
|
return "correctness";
|
|
45
57
|
}
|
|
46
58
|
|
|
@@ -50,6 +62,8 @@ export function classifyDefect(
|
|
|
50
62
|
return "unknown";
|
|
51
63
|
}
|
|
52
64
|
|
|
53
|
-
export function classifyDiagnostic(
|
|
65
|
+
export function classifyDiagnostic(
|
|
66
|
+
d: Pick<Diagnostic, "rule" | "tool" | "message">,
|
|
67
|
+
): DefectClass {
|
|
54
68
|
return classifyDefect(d.rule, d.tool, d.message);
|
|
55
69
|
}
|
|
@@ -17,15 +17,15 @@
|
|
|
17
17
|
import * as path from "node:path";
|
|
18
18
|
import type { FileKind } from "../file-kinds.js";
|
|
19
19
|
import { detectFileKind } from "../file-kinds.js";
|
|
20
|
+
import { isTestFile } from "../file-utils.js";
|
|
20
21
|
import { getPrimaryDispatchGroup } from "../language-policy.js";
|
|
21
22
|
import { resolveLanguageRootForFile } from "../language-profile.js";
|
|
22
|
-
import { isTestFile } from "../file-utils.js";
|
|
23
23
|
import { logLatency } from "../latency-logger.js";
|
|
24
24
|
import { normalizeMapKey } from "../path-utils.js";
|
|
25
25
|
import { RUNTIME_CONFIG } from "../runtime-config.js";
|
|
26
26
|
import { safeSpawnAsync } from "../safe-spawn.js";
|
|
27
27
|
import { classifyDiagnostic } from "./diagnostic-taxonomy.js";
|
|
28
|
-
import { FactStore } from "./fact-store.js";
|
|
28
|
+
import type { FactStore } from "./fact-store.js";
|
|
29
29
|
import { getToolPlan } from "./plan.js";
|
|
30
30
|
import { resolveRunnerPath } from "./runner-context.js";
|
|
31
31
|
import { getToolProfile } from "./tool-profile.js";
|
|
@@ -135,7 +135,7 @@ export function createDispatchContext(
|
|
|
135
135
|
cwd: normalizedCwd,
|
|
136
136
|
kind,
|
|
137
137
|
pi,
|
|
138
|
-
autofix:
|
|
138
|
+
autofix: false,
|
|
139
139
|
deltaMode: !pi.getFlag("no-delta"),
|
|
140
140
|
facts,
|
|
141
141
|
blockingOnly,
|
|
@@ -215,7 +215,10 @@ function dedupeOverlappingDiagnostics(diagnostics: Diagnostic[]): Diagnostic[] {
|
|
|
215
215
|
* Syntax: `// pi-lens-ignore: rule-id` (JS/TS) or `# pi-lens-ignore: rule-id` (Python/Ruby/etc.)
|
|
216
216
|
* Place on the same line as the diagnostic or the line immediately above it.
|
|
217
217
|
*/
|
|
218
|
-
function applyInlineSuppressions(
|
|
218
|
+
function applyInlineSuppressions(
|
|
219
|
+
diagnostics: Diagnostic[],
|
|
220
|
+
content: string,
|
|
221
|
+
): Diagnostic[] {
|
|
219
222
|
if (!content || !diagnostics.length) return diagnostics;
|
|
220
223
|
|
|
221
224
|
// Build a set of (line, ruleId) pairs that are suppressed.
|
|
@@ -226,9 +229,12 @@ function applyInlineSuppressions(diagnostics: Diagnostic[], content: string): Di
|
|
|
226
229
|
for (let i = 0; i < lines.length; i++) {
|
|
227
230
|
const m = SUPPRESS_RE.exec(lines[i]);
|
|
228
231
|
if (!m) continue;
|
|
229
|
-
const rules = m[1]
|
|
232
|
+
const rules = m[1]
|
|
233
|
+
.split(",")
|
|
234
|
+
.map((r) => r.trim())
|
|
235
|
+
.filter(Boolean);
|
|
230
236
|
const suppressedLine = i + 1; // same line (1-based)
|
|
231
|
-
const nextLine = i + 2;
|
|
237
|
+
const nextLine = i + 2; // next line (1-based)
|
|
232
238
|
for (const ruleId of rules) {
|
|
233
239
|
suppressed.add(`${suppressedLine}:${ruleId}`);
|
|
234
240
|
suppressed.add(`${nextLine}:${ruleId}`);
|
|
@@ -342,7 +348,7 @@ function buildCoverageNotice(
|
|
|
342
348
|
runnerLatencies: RunnerLatency[],
|
|
343
349
|
): Diagnostic | undefined {
|
|
344
350
|
if (!ctx.kind) return undefined;
|
|
345
|
-
const lspEnabled =
|
|
351
|
+
const lspEnabled = !ctx.pi.getFlag("no-lsp");
|
|
346
352
|
const primary = getPrimaryDispatchGroup(ctx.kind, lspEnabled);
|
|
347
353
|
if (!primary || primary.runnerIds.length === 0) return undefined;
|
|
348
354
|
|
|
@@ -367,7 +373,9 @@ function buildCoverageNotice(
|
|
|
367
373
|
(plan?.groups ?? [])
|
|
368
374
|
.filter(
|
|
369
375
|
(group) =>
|
|
370
|
-
!group.runnerIds.every((runnerId) =>
|
|
376
|
+
!group.runnerIds.every((runnerId) =>
|
|
377
|
+
primary.runnerIds.includes(runnerId),
|
|
378
|
+
),
|
|
371
379
|
)
|
|
372
380
|
.flatMap((group) => group.runnerIds)
|
|
373
381
|
.filter((runnerId) => !primary.runnerIds.includes(runnerId)),
|
|
@@ -376,7 +384,12 @@ function buildCoverageNotice(
|
|
|
376
384
|
// Structural-only runners (tree-sitter, ast-grep, similarity) are not
|
|
377
385
|
// substitutes for real linters — don't suppress the notice if only they ran.
|
|
378
386
|
const STRUCTURAL_RUNNERS = new Set([
|
|
379
|
-
"tree-sitter",
|
|
387
|
+
"tree-sitter",
|
|
388
|
+
"ast-grep-napi",
|
|
389
|
+
"similarity",
|
|
390
|
+
"spellcheck",
|
|
391
|
+
"architect",
|
|
392
|
+
"fact-rules",
|
|
380
393
|
]);
|
|
381
394
|
const anyLinterHasCoverage = runnerLatencies.some(
|
|
382
395
|
(r) =>
|
|
@@ -499,7 +512,7 @@ async function runGroup(
|
|
|
499
512
|
? group.runnerIds.filter((id) => {
|
|
500
513
|
const runner = registry.get(id);
|
|
501
514
|
return runner && ctx.kind && group.filterKinds?.includes(ctx.kind);
|
|
502
|
-
|
|
515
|
+
})
|
|
503
516
|
: group.runnerIds;
|
|
504
517
|
|
|
505
518
|
const semantic = group.semantic ?? "warning";
|
|
@@ -668,12 +681,20 @@ export async function dispatchForFile(
|
|
|
668
681
|
// This avoids partial-baseline corruption when processing multiple groups.
|
|
669
682
|
const dedupedDiagnostics = dedupeOverlappingDiagnostics(allDiagnostics);
|
|
670
683
|
const overlapSuppressed = suppressLintOverlapsWithLsp(dedupedDiagnostics);
|
|
671
|
-
const fileContent =
|
|
672
|
-
|
|
684
|
+
const fileContent =
|
|
685
|
+
ctx.facts.getFileFact<string>(ctx.filePath, "file.content") ?? "";
|
|
686
|
+
const inlineSuppressed = applyInlineSuppressions(
|
|
687
|
+
overlapSuppressed,
|
|
688
|
+
fileContent,
|
|
689
|
+
);
|
|
673
690
|
let visibleDiagnostics = inlineSuppressed;
|
|
674
691
|
let resolvedCount = 0;
|
|
675
692
|
if (ctx.deltaMode && previousBaseline) {
|
|
676
|
-
const filtered = filterDelta(
|
|
693
|
+
const filtered = filterDelta(
|
|
694
|
+
visibleDiagnostics,
|
|
695
|
+
previousBaseline,
|
|
696
|
+
(d) => d.id,
|
|
697
|
+
);
|
|
677
698
|
visibleDiagnostics = promoteDeltaUnusedToBlockers(filtered.new);
|
|
678
699
|
resolvedCount = filtered.fixed.length;
|
|
679
700
|
}
|
|
@@ -693,15 +714,19 @@ export async function dispatchForFile(
|
|
|
693
714
|
|
|
694
715
|
// Append fixed and fixable diagnostics to the persistent worklog
|
|
695
716
|
if (fixedItems.length > 0) {
|
|
696
|
-
import("../fix-worklog.js")
|
|
697
|
-
|
|
698
|
-
|
|
717
|
+
import("../fix-worklog.js")
|
|
718
|
+
.then(({ appendToWorklog }) => {
|
|
719
|
+
appendToWorklog(ctx.cwd, fixedItems, true);
|
|
720
|
+
})
|
|
721
|
+
.catch(() => {});
|
|
699
722
|
}
|
|
700
723
|
const fixableWarnings = warnings.filter((d) => d.fixable);
|
|
701
724
|
if (fixableWarnings.length > 0) {
|
|
702
|
-
import("../fix-worklog.js")
|
|
703
|
-
|
|
704
|
-
|
|
725
|
+
import("../fix-worklog.js")
|
|
726
|
+
.then(({ appendToWorklog }) => {
|
|
727
|
+
appendToWorklog(ctx.cwd, fixableWarnings, false);
|
|
728
|
+
})
|
|
729
|
+
.catch(() => {});
|
|
705
730
|
}
|
|
706
731
|
|
|
707
732
|
const inlineBlockers = blockers.filter((d) => d.tool !== "similarity");
|
|
@@ -804,7 +829,10 @@ function looksLikeDiagnosticCodePath(value: string): boolean {
|
|
|
804
829
|
return false;
|
|
805
830
|
}
|
|
806
831
|
|
|
807
|
-
function normalizeDiagnosticFilePath(
|
|
832
|
+
function normalizeDiagnosticFilePath(
|
|
833
|
+
ctx: DispatchContext,
|
|
834
|
+
rawPath?: string,
|
|
835
|
+
): string {
|
|
808
836
|
if (typeof rawPath === "string" && looksLikeDiagnosticCodePath(rawPath)) {
|
|
809
837
|
ctx.log(
|
|
810
838
|
`runner path normalization: ignored diagnostic code-like path '${rawPath}', using current file`,
|
|
@@ -39,15 +39,16 @@ const DEFAULT_VALUE_PATTERN =
|
|
|
39
39
|
const STRUCTURED_ERROR_PATTERN =
|
|
40
40
|
/\breturn\s+\{[^}]*(?:success\s*:\s*false|error\s*:)/;
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
// Any non-trivial comment (≥ 4 non-space chars) counts as documented intent.
|
|
43
|
+
// This covers patterns like: // continue, /* not found */, // best-effort, etc.
|
|
44
|
+
const EXPLAINING_COMMENT_PATTERN = /(?:\/\/\s*\S.{3,}|\/\*\s*\S[\s\S]{3,}?\*\/)/;
|
|
44
45
|
|
|
45
46
|
const FS_PROBE_PATTERN =
|
|
46
47
|
/\b(?:existsSync|statSync|lstatSync|readFileSync|accessSync)\b/;
|
|
47
48
|
|
|
48
49
|
const DB_PATTERN = /\b(?:query|execute|findOne|findMany|findById|insert|update|delete|select|prisma\.|knex\.|sequelize\.)/;
|
|
49
50
|
const NETWORK_PATTERN = /\b(?:fetch|axios|http\.|https\.|request\.|got\.|undici\.)/;
|
|
50
|
-
const FS_PATTERN = /\b(?:
|
|
51
|
+
const FS_PATTERN = /\b(?:readFileSync?|writeFileSync?|appendFileSync?|readdirSync?|mkdirSync?|statSync?|unlinkSync?|existsSync|accessSync?|copyFileSync?|renameSync?)\b/;
|
|
51
52
|
const PROCESS_PATTERN = /\b(?:spawn|exec|execSync|spawnSync|child_process\.)\b/;
|
|
52
53
|
|
|
53
54
|
function detectBoundaryCategory(
|