pi-lens 2.2.9 → 3.0.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 +198 -0
- package/README.md +709 -519
- package/clients/__tests__/file-time.test.js +216 -0
- package/clients/__tests__/file-time.test.ts +276 -0
- package/clients/__tests__/format-service.test.js +245 -0
- package/clients/__tests__/format-service.test.ts +339 -0
- package/clients/__tests__/formatters.test.js +271 -0
- package/clients/__tests__/formatters.test.ts +401 -0
- package/clients/amain-types.js +164 -0
- package/clients/amain-types.ts +165 -0
- package/clients/architect-client.js +56 -12
- package/clients/architect-client.ts +81 -16
- package/clients/ast-grep-client.js +2 -2
- package/clients/ast-grep-client.ts +14 -39
- package/clients/ast-grep-parser.ts +1 -1
- package/clients/ast-grep-rule-manager.js +8 -0
- package/clients/ast-grep-rule-manager.ts +10 -1
- package/clients/ast-grep-types.js +9 -0
- package/clients/ast-grep-types.ts +106 -0
- package/clients/auto-loop.js +10 -0
- package/clients/auto-loop.ts +14 -1
- package/clients/biome-client.js +81 -19
- package/clients/biome-client.ts +103 -22
- package/clients/bus/bus.js +191 -0
- package/clients/bus/bus.ts +251 -0
- package/clients/bus/events.js +214 -0
- package/clients/bus/events.ts +279 -0
- package/clients/bus/index.js +8 -0
- package/clients/bus/index.ts +9 -0
- package/clients/bus/integration.js +158 -0
- package/clients/bus/integration.ts +214 -0
- package/clients/complexity-client.js +13 -7
- package/clients/complexity-client.ts +13 -7
- package/clients/config-validator.js +465 -0
- package/clients/config-validator.ts +558 -0
- package/clients/dependency-checker.js +4 -10
- package/clients/dependency-checker.ts +4 -10
- package/clients/dispatch/__tests__/autofix-integration.test.js +245 -0
- package/clients/dispatch/__tests__/autofix-integration.test.ts +300 -0
- package/clients/dispatch/__tests__/runner-registration.test.js +236 -0
- package/clients/dispatch/__tests__/runner-registration.test.ts +282 -0
- package/clients/dispatch/bus-dispatcher.js +177 -0
- package/clients/dispatch/bus-dispatcher.ts +251 -0
- package/clients/dispatch/dispatcher.edge.test.js +82 -0
- package/clients/dispatch/dispatcher.edge.test.ts +100 -0
- package/clients/dispatch/dispatcher.format.test.js +46 -0
- package/clients/dispatch/dispatcher.format.test.ts +58 -0
- package/clients/dispatch/dispatcher.inline.test.js +74 -0
- package/clients/dispatch/dispatcher.inline.test.ts +93 -0
- package/clients/dispatch/dispatcher.js +19 -53
- package/clients/dispatch/dispatcher.ts +20 -67
- package/clients/dispatch/plan.js +9 -4
- package/clients/dispatch/plan.ts +9 -4
- package/clients/dispatch/runners/architect.js +21 -7
- package/clients/dispatch/runners/architect.test.js +138 -0
- package/clients/dispatch/runners/architect.test.ts +162 -0
- package/clients/dispatch/runners/architect.ts +22 -7
- package/clients/dispatch/runners/ast-grep-napi.js +462 -0
- package/clients/dispatch/runners/ast-grep-napi.test.js +111 -0
- package/clients/dispatch/runners/ast-grep-napi.test.ts +133 -0
- package/clients/dispatch/runners/ast-grep-napi.ts +506 -0
- package/clients/dispatch/runners/ast-grep.js +62 -19
- package/clients/dispatch/runners/ast-grep.ts +70 -18
- package/clients/dispatch/runners/biome.js +29 -53
- package/clients/dispatch/runners/biome.ts +29 -63
- package/clients/dispatch/runners/config-validation.js +67 -0
- package/clients/dispatch/runners/config-validation.ts +82 -0
- package/clients/dispatch/runners/go-vet.js +4 -28
- package/clients/dispatch/runners/go-vet.ts +4 -32
- package/clients/dispatch/runners/index.js +30 -10
- package/clients/dispatch/runners/index.ts +30 -10
- package/clients/dispatch/runners/oxlint.js +141 -0
- package/clients/dispatch/runners/oxlint.test.js +230 -0
- package/clients/dispatch/runners/oxlint.test.ts +303 -0
- package/clients/dispatch/runners/oxlint.ts +175 -0
- package/clients/dispatch/runners/pyright.js +40 -70
- package/clients/dispatch/runners/pyright.test.js +16 -2
- package/clients/dispatch/runners/pyright.test.ts +14 -2
- package/clients/dispatch/runners/pyright.ts +48 -91
- package/clients/dispatch/runners/python-slop.js +97 -0
- package/clients/dispatch/runners/python-slop.test.js +203 -0
- package/clients/dispatch/runners/python-slop.test.ts +298 -0
- package/clients/dispatch/runners/python-slop.ts +124 -0
- package/clients/dispatch/runners/ruff.js +18 -71
- package/clients/dispatch/runners/ruff.ts +19 -79
- package/clients/dispatch/runners/rust-clippy.js +28 -32
- package/clients/dispatch/runners/rust-clippy.ts +29 -31
- package/clients/dispatch/runners/scan_codebase.test.js +89 -0
- package/clients/dispatch/runners/scan_codebase.test.ts +105 -0
- package/clients/dispatch/runners/shellcheck.js +147 -0
- package/clients/dispatch/runners/shellcheck.test.js +98 -0
- package/clients/dispatch/runners/shellcheck.test.ts +129 -0
- package/clients/dispatch/runners/shellcheck.ts +188 -0
- package/clients/dispatch/runners/similarity.js +230 -0
- package/clients/dispatch/runners/similarity.ts +339 -0
- package/clients/dispatch/runners/spellcheck.js +106 -0
- package/clients/dispatch/runners/spellcheck.test.js +158 -0
- package/clients/dispatch/runners/spellcheck.test.ts +214 -0
- package/clients/dispatch/runners/spellcheck.ts +136 -0
- package/clients/dispatch/runners/tree-sitter.js +107 -0
- package/clients/dispatch/runners/tree-sitter.ts +135 -0
- package/clients/dispatch/runners/ts-lsp.js +104 -33
- package/clients/dispatch/runners/ts-lsp.ts +120 -38
- package/clients/dispatch/runners/ts-slop.js +113 -0
- package/clients/dispatch/runners/ts-slop.test.js +180 -0
- package/clients/dispatch/runners/ts-slop.test.ts +230 -0
- package/clients/dispatch/runners/ts-slop.ts +142 -0
- package/clients/dispatch/runners/utils/diagnostic-parsers.js +134 -0
- package/clients/dispatch/runners/utils/diagnostic-parsers.ts +186 -0
- package/clients/dispatch/runners/utils/runner-helpers.js +115 -0
- package/clients/dispatch/runners/utils/runner-helpers.ts +167 -0
- package/clients/dispatch/runners/utils.js +2 -4
- package/clients/dispatch/runners/utils.ts +2 -4
- package/clients/dispatch/types.ts +1 -1
- package/clients/dispatch/utils/format-utils.js +49 -0
- package/clients/dispatch/utils/format-utils.ts +60 -0
- package/clients/dogfood.test.js +201 -0
- package/clients/dogfood.test.ts +269 -0
- package/clients/file-time.js +152 -0
- package/clients/file-time.ts +208 -0
- package/clients/file-utils.js +40 -0
- package/clients/file-utils.ts +44 -0
- package/clients/fix-scanners.js +10 -20
- package/clients/fix-scanners.ts +10 -22
- package/clients/format-service.js +172 -0
- package/clients/format-service.ts +254 -0
- package/clients/formatters.js +435 -0
- package/clients/formatters.ts +508 -0
- package/clients/go-client.js +5 -14
- package/clients/go-client.ts +5 -13
- package/clients/installer/index.js +356 -0
- package/clients/installer/index.ts +426 -0
- package/clients/jscpd-client.js +11 -9
- package/clients/jscpd-client.ts +12 -8
- package/clients/knip-client.js +3 -7
- package/clients/knip-client.ts +3 -6
- package/clients/lsp/__tests__/client.test.js +325 -0
- package/clients/lsp/__tests__/client.test.ts +434 -0
- package/clients/lsp/__tests__/config.test.js +166 -0
- package/clients/lsp/__tests__/config.test.ts +209 -0
- package/clients/lsp/__tests__/error-recovery.test.js +213 -0
- package/clients/lsp/__tests__/error-recovery.test.ts +279 -0
- package/clients/lsp/__tests__/integration.test.js +127 -0
- package/clients/lsp/__tests__/integration.test.ts +160 -0
- package/clients/lsp/__tests__/launch.test.js +260 -0
- package/clients/lsp/__tests__/launch.test.ts +329 -0
- package/clients/lsp/__tests__/server.test.js +259 -0
- package/clients/lsp/__tests__/server.test.ts +332 -0
- package/clients/lsp/__tests__/service.test.js +417 -0
- package/clients/lsp/__tests__/service.test.ts +499 -0
- package/clients/lsp/client.js +235 -0
- package/clients/lsp/client.ts +328 -0
- package/clients/lsp/config.js +115 -0
- package/clients/lsp/config.ts +149 -0
- package/clients/lsp/index.js +222 -0
- package/clients/lsp/index.ts +280 -0
- package/clients/lsp/installer/index.js +391 -0
- package/clients/lsp/interactive-install.js +210 -0
- package/clients/lsp/interactive-install.ts +251 -0
- package/clients/lsp/language.js +170 -0
- package/clients/lsp/language.ts +216 -0
- package/clients/lsp/launch.js +174 -0
- package/clients/lsp/launch.ts +240 -0
- package/clients/lsp/lsp/launch.js +116 -0
- package/clients/lsp/lsp/server.js +532 -0
- package/clients/lsp/lsp-index.js +10 -0
- package/clients/lsp/lsp-index.ts +11 -0
- package/clients/lsp/path-utils.js +48 -0
- package/clients/lsp/path-utils.ts +52 -0
- package/clients/lsp/server.js +615 -0
- package/clients/lsp/server.ts +800 -0
- package/clients/lsp/test-py-spawn/requirements.txt +1 -0
- package/clients/lsp/test-py-spawn/test.py +3 -0
- package/clients/lsp/test-py-svc/requirements.txt +1 -0
- package/clients/lsp/test-py-svc/test.py +3 -0
- package/clients/lsp/test-python-project/requirements.txt +1 -0
- package/clients/lsp/test-python-project/test.py +5 -0
- package/clients/metrics-history.js +2 -2
- package/clients/metrics-history.ts +2 -2
- package/clients/production-readiness.js +522 -0
- package/clients/production-readiness.ts +556 -0
- package/clients/project-index.js +255 -0
- package/clients/project-index.ts +383 -0
- package/clients/project-metadata.js +531 -0
- package/clients/project-metadata.ts +624 -0
- package/clients/ruff-client.js +56 -16
- package/clients/ruff-client.ts +72 -15
- package/clients/runner-tracker.js +152 -0
- package/clients/runner-tracker.ts +213 -0
- package/clients/rust-client.js +4 -11
- package/clients/rust-client.ts +5 -11
- package/clients/safe-spawn.js +96 -0
- package/clients/safe-spawn.ts +128 -0
- package/clients/scan-architectural-debt.js +3 -6
- package/clients/scan-architectural-debt.ts +3 -6
- package/clients/scan-utils.js +5 -20
- package/clients/scan-utils.ts +5 -29
- package/clients/secrets-scanner.js +3 -17
- package/clients/secrets-scanner.ts +4 -20
- package/clients/services/__tests__/effect-integration.test.js +86 -0
- package/clients/services/__tests__/effect-integration.test.ts +111 -0
- package/clients/services/effect-integration.js +194 -0
- package/clients/services/effect-integration.ts +268 -0
- package/clients/services/index.js +7 -0
- package/clients/services/index.ts +8 -0
- package/clients/services/runner-service.js +105 -0
- package/clients/services/runner-service.ts +179 -0
- package/clients/sg-runner.js +87 -13
- package/clients/sg-runner.ts +97 -13
- package/clients/state-matrix.js +160 -0
- package/clients/state-matrix.ts +202 -0
- package/clients/subprocess-client.js +10 -9
- package/clients/subprocess-client.ts +10 -8
- package/clients/test-runner-client.js +3 -7
- package/clients/test-runner-client.ts +3 -6
- package/clients/tool-availability.js +4 -10
- package/clients/tool-availability.ts +4 -9
- package/clients/tree-sitter-client.js +564 -0
- package/clients/tree-sitter-client.ts +797 -0
- package/clients/tree-sitter-query-loader.js +355 -0
- package/clients/tree-sitter-query-loader.ts +425 -0
- package/clients/type-coverage-client.js +3 -7
- package/clients/type-coverage-client.ts +3 -6
- package/clients/typescript-client.codefix.test.js +157 -0
- package/clients/typescript-client.codefix.test.ts +186 -0
- package/clients/typescript-client.js +43 -0
- package/clients/typescript-client.ts +98 -0
- package/commands/booboo.js +799 -219
- package/commands/booboo.ts +1004 -225
- package/commands/clients/ast-grep-client.js +250 -0
- package/commands/clients/ast-grep-parser.js +86 -0
- package/commands/clients/ast-grep-rule-manager.js +91 -0
- package/commands/clients/ast-grep-types.js +9 -0
- package/commands/clients/biome-client.js +380 -0
- package/commands/clients/complexity-client.js +667 -0
- package/commands/clients/file-kinds.js +177 -0
- package/commands/clients/file-utils.js +40 -0
- package/commands/clients/jscpd-client.js +169 -0
- package/commands/clients/knip-client.js +211 -0
- package/commands/clients/ruff-client.js +297 -0
- package/commands/clients/safe-spawn.js +88 -0
- package/commands/clients/scan-utils.js +83 -0
- package/commands/clients/sg-runner.js +190 -0
- package/commands/clients/types.js +11 -0
- package/commands/clients/typescript-client.js +505 -0
- package/commands/fix-from-booboo.js +398 -0
- package/commands/fix-from-booboo.ts +485 -0
- package/commands/fix-simplified.js +618 -0
- package/commands/fix-simplified.ts +768 -0
- package/commands/rate.js +10 -14
- package/commands/rate.ts +9 -16
- package/default-architect.yaml +59 -15
- package/index.ts +342 -429
- package/package.json +16 -3
- package/rules/ast-grep-rules/rules/empty-catch.yml +38 -13
- package/rules/ast-grep-rules/rules/no-array-constructor.yml +1 -0
- package/rules/ast-grep-rules/rules/no-debugger.yml +2 -0
- package/rules/python-slop-rules/.sgconfig.yml +4 -0
- package/rules/python-slop-rules/rules/slop-rules.yml +647 -0
- package/rules/tree-sitter-queries/python/bare-except.yml +54 -0
- package/rules/tree-sitter-queries/python/eval-exec.yml +50 -0
- package/rules/tree-sitter-queries/python/is-vs-equals.yml +60 -0
- package/rules/tree-sitter-queries/python/mutable-default-arg.yml +57 -0
- package/rules/tree-sitter-queries/python/unreachable-except.yml +60 -0
- package/rules/tree-sitter-queries/python/wildcard-import.yml +46 -0
- package/rules/tree-sitter-queries/tsx/dangerously-set-inner-html.yml +63 -0
- package/rules/tree-sitter-queries/typescript/await-in-loop.yml +56 -0
- package/rules/tree-sitter-queries/typescript/console-statement.yml +47 -0
- package/rules/tree-sitter-queries/typescript/debugger.yml +47 -0
- package/rules/tree-sitter-queries/typescript/deep-nesting.yml +117 -0
- package/rules/tree-sitter-queries/typescript/deep-promise-chain.yml +73 -0
- package/rules/tree-sitter-queries/typescript/empty-catch.yml +64 -0
- package/rules/tree-sitter-queries/typescript/eval.yml +48 -0
- package/rules/tree-sitter-queries/typescript/hardcoded-secrets.yml +78 -0
- package/rules/tree-sitter-queries/typescript/long-parameter-list.yml +62 -0
- package/rules/tree-sitter-queries/typescript/mixed-async-styles.yml +49 -0
- package/rules/tree-sitter-queries/typescript/nested-ternary.yml +45 -0
- package/rules/ts-slop-rules/.sgconfig.yml +4 -0
- package/rules/ts-slop-rules/rules/in-correct-optional-input-type.yml +10 -0
- package/rules/ts-slop-rules/rules/jwt-no-verify.yml +13 -0
- package/rules/ts-slop-rules/rules/no-architecture-violation.yml +10 -0
- package/rules/ts-slop-rules/rules/no-case-declarations.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dangerously-set-inner-html.yml +10 -0
- package/rules/ts-slop-rules/rules/no-debugger.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-args.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-class-members.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-keys.yml +10 -0
- package/rules/ts-slop-rules/rules/no-eval.yml +13 -0
- package/rules/ts-slop-rules/rules/no-hardcoded-secrets.yml +12 -0
- package/rules/ts-slop-rules/rules/no-implied-eval.yml +12 -0
- package/rules/ts-slop-rules/rules/no-inner-html.yml +13 -0
- package/rules/ts-slop-rules/rules/no-javascript-url.yml +10 -0
- package/rules/ts-slop-rules/rules/no-mutable-default.yml +10 -0
- package/rules/ts-slop-rules/rules/no-nested-links.yml +12 -0
- package/rules/ts-slop-rules/rules/no-new-symbol.yml +10 -0
- package/rules/ts-slop-rules/rules/no-new-wrappers.yml +13 -0
- package/rules/ts-slop-rules/rules/no-open-redirect.yml +16 -0
- package/rules/ts-slop-rules/rules/slop-rules.yml +455 -0
- package/rules/ts-slop-rules/rules/weak-rsa-key.yml +12 -0
- package/skills/ast-grep/SKILL.md +182 -0
- package/clients/dispatch/runners/secrets.js +0 -109
- package/commands/fix.js +0 -244
- package/commands/fix.ts +0 -373
- package/rules/ast-grep-rules/rules/no-lonely-if.yml +0 -13
package/index.ts
CHANGED
|
@@ -8,21 +8,30 @@ import { AgentBehaviorClient } from "./clients/agent-behavior-client.js";
|
|
|
8
8
|
import { ArchitectClient } from "./clients/architect-client.js";
|
|
9
9
|
import { AstGrepClient } from "./clients/ast-grep-client.js";
|
|
10
10
|
import { BiomeClient } from "./clients/biome-client.js";
|
|
11
|
+
import {
|
|
12
|
+
enableDebug as enableBusDebug,
|
|
13
|
+
FileModified,
|
|
14
|
+
initBusIntegration,
|
|
15
|
+
SessionStarted,
|
|
16
|
+
TurnEnded,
|
|
17
|
+
} from "./clients/bus/index.js";
|
|
11
18
|
import { CacheManager } from "./clients/cache-manager.js";
|
|
12
19
|
import { ComplexityClient } from "./clients/complexity-client.js";
|
|
13
20
|
import { DependencyChecker } from "./clients/dependency-checker.js";
|
|
21
|
+
import { dispatchLintWithBus } from "./clients/dispatch/bus-dispatcher.js";
|
|
14
22
|
import { dispatchLint } from "./clients/dispatch/integration.js";
|
|
23
|
+
import {
|
|
24
|
+
getFormatService,
|
|
25
|
+
resetFormatService,
|
|
26
|
+
} from "./clients/format-service.js";
|
|
15
27
|
import { GoClient } from "./clients/go-client.js";
|
|
28
|
+
import { ensureTool } from "./clients/installer/index.js";
|
|
16
29
|
import { buildInterviewer } from "./clients/interviewer.js";
|
|
17
30
|
import { JscpdClient } from "./clients/jscpd-client.js";
|
|
18
31
|
import { KnipClient } from "./clients/knip-client.js";
|
|
32
|
+
import { getLSPService, resetLSPService } from "./clients/lsp/index.js";
|
|
19
33
|
import { MetricsClient } from "./clients/metrics-client.js";
|
|
20
|
-
import {
|
|
21
|
-
captureSnapshot,
|
|
22
|
-
captureSnapshots,
|
|
23
|
-
formatTrendCell,
|
|
24
|
-
getTrendSummary,
|
|
25
|
-
} from "./clients/metrics-history.js";
|
|
34
|
+
import { captureSnapshot } from "./clients/metrics-history.js";
|
|
26
35
|
import { RuffClient } from "./clients/ruff-client.js";
|
|
27
36
|
import {
|
|
28
37
|
formatRulesForPrompt,
|
|
@@ -32,14 +41,13 @@ import {
|
|
|
32
41
|
import { RustClient } from "./clients/rust-client.js";
|
|
33
42
|
import { getSourceFiles } from "./clients/scan-utils.js";
|
|
34
43
|
import { formatSecrets, scanForSecrets } from "./clients/secrets-scanner.js";
|
|
44
|
+
import { dispatchLintWithEffect } from "./clients/services/effect-integration.js";
|
|
35
45
|
import { TestRunnerClient } from "./clients/test-runner-client.js";
|
|
36
46
|
import { TodoScanner } from "./clients/todo-scanner.js";
|
|
37
47
|
import { TypeCoverageClient } from "./clients/type-coverage-client.js";
|
|
38
48
|
import { TypeScriptClient } from "./clients/typescript-client.js";
|
|
39
49
|
import { handleBooboo } from "./commands/booboo.js";
|
|
40
|
-
import {
|
|
41
|
-
import { handleRate } from "./commands/rate.js";
|
|
42
|
-
import { handleRefactor, initRefactorLoop } from "./commands/refactor.js";
|
|
50
|
+
import { initRefactorLoop } from "./commands/refactor.js";
|
|
43
51
|
|
|
44
52
|
/** Parse a diff to extract modified line ranges in the new file.
|
|
45
53
|
* Handles pi's custom diff format:
|
|
@@ -133,7 +141,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
133
141
|
const agentBehaviorClient = new AgentBehaviorClient();
|
|
134
142
|
const cacheManager = new CacheManager();
|
|
135
143
|
|
|
136
|
-
// --- Initialize auto-loops
|
|
144
|
+
// --- Initialize auto-loops ---
|
|
137
145
|
initRefactorLoop(pi);
|
|
138
146
|
|
|
139
147
|
// --- Flags ---
|
|
@@ -150,6 +158,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
150
158
|
default: false,
|
|
151
159
|
});
|
|
152
160
|
|
|
161
|
+
pi.registerFlag("no-oxlint", {
|
|
162
|
+
description: "Disable Oxlint fast JS/TS linter",
|
|
163
|
+
type: "boolean",
|
|
164
|
+
default: false,
|
|
165
|
+
});
|
|
166
|
+
|
|
153
167
|
pi.registerFlag("no-ast-grep", {
|
|
154
168
|
description: "Disable ast-grep structural analysis",
|
|
155
169
|
type: "boolean",
|
|
@@ -162,6 +176,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
162
176
|
default: false,
|
|
163
177
|
});
|
|
164
178
|
|
|
179
|
+
pi.registerFlag("no-shellcheck", {
|
|
180
|
+
description: "Disable shellcheck for shell scripts",
|
|
181
|
+
type: "boolean",
|
|
182
|
+
default: false,
|
|
183
|
+
});
|
|
184
|
+
|
|
165
185
|
pi.registerFlag("no-lsp", {
|
|
166
186
|
description: "Disable TypeScript LSP",
|
|
167
187
|
type: "boolean",
|
|
@@ -174,17 +194,32 @@ export default function (pi: ExtensionAPI) {
|
|
|
174
194
|
default: false,
|
|
175
195
|
});
|
|
176
196
|
|
|
177
|
-
pi.registerFlag("
|
|
197
|
+
pi.registerFlag("no-autoformat", {
|
|
178
198
|
description:
|
|
179
|
-
"
|
|
199
|
+
"Disable automatic formatting on file write (formatters run by default)",
|
|
180
200
|
type: "boolean",
|
|
181
201
|
default: false,
|
|
182
202
|
});
|
|
183
203
|
|
|
184
|
-
pi.registerFlag("autofix
|
|
185
|
-
description:
|
|
204
|
+
pi.registerFlag("no-autofix", {
|
|
205
|
+
description:
|
|
206
|
+
"Disable auto-fixing of lint issues (Biome, Ruff). Use --no-autofix-biome or --no-autofix-ruff for individual control.",
|
|
186
207
|
type: "boolean",
|
|
187
|
-
default:
|
|
208
|
+
default: false,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
pi.registerFlag("no-autofix-biome", {
|
|
212
|
+
description:
|
|
213
|
+
"Disable Biome auto-fix on write (Biome autofix is enabled by default)",
|
|
214
|
+
type: "boolean",
|
|
215
|
+
default: false,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
pi.registerFlag("no-autofix-ruff", {
|
|
219
|
+
description:
|
|
220
|
+
"Disable Ruff auto-fix on write (Ruff autofix is enabled by default)",
|
|
221
|
+
type: "boolean",
|
|
222
|
+
default: false,
|
|
188
223
|
});
|
|
189
224
|
|
|
190
225
|
pi.registerFlag("no-tests", {
|
|
@@ -212,6 +247,39 @@ export default function (pi: ExtensionAPI) {
|
|
|
212
247
|
default: false,
|
|
213
248
|
});
|
|
214
249
|
|
|
250
|
+
// Internal flag for development/debugging (not surfaced to users)
|
|
251
|
+
pi.registerFlag("lens-bus", {
|
|
252
|
+
description: "[Internal] Enable event bus system",
|
|
253
|
+
type: "boolean",
|
|
254
|
+
default: false,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
pi.registerFlag("lens-bus-debug", {
|
|
258
|
+
description: "Enable verbose bus event logging",
|
|
259
|
+
type: "boolean",
|
|
260
|
+
default: false,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
pi.registerFlag("lens-effect", {
|
|
264
|
+
description: "Enable Effect-TS concurrent runner execution (Phase 2)",
|
|
265
|
+
type: "boolean",
|
|
266
|
+
default: false,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
pi.registerFlag("lens-lsp", {
|
|
270
|
+
description:
|
|
271
|
+
"Enable LSP (Language Server Protocol) for semantic analysis (Phase 3)",
|
|
272
|
+
type: "boolean",
|
|
273
|
+
default: false,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
pi.registerFlag("auto-install", {
|
|
277
|
+
description:
|
|
278
|
+
"Auto-install missing LSP servers without prompting (for Go, Rust, YAML, JSON, Bash)",
|
|
279
|
+
type: "boolean",
|
|
280
|
+
default: false,
|
|
281
|
+
});
|
|
282
|
+
|
|
215
283
|
// --- Commands ---
|
|
216
284
|
|
|
217
285
|
pi.registerCommand("lens-booboo", {
|
|
@@ -235,404 +303,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
235
303
|
),
|
|
236
304
|
});
|
|
237
305
|
|
|
238
|
-
//
|
|
239
|
-
// Rules marked "skip" are architectural — they need deliberate user decisions.
|
|
240
|
-
// They are excluded from inline tool_result hard stops (use /lens-refactor instead).
|
|
241
|
-
const RULE_ACTIONS: Record<
|
|
242
|
-
string,
|
|
243
|
-
{ type: "biome" | "agent" | "skip"; note: string }
|
|
244
|
-
> = {
|
|
245
|
-
"no-lonely-if": { type: "biome", note: "auto-fixed by Biome --write" },
|
|
246
|
-
"empty-catch": {
|
|
247
|
-
type: "agent",
|
|
248
|
-
note: "Add this.log('Error: ' + err.message) to the catch block",
|
|
249
|
-
},
|
|
250
|
-
"no-console-log": {
|
|
251
|
-
type: "agent",
|
|
252
|
-
note: "Remove or replace with class logger method",
|
|
253
|
-
},
|
|
254
|
-
"no-debugger": { type: "agent", note: "Remove the debugger statement" },
|
|
255
|
-
"no-return-await": {
|
|
256
|
-
type: "agent",
|
|
257
|
-
note: "Remove the unnecessary `return await`",
|
|
258
|
-
},
|
|
259
|
-
"nested-ternary": {
|
|
260
|
-
type: "agent",
|
|
261
|
-
note: "Extract to if/else or a named variable",
|
|
262
|
-
},
|
|
263
|
-
"no-throw-string": {
|
|
264
|
-
type: "agent",
|
|
265
|
-
note: "Wrap in `new Error(...)` instead of throwing a string",
|
|
266
|
-
},
|
|
267
|
-
"no-star-imports": {
|
|
268
|
-
type: "skip",
|
|
269
|
-
note: "Requires knowing which exports are actually used.",
|
|
270
|
-
},
|
|
271
|
-
"no-as-any": {
|
|
272
|
-
type: "skip",
|
|
273
|
-
note: "Replacing `as any` requires knowing the correct type.",
|
|
274
|
-
},
|
|
275
|
-
"no-non-null-assertion": {
|
|
276
|
-
type: "skip",
|
|
277
|
-
note: "Each `!` needs nullability analysis in context.",
|
|
278
|
-
},
|
|
279
|
-
"large-class": {
|
|
280
|
-
type: "skip",
|
|
281
|
-
note: "Splitting a class requires architectural decisions.",
|
|
282
|
-
},
|
|
283
|
-
"long-method": {
|
|
284
|
-
type: "skip",
|
|
285
|
-
note: "Extraction requires understanding the function's purpose.",
|
|
286
|
-
},
|
|
287
|
-
"long-parameter-list": {
|
|
288
|
-
type: "skip",
|
|
289
|
-
note: "Redesigning the signature requires an API decision.",
|
|
290
|
-
},
|
|
291
|
-
"no-shadow": {
|
|
292
|
-
type: "skip",
|
|
293
|
-
note: "Renaming requires understanding all variable scopes.",
|
|
294
|
-
},
|
|
295
|
-
"no-process-env": {
|
|
296
|
-
type: "skip",
|
|
297
|
-
note: "Using process.env directly makes code untestable. Use DI or a config module.",
|
|
298
|
-
},
|
|
299
|
-
"no-param-reassign": {
|
|
300
|
-
type: "agent",
|
|
301
|
-
note: "Create a new variable instead of reassigning the parameter.",
|
|
302
|
-
},
|
|
303
|
-
"no-single-char-var": {
|
|
304
|
-
type: "skip",
|
|
305
|
-
note: "Renaming requires understanding the variable's purpose.",
|
|
306
|
-
},
|
|
307
|
-
"switch-without-default": {
|
|
308
|
-
type: "agent",
|
|
309
|
-
note: "Add a default case to handle unexpected values.",
|
|
310
|
-
},
|
|
311
|
-
"no-architecture-violation": {
|
|
312
|
-
type: "skip",
|
|
313
|
-
note: "Layer boundary violations require architectural decisions.",
|
|
314
|
-
},
|
|
315
|
-
"switch-exhaustiveness": {
|
|
316
|
-
type: "agent",
|
|
317
|
-
note: "Add the missing case(s) or a default clause to handle all union values.",
|
|
318
|
-
},
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
// Derived from RULE_ACTIONS — used to suppress architectural rules from inline hard stops.
|
|
322
|
-
const SKIP_RULES = new Set(
|
|
323
|
-
Object.entries(RULE_ACTIONS)
|
|
324
|
-
.filter(([, v]) => v.type === "skip")
|
|
325
|
-
.map(([k]) => k),
|
|
326
|
-
);
|
|
327
|
-
|
|
328
|
-
pi.registerCommand("lens-booboo-fix", {
|
|
329
|
-
description:
|
|
330
|
-
"Iterative fix loop: auto-fixes Biome/Ruff, then generates a per-issue plan for agent to execute. Run repeatedly until clean. Usage: /lens-booboo-fix [path] [--reset]",
|
|
331
|
-
handler: (args, ctx) =>
|
|
332
|
-
handleFix(
|
|
333
|
-
args,
|
|
334
|
-
ctx,
|
|
335
|
-
{
|
|
336
|
-
tsClient,
|
|
337
|
-
astGrep: astGrepClient,
|
|
338
|
-
ruff: ruffClient,
|
|
339
|
-
biome: biomeClient,
|
|
340
|
-
knip: knipClient,
|
|
341
|
-
jscpd: jscpdClient,
|
|
342
|
-
complexity: complexityClient,
|
|
343
|
-
},
|
|
344
|
-
pi,
|
|
345
|
-
RULE_ACTIONS,
|
|
346
|
-
),
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
pi.registerCommand("lens-booboo-refactor", {
|
|
350
|
-
description:
|
|
351
|
-
"Interactive architectural refactor: scans for worst offender, opens a browser interview with options + recommendation, then steers the agent with your decision. Usage: /lens-booboo-refactor [path]",
|
|
352
|
-
handler: (args, ctx) =>
|
|
353
|
-
handleRefactor(
|
|
354
|
-
args,
|
|
355
|
-
ctx,
|
|
356
|
-
{
|
|
357
|
-
astGrep: astGrepClient,
|
|
358
|
-
complexity: complexityClient,
|
|
359
|
-
architect: architectClient,
|
|
360
|
-
},
|
|
361
|
-
pi,
|
|
362
|
-
SKIP_RULES,
|
|
363
|
-
RULE_ACTIONS,
|
|
364
|
-
),
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
pi.registerCommand("lens-metrics", {
|
|
368
|
-
description:
|
|
369
|
-
"Measure complexity metrics for all files and export to report.md. Usage: /lens-metrics [path]",
|
|
370
|
-
handler: async (args, ctx) => {
|
|
371
|
-
const targetPath = args.trim() || ctx.cwd || process.cwd();
|
|
372
|
-
ctx.ui.notify("📊 Measuring code metrics...", "info");
|
|
373
|
-
|
|
374
|
-
const reviewDir = path.join(process.cwd(), ".pi-lens", "reviews");
|
|
375
|
-
const timestamp = new Date()
|
|
376
|
-
.toISOString()
|
|
377
|
-
.replace(/[:.]/g, "-")
|
|
378
|
-
.slice(0, 19);
|
|
379
|
-
const projectName = path.basename(process.cwd());
|
|
380
|
-
|
|
381
|
-
const results: import("./clients/complexity-client.js").FileComplexity[] =
|
|
382
|
-
[];
|
|
383
|
-
|
|
384
|
-
const isTsProject = nodeFs.existsSync(
|
|
385
|
-
path.join(targetPath, "tsconfig.json"),
|
|
386
|
-
);
|
|
387
|
-
const files = getSourceFiles(targetPath, isTsProject);
|
|
388
|
-
for (const fullPath of files) {
|
|
389
|
-
if (complexityClient.isSupportedFile(fullPath)) {
|
|
390
|
-
const metrics = complexityClient.analyzeFile(fullPath);
|
|
391
|
-
if (metrics) {
|
|
392
|
-
results.push(metrics);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (results.length === 0) {
|
|
398
|
-
ctx.ui.notify("No supported files found to analyze", "warning");
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// Calculate aggregates
|
|
403
|
-
const avgMI =
|
|
404
|
-
results.reduce((a, b) => a + b.maintainabilityIndex, 0) /
|
|
405
|
-
results.length;
|
|
406
|
-
const avgCognitive =
|
|
407
|
-
results.reduce((a, b) => a + b.cognitiveComplexity, 0) / results.length;
|
|
408
|
-
const avgCyclomatic =
|
|
409
|
-
results.reduce((a, b) => a + b.cyclomaticComplexity, 0) /
|
|
410
|
-
results.length;
|
|
411
|
-
const avgFunctionLength =
|
|
412
|
-
results.reduce((a, b) => a + b.avgFunctionLength, 0) / results.length;
|
|
413
|
-
const maxNesting = Math.max(...results.map((r) => r.maxNestingDepth));
|
|
414
|
-
const maxCognitive = Math.max(
|
|
415
|
-
...results.map((r) => r.cognitiveComplexity),
|
|
416
|
-
);
|
|
417
|
-
const minMI = Math.min(...results.map((r) => r.maintainabilityIndex));
|
|
418
|
-
const totalFunctions = results.reduce((a, b) => a + b.functionCount, 0);
|
|
419
|
-
const totalLOC = results.reduce((a, b) => a + b.linesOfCode, 0);
|
|
420
|
-
|
|
421
|
-
// Grade distribution
|
|
422
|
-
const grades = results.map((r) => {
|
|
423
|
-
const mi = r.maintainabilityIndex;
|
|
424
|
-
if (mi >= 80) return { letter: "A", color: "🟢" };
|
|
425
|
-
if (mi >= 60) return { letter: "B", color: "🟡" };
|
|
426
|
-
if (mi >= 40) return { letter: "C", color: "🟠" };
|
|
427
|
-
if (mi >= 20) return { letter: "D", color: "🔴" };
|
|
428
|
-
return { letter: "F", color: "⚫" };
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
const gradeCount = { A: 0, B: 0, C: 0, D: 0, F: 0 };
|
|
432
|
-
for (const g of grades) {
|
|
433
|
-
gradeCount[g.letter as keyof typeof gradeCount]++;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// Capture snapshots for history tracking
|
|
437
|
-
const history = captureSnapshots(
|
|
438
|
-
results.map((r) => ({
|
|
439
|
-
filePath: r.filePath,
|
|
440
|
-
metrics: {
|
|
441
|
-
maintainabilityIndex: r.maintainabilityIndex,
|
|
442
|
-
cognitiveComplexity: r.cognitiveComplexity,
|
|
443
|
-
maxNestingDepth: r.maxNestingDepth,
|
|
444
|
-
linesOfCode: r.linesOfCode,
|
|
445
|
-
},
|
|
446
|
-
})),
|
|
447
|
-
);
|
|
448
|
-
|
|
449
|
-
// Build report
|
|
450
|
-
let report = `# Code Metrics Report: ${projectName}\n\n`;
|
|
451
|
-
report += `**Generated:** ${new Date().toISOString()}\n\n`;
|
|
452
|
-
report += `**Path:** \`${targetPath}\`\n\n`;
|
|
453
|
-
report += `---\n\n`;
|
|
454
|
-
|
|
455
|
-
// AI slop aggregates
|
|
456
|
-
const totalAISlopWarnings = results.reduce((a, b) => {
|
|
457
|
-
return a + complexityClient.checkThresholds(b).length;
|
|
458
|
-
}, 0);
|
|
459
|
-
const totalEmojiComments = results.reduce(
|
|
460
|
-
(a, b) => a + b.aiCommentPatterns,
|
|
461
|
-
0,
|
|
462
|
-
);
|
|
463
|
-
const totalTryCatch = results.reduce((a, b) => a + b.tryCatchCount, 0);
|
|
464
|
-
const totalSingleUse = results.reduce(
|
|
465
|
-
(a, b) => a + b.singleUseFunctions,
|
|
466
|
-
0,
|
|
467
|
-
);
|
|
468
|
-
const maxParams = Math.max(...results.map((r) => r.maxParamsInFunction));
|
|
469
|
-
|
|
470
|
-
// Summary
|
|
471
|
-
report += `## Summary\n\n`;
|
|
472
|
-
report += `| Metric | Value |\n`;
|
|
473
|
-
report += `|--------|-------|\n`;
|
|
474
|
-
report += `| Files Analyzed | ${results.length} |\n`;
|
|
475
|
-
report += `| Total Functions | ${totalFunctions} |\n`;
|
|
476
|
-
report += `| Total Lines of Code | ${totalLOC.toLocaleString()} |\n`;
|
|
477
|
-
report += `| Avg Maintainability Index | ${avgMI.toFixed(1)} |\n`;
|
|
478
|
-
report += `| Min Maintainability Index | ${minMI.toFixed(1)} |\n`;
|
|
479
|
-
report += `| Avg Cognitive Complexity | ${avgCognitive.toFixed(1)} |\n`;
|
|
480
|
-
report += `| Max Cognitive Complexity | ${maxCognitive} |\n`;
|
|
481
|
-
report += `| Avg Cyclomatic Complexity | ${avgCyclomatic.toFixed(1)} |\n`;
|
|
482
|
-
report += `| Max Nesting Depth | ${maxNesting} |\n`;
|
|
483
|
-
report += `| Avg Function Length | ${avgFunctionLength.toFixed(1)} lines |\n\n`;
|
|
484
|
-
|
|
485
|
-
// AI Slop Summary
|
|
486
|
-
report += `## AI Slop Indicators (Aggregate)\n\n`;
|
|
487
|
-
report += `| Indicator | Count |\n`;
|
|
488
|
-
report += `|-----------|-------|\n`;
|
|
489
|
-
report += `| Total Warnings | ${totalAISlopWarnings} |\n`;
|
|
490
|
-
report += `| Emoji/Boilerplate Comments | ${totalEmojiComments} |\n`;
|
|
491
|
-
report += `| Try/Catch Blocks | ${totalTryCatch} |\n`;
|
|
492
|
-
report += `| Single-Use Helper Functions | ${totalSingleUse} |\n`;
|
|
493
|
-
report += `| Max Function Parameters | ${maxParams} |\n\n`;
|
|
494
|
-
|
|
495
|
-
// Grade distribution
|
|
496
|
-
report += `## Maintainability Grade Distribution\n\n`;
|
|
497
|
-
report += `| Grade | Count | Percentage |\n`;
|
|
498
|
-
report += `|-------|-------|------------|\n`;
|
|
499
|
-
for (const [grade, count] of Object.entries(gradeCount)) {
|
|
500
|
-
const pct = ((count / results.length) * 100).toFixed(1);
|
|
501
|
-
const gradeIcons: Record<string, string> = {
|
|
502
|
-
A: "🟢",
|
|
503
|
-
B: "🟡",
|
|
504
|
-
C: "🟠",
|
|
505
|
-
D: "🔴",
|
|
506
|
-
};
|
|
507
|
-
const gradeThresholds: Record<string, number> = {
|
|
508
|
-
A: 80,
|
|
509
|
-
B: 60,
|
|
510
|
-
C: 40,
|
|
511
|
-
D: 20,
|
|
512
|
-
};
|
|
513
|
-
const icon = gradeIcons[grade] ?? "⚫";
|
|
514
|
-
const threshold = gradeThresholds[grade] ?? 0;
|
|
515
|
-
report += `| ${icon} ${grade} (MI ≥ ${threshold}) | ${count} | ${pct}% |\n`;
|
|
516
|
-
}
|
|
517
|
-
report += `\n`;
|
|
518
|
-
|
|
519
|
-
// All files table (sorted by MI ascending)
|
|
520
|
-
report += `## All Files\n\n`;
|
|
521
|
-
report += `| Grade | File | MI | Cognitive | LOC | Entropy | Trend |\n`;
|
|
522
|
-
report += `|-------|------|-----|-----------|-----|---------|-------|\n`;
|
|
523
|
-
|
|
524
|
-
const sorted = [...results].sort(
|
|
525
|
-
(a, b) => a.maintainabilityIndex - b.maintainabilityIndex,
|
|
526
|
-
);
|
|
527
|
-
for (const f of sorted) {
|
|
528
|
-
const mi = f.maintainabilityIndex;
|
|
529
|
-
let grade: string;
|
|
530
|
-
if (mi >= 80) grade = "🟢 A";
|
|
531
|
-
else if (mi >= 60) grade = "🟡 B";
|
|
532
|
-
else if (mi >= 40) grade = "🟠 C";
|
|
533
|
-
else if (mi >= 20) grade = "🔴 D";
|
|
534
|
-
else grade = "⚫ F";
|
|
535
|
-
|
|
536
|
-
// Make path relative for readability
|
|
537
|
-
const relPath = path.relative(targetPath, f.filePath);
|
|
538
|
-
const trendCell = formatTrendCell(f.filePath, history);
|
|
539
|
-
const entropyCell = f.codeEntropy > 0 ? f.codeEntropy.toFixed(2) : "—";
|
|
540
|
-
|
|
541
|
-
report += `| ${grade} | ${relPath} | ${mi.toFixed(1)} | ${f.cognitiveComplexity} | ${f.linesOfCode} | ${entropyCell} | ${trendCell} |\n`;
|
|
542
|
-
}
|
|
543
|
-
report += `\n`;
|
|
544
|
-
|
|
545
|
-
// Trend Summary
|
|
546
|
-
const trendSummary = getTrendSummary(history);
|
|
547
|
-
report += `## Trend Summary\n\n`;
|
|
548
|
-
report += `| Trend | Count |\n`;
|
|
549
|
-
report += `|-------|-------|\n`;
|
|
550
|
-
report += `| 📈 Improving | ${trendSummary.improving} |\n`;
|
|
551
|
-
report += `| ➡️ Stable | ${trendSummary.stable} |\n`;
|
|
552
|
-
report += `| 📉 Regressing | ${trendSummary.regressing} |\n\n`;
|
|
553
|
-
|
|
554
|
-
if (trendSummary.worstRegressions.length > 0) {
|
|
555
|
-
report += `### Top Regressions\n\n`;
|
|
556
|
-
report += `Files with largest MI decline since last scan:\n\n`;
|
|
557
|
-
for (const r of trendSummary.worstRegressions) {
|
|
558
|
-
report += `- **${r.file}**: MI ${r.miDelta > 0 ? "+" : ""}${r.miDelta}\n`;
|
|
559
|
-
}
|
|
560
|
-
report += `\n`;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Top 10 worst files (actionable)
|
|
564
|
-
report += `## Top 10 Files Needing Attention\n\n`;
|
|
565
|
-
report += `These files have the lowest maintainability scores:\n\n`;
|
|
566
|
-
for (let i = 0; i < Math.min(10, sorted.length); i++) {
|
|
567
|
-
const f = sorted[i];
|
|
568
|
-
const relPath = path.relative(targetPath, f.filePath);
|
|
569
|
-
const warnings: string[] = [];
|
|
570
|
-
|
|
571
|
-
if (f.maintainabilityIndex < 20) warnings.push("🔴 Critical: MI < 20");
|
|
572
|
-
else if (f.maintainabilityIndex < 40) warnings.push("🟠 Low: MI < 40");
|
|
573
|
-
if (f.cognitiveComplexity > 50)
|
|
574
|
-
warnings.push(`High cognitive (${f.cognitiveComplexity})`);
|
|
575
|
-
if (f.maxNestingDepth > 5)
|
|
576
|
-
warnings.push(`Deep nesting (${f.maxNestingDepth})`);
|
|
577
|
-
if (f.maxFunctionLength > 50)
|
|
578
|
-
warnings.push(`Long functions (max ${f.maxFunctionLength})`);
|
|
579
|
-
|
|
580
|
-
// AI slop indicators
|
|
581
|
-
const slopWarnings = complexityClient.checkThresholds(f);
|
|
582
|
-
for (const w of slopWarnings) {
|
|
583
|
-
if (
|
|
584
|
-
w.includes("AI-style") ||
|
|
585
|
-
w.includes("try/catch") ||
|
|
586
|
-
w.includes("single-use") ||
|
|
587
|
-
w.includes("parameter list")
|
|
588
|
-
) {
|
|
589
|
-
warnings.push(`🤖 ${w.split(" — ")[0]}`);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
report += `${i + 1}. **${relPath}** — MI: ${f.maintainabilityIndex.toFixed(1)}\n`;
|
|
594
|
-
if (warnings.length > 0) {
|
|
595
|
-
report += ` - ${warnings.join(", ")}\n`;
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
report += `\n`;
|
|
599
|
-
|
|
600
|
-
// Save report
|
|
601
|
-
nodeFs.mkdirSync(reviewDir, { recursive: true });
|
|
602
|
-
|
|
603
|
-
const reportPath = path.join(reviewDir, `metrics-${timestamp}.md`);
|
|
604
|
-
nodeFs.writeFileSync(reportPath, report, "utf-8");
|
|
605
|
-
|
|
606
|
-
// Also save latest.md for easy access
|
|
607
|
-
const latestPath = path.join(reviewDir, "latest.md");
|
|
608
|
-
nodeFs.writeFileSync(latestPath, report, "utf-8");
|
|
609
|
-
|
|
610
|
-
// Console summary
|
|
611
|
-
const summary = [
|
|
612
|
-
`📊 Metrics Report`,
|
|
613
|
-
` ${results.length} files, ${totalLOC.toLocaleString()} LOC, ${totalFunctions} functions`,
|
|
614
|
-
` MI: ${avgMI.toFixed(1)} avg (${gradeCount.A}A ${gradeCount.B}B ${gradeCount.C}C ${gradeCount.D}D ${gradeCount.F}F)`,
|
|
615
|
-
` Cognitive: ${avgCognitive.toFixed(1)} avg, ${maxCognitive} max`,
|
|
616
|
-
`📄 Saved: ${reportPath}`,
|
|
617
|
-
].join("\n");
|
|
618
|
-
|
|
619
|
-
ctx.ui.notify(summary, "info");
|
|
620
|
-
},
|
|
621
|
-
});
|
|
622
|
-
|
|
623
|
-
pi.registerCommand("lens-rate", {
|
|
624
|
-
description:
|
|
625
|
-
"Show code quality score with visual breakdown. Usage: /lens-rate [path]",
|
|
626
|
-
handler: async (args, ctx) => {
|
|
627
|
-
const result = await handleRate(args, ctx, {
|
|
628
|
-
complexity: complexityClient,
|
|
629
|
-
knip: knipClient,
|
|
630
|
-
typeCoverage: typeCoverageClient,
|
|
631
|
-
architect: architectClient,
|
|
632
|
-
});
|
|
633
|
-
ctx.ui.notify(result, "info");
|
|
634
|
-
},
|
|
635
|
-
});
|
|
306
|
+
// DISABLED: lens-booboo-fix command - disabled per user request
|
|
636
307
|
|
|
637
308
|
pi.registerCommand("lens-tdi", {
|
|
638
309
|
description:
|
|
@@ -914,13 +585,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
914
585
|
// Delta baselines: store pre-write diagnostics to diff against post-write
|
|
915
586
|
const astGrepBaselines = new Map<
|
|
916
587
|
string,
|
|
917
|
-
import("./clients/ast-grep-
|
|
588
|
+
import("./clients/ast-grep-types.js").AstGrepDiagnostic[]
|
|
918
589
|
>();
|
|
919
590
|
const biomeBaselines = new Map<
|
|
920
591
|
string,
|
|
921
592
|
import("./clients/biome-client.js").BiomeDiagnostic[]
|
|
922
593
|
>();
|
|
923
594
|
|
|
595
|
+
// Track files already auto-fixed this turn to prevent fix loops
|
|
596
|
+
const fixedThisTurn = new Set<string>();
|
|
597
|
+
|
|
924
598
|
// Project rules scan result (from .claude/rules, .agents/rules, etc.)
|
|
925
599
|
let projectRulesScan: RuleScanResult = { rules: [], hasCustomRules: false };
|
|
926
600
|
|
|
@@ -930,6 +604,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
930
604
|
_verbose = !!pi.getFlag("lens-verbose");
|
|
931
605
|
dbg("session_start fired");
|
|
932
606
|
|
|
607
|
+
// Initialize event bus system (Phase 1)
|
|
608
|
+
const busEnabled = pi.getFlag("lens-bus");
|
|
609
|
+
if (busEnabled) {
|
|
610
|
+
const busDebug = pi.getFlag("lens-bus-debug");
|
|
611
|
+
initBusIntegration(pi, { debug: !!busDebug });
|
|
612
|
+
if (busDebug) enableBusDebug(true);
|
|
613
|
+
SessionStarted.publish({
|
|
614
|
+
cwd: ctx.cwd ?? process.cwd(),
|
|
615
|
+
timestamp: Date.now(),
|
|
616
|
+
});
|
|
617
|
+
dbg("session_start: bus integration initialized");
|
|
618
|
+
}
|
|
619
|
+
|
|
933
620
|
// Reset session state
|
|
934
621
|
metricsClient.reset();
|
|
935
622
|
complexityBaselines.clear();
|
|
@@ -948,6 +635,23 @@ export default function (pi: ExtensionAPI) {
|
|
|
948
635
|
log(`Active tools: ${tools.join(", ")}`);
|
|
949
636
|
dbg(`session_start tools: ${tools.join(", ")}`);
|
|
950
637
|
|
|
638
|
+
// Pre-install TypeScript LSP if --lens-lsp flag is set (avoid delay on first use)
|
|
639
|
+
if (pi.getFlag("lens-lsp")) {
|
|
640
|
+
dbg("session_start: pre-installing TypeScript LSP...");
|
|
641
|
+
// Fire-and-forget: don't block session start, just warm up the cache
|
|
642
|
+
ensureTool("typescript-language-server")
|
|
643
|
+
.then((path) => {
|
|
644
|
+
if (path) {
|
|
645
|
+
dbg(`session_start: TypeScript LSP ready at ${path}`);
|
|
646
|
+
} else {
|
|
647
|
+
console.error("[lens] TypeScript LSP installation failed");
|
|
648
|
+
}
|
|
649
|
+
})
|
|
650
|
+
.catch((err) => {
|
|
651
|
+
console.error("[lens] TypeScript LSP pre-install error:", err);
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
|
|
951
655
|
const cwd = ctx.cwd ?? process.cwd();
|
|
952
656
|
projectRoot = cwd; // Module-level for architect client
|
|
953
657
|
dbg(`session_start cwd: ${cwd}`);
|
|
@@ -1160,10 +864,27 @@ export default function (pi: ExtensionAPI) {
|
|
|
1160
864
|
if (/\.(ts|tsx|js|jsx)$/.test(filePath) && !pi.getFlag("no-lsp")) {
|
|
1161
865
|
tsClient.updateFile(filePath, nodeFs.readFileSync(filePath, "utf-8"));
|
|
1162
866
|
const diags = tsClient.getDiagnostics(filePath);
|
|
867
|
+
const fixes = tsClient.getAllCodeFixes(filePath);
|
|
1163
868
|
if (diags.length > 0) {
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
869
|
+
const errorDiags = diags.filter((d) => d.severity === 1);
|
|
870
|
+
if (errorDiags.length > 0) {
|
|
871
|
+
hints.push(
|
|
872
|
+
`⚠ Pre-write: file has ${errorDiags.length} TypeScript error(s):`,
|
|
873
|
+
);
|
|
874
|
+
// Show first 3 errors with quick fixes
|
|
875
|
+
for (const d of errorDiags.slice(0, 3)) {
|
|
876
|
+
const lineFixes = fixes.get(d.range.start.line);
|
|
877
|
+
const fixHint = lineFixes?.[0]?.description
|
|
878
|
+
? ` 💡 ${lineFixes[0].description}`
|
|
879
|
+
: "";
|
|
880
|
+
hints.push(
|
|
881
|
+
` L${d.range.start.line + 1}: ${d.message.split("\n")[0].substring(0, 80)}${fixHint}`,
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
if (errorDiags.length > 3) {
|
|
885
|
+
hints.push(` ... and ${errorDiags.length - 3} more errors`);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
1167
888
|
}
|
|
1168
889
|
}
|
|
1169
890
|
|
|
@@ -1195,17 +916,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
1195
916
|
);
|
|
1196
917
|
}
|
|
1197
918
|
|
|
1198
|
-
// Architectural rules pre-write hints
|
|
1199
|
-
|
|
1200
|
-
const relPath = path.relative(projectRoot, filePath).replace(/\\/g, "/");
|
|
1201
|
-
const archHints = architectClient.getHints(relPath);
|
|
1202
|
-
if (archHints.length > 0) {
|
|
1203
|
-
hints.push(`📐 Architectural rules for ${relPath}:`);
|
|
1204
|
-
for (const h of archHints) {
|
|
1205
|
-
hints.push(` → ${h}`);
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
919
|
+
// Architectural rules: Skip pre-write hints (too noisy)
|
|
920
|
+
// Post-write violations will be shown via architect runner in dispatch
|
|
1209
921
|
|
|
1210
922
|
dbg(` pre-write hints: ${hints.length} — ${hints.join(" | ") || "none"}`);
|
|
1211
923
|
if (hints.length > 0) {
|
|
@@ -1301,6 +1013,58 @@ export default function (pi: ExtensionAPI) {
|
|
|
1301
1013
|
void err;
|
|
1302
1014
|
}
|
|
1303
1015
|
|
|
1016
|
+
// --- Auto-format on write (default enabled) ---
|
|
1017
|
+
// Runs detected formatters concurrently via Effect-TS
|
|
1018
|
+
let formatChanged = false;
|
|
1019
|
+
if (!pi.getFlag("no-autoformat") && fileContent) {
|
|
1020
|
+
const formatService = getFormatService();
|
|
1021
|
+
try {
|
|
1022
|
+
// Record file read to establish FileTime baseline before formatting
|
|
1023
|
+
// This prevents "modified externally" false positives when agent writes file
|
|
1024
|
+
formatService.recordRead(filePath);
|
|
1025
|
+
const result = await formatService.formatFile(filePath);
|
|
1026
|
+
if (result.anyChanged) {
|
|
1027
|
+
formatChanged = true;
|
|
1028
|
+
dbg(
|
|
1029
|
+
`autoformat: ${result.formatters.map((f) => `${f.name}(${f.changed ? "changed" : "unchanged"})`).join(", ")}`,
|
|
1030
|
+
);
|
|
1031
|
+
// Re-read content after formatting for downstream processing
|
|
1032
|
+
fileContent = nodeFs.readFileSync(filePath, "utf-8");
|
|
1033
|
+
}
|
|
1034
|
+
} catch (err) {
|
|
1035
|
+
dbg(`autoformat error: ${err}`);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// --- Publish file modified event to bus (Phase 1) ---
|
|
1040
|
+
if (pi.getFlag("lens-bus")) {
|
|
1041
|
+
FileModified.publish({
|
|
1042
|
+
filePath,
|
|
1043
|
+
content: fileContent,
|
|
1044
|
+
changeType: event.toolName === "edit" ? "edit" : "write",
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// --- LSP integration (Phase 3) ---
|
|
1049
|
+
if (pi.getFlag("lens-lsp") && fileContent) {
|
|
1050
|
+
const lspService = getLSPService();
|
|
1051
|
+
lspService
|
|
1052
|
+
.hasLSP(filePath)
|
|
1053
|
+
.then(async (hasLSP) => {
|
|
1054
|
+
if (hasLSP) {
|
|
1055
|
+
// Open or update file in LSP
|
|
1056
|
+
if (event.toolName === "write") {
|
|
1057
|
+
await lspService.openFile(filePath, fileContent);
|
|
1058
|
+
} else {
|
|
1059
|
+
await lspService.updateFile(filePath, fileContent);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
})
|
|
1063
|
+
.catch((err) => {
|
|
1064
|
+
dbg(`LSP error: ${err}`);
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1304
1068
|
// --- Secrets scan (blocking - must check before other linting) ---
|
|
1305
1069
|
if (fileContent) {
|
|
1306
1070
|
const secretFindings = scanForSecrets(fileContent, filePath);
|
|
@@ -1318,14 +1082,142 @@ export default function (pi: ExtensionAPI) {
|
|
|
1318
1082
|
|
|
1319
1083
|
let lspOutput = preHint ? `\n\n${preHint}` : "";
|
|
1320
1084
|
|
|
1085
|
+
// --- Auto-fix on write (safely - track to prevent loops) ---
|
|
1086
|
+
// Apply fixes BEFORE dispatch so dispatch only reports remaining issues
|
|
1087
|
+
// Autofix is enabled by default, use --no-autofix to disable
|
|
1088
|
+
const noAutofix = pi.getFlag("no-autofix");
|
|
1089
|
+
const noAutofixBiome = pi.getFlag("no-autofix-biome");
|
|
1090
|
+
const noAutofixRuff = pi.getFlag("no-autofix-ruff");
|
|
1091
|
+
let fixedCount = 0;
|
|
1092
|
+
|
|
1093
|
+
if (!fixedThisTurn.has(filePath) && !noAutofix) {
|
|
1094
|
+
// Python: Ruff auto-fix (enabled by default)
|
|
1095
|
+
if (
|
|
1096
|
+
!noAutofixRuff &&
|
|
1097
|
+
ruffClient.isAvailable() &&
|
|
1098
|
+
ruffClient.isPythonFile(filePath)
|
|
1099
|
+
) {
|
|
1100
|
+
const result = ruffClient.fixFile(filePath);
|
|
1101
|
+
if (result.success && result.fixed > 0) {
|
|
1102
|
+
fixedCount += result.fixed;
|
|
1103
|
+
fixedThisTurn.add(filePath);
|
|
1104
|
+
dbg(`autofix: ruff fixed ${result.fixed} issue(s) in ${filePath}`);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// JS/TS/JSON: Biome auto-fix (enabled by default)
|
|
1109
|
+
if (
|
|
1110
|
+
!noAutofixBiome &&
|
|
1111
|
+
biomeClient.isAvailable() &&
|
|
1112
|
+
biomeClient.isSupportedFile(filePath)
|
|
1113
|
+
) {
|
|
1114
|
+
const result = biomeClient.fixFile(filePath);
|
|
1115
|
+
if (result.success && result.fixed > 0) {
|
|
1116
|
+
fixedCount += result.fixed;
|
|
1117
|
+
fixedThisTurn.add(filePath);
|
|
1118
|
+
dbg(`autofix: biome fixed ${result.fixed} issue(s) in ${filePath}`);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1321
1123
|
// --- Declarative dispatch: run all applicable lint tools ---
|
|
1322
1124
|
// Phase 2: Replaced ~400 lines of if/else with unified dispatch system
|
|
1323
1125
|
dbg(`dispatch: running lint tools for ${filePath}`);
|
|
1324
|
-
|
|
1126
|
+
|
|
1127
|
+
// Select dispatcher based on flags:
|
|
1128
|
+
// - lens-effect: Effect-TS concurrent execution (Phase 2)
|
|
1129
|
+
// - lens-bus: Bus-enabled dispatcher (Phase 1)
|
|
1130
|
+
// - default: Original sequential dispatcher
|
|
1131
|
+
let dispatchOutput: string;
|
|
1132
|
+
if (pi.getFlag("lens-effect")) {
|
|
1133
|
+
dispatchOutput = await dispatchLintWithEffect(filePath, projectRoot, pi);
|
|
1134
|
+
} else if (pi.getFlag("lens-bus")) {
|
|
1135
|
+
dispatchOutput = await dispatchLintWithBus(filePath, projectRoot, pi);
|
|
1136
|
+
} else {
|
|
1137
|
+
dispatchOutput = await dispatchLint(filePath, projectRoot, pi);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1325
1140
|
if (dispatchOutput) {
|
|
1326
1141
|
lspOutput += `\n\n${dispatchOutput}`;
|
|
1327
1142
|
}
|
|
1328
1143
|
|
|
1144
|
+
// Report autofix results
|
|
1145
|
+
if (fixedCount > 0) {
|
|
1146
|
+
lspOutput += `\n\n✅ Auto-fixed ${fixedCount} issue(s) in ${path.basename(filePath)}`;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// Warn agent if file was modified by auto-format or auto-fix
|
|
1150
|
+
// This ensures they know to re-read before next edit
|
|
1151
|
+
if (formatChanged || fixedCount > 0) {
|
|
1152
|
+
lspOutput += `\n\n⚠️ **File modified by auto-format/fix. Re-read before next edit.**`;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// --- Test runner: run corresponding tests on write ---
|
|
1156
|
+
if (!pi.getFlag("no-tests")) {
|
|
1157
|
+
const testInfo = testRunnerClient.findTestFile(filePath, cwd);
|
|
1158
|
+
if (testInfo) {
|
|
1159
|
+
dbg(
|
|
1160
|
+
`test-runner: found test file ${testInfo.testFile} for ${filePath}`,
|
|
1161
|
+
);
|
|
1162
|
+
const detectedRunner = testRunnerClient.detectRunner(cwd);
|
|
1163
|
+
if (detectedRunner) {
|
|
1164
|
+
const testResult = testRunnerClient.runTestFile(
|
|
1165
|
+
testInfo.testFile,
|
|
1166
|
+
cwd,
|
|
1167
|
+
detectedRunner.runner,
|
|
1168
|
+
detectedRunner.config,
|
|
1169
|
+
);
|
|
1170
|
+
if (testResult && !testResult.error) {
|
|
1171
|
+
const testOutput = testRunnerClient.formatResult(testResult);
|
|
1172
|
+
if (testOutput) {
|
|
1173
|
+
lspOutput += `\n\n${testOutput}`;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// --- TypeScript Language Service diagnostics (post-write) ---
|
|
1181
|
+
// Fast semantic analysis for type errors, unused vars, etc.
|
|
1182
|
+
if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) {
|
|
1183
|
+
try {
|
|
1184
|
+
const tsClient = new TypeScriptClient();
|
|
1185
|
+
|
|
1186
|
+
// Track the file in the language service
|
|
1187
|
+
tsClient.addFile(filePath, fileContent || "");
|
|
1188
|
+
|
|
1189
|
+
// Get diagnostics (syntactic + semantic)
|
|
1190
|
+
const diagnostics = tsClient.getDiagnostics(filePath);
|
|
1191
|
+
|
|
1192
|
+
if (diagnostics.length > 0) {
|
|
1193
|
+
// Filter to most important diagnostics
|
|
1194
|
+
const importantDiags = diagnostics.filter((d) => {
|
|
1195
|
+
// Focus on errors and important warnings
|
|
1196
|
+
// Skip noisy ones like "cannot find name" from missing imports
|
|
1197
|
+
const code = (d as any).code;
|
|
1198
|
+
if (code === 2304) return false; // "Cannot find name"
|
|
1199
|
+
if (code === 2307) return false; // "Cannot find module"
|
|
1200
|
+
// DiagnosticSeverity.Error = 1, Warning = 2
|
|
1201
|
+
return d.severity === 1 || (d.severity === 2 && !code);
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
if (importantDiags.length > 0) {
|
|
1205
|
+
const tsOutput = importantDiags
|
|
1206
|
+
.slice(0, 5)
|
|
1207
|
+
.map((d) => {
|
|
1208
|
+
// DiagnosticSeverity.Error = 1
|
|
1209
|
+
const severity = d.severity === 1 ? "🔴" : "🟡";
|
|
1210
|
+
return ` ${severity} [TS${(d as any).code}] ${d.message.split("\n")[0]}`;
|
|
1211
|
+
})
|
|
1212
|
+
.join("\n");
|
|
1213
|
+
lspOutput += `\n\n📐 TypeScript Diagnostics:\n${tsOutput}`;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
} catch (err) {
|
|
1217
|
+
dbg(`typescript-client error: ${err}`);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1329
1221
|
// Agent behavior warnings (blind writes, thrashing)
|
|
1330
1222
|
if (behaviorWarnings.length > 0) {
|
|
1331
1223
|
lspOutput += `\n\n${agentBehaviorClient.formatWarnings(behaviorWarnings)}`;
|
|
@@ -1354,6 +1246,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
1354
1246
|
const turnState = cacheManager.readTurnState(cwd);
|
|
1355
1247
|
const files = Object.keys(turnState.files);
|
|
1356
1248
|
|
|
1249
|
+
// Publish turn ended event to bus (Phase 1)
|
|
1250
|
+
if (pi.getFlag("lens-bus") && files.length > 0) {
|
|
1251
|
+
TurnEnded.publish({
|
|
1252
|
+
cwd,
|
|
1253
|
+
modifiedFiles: files,
|
|
1254
|
+
timestamp: Date.now(),
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1357
1258
|
if (files.length === 0) return;
|
|
1358
1259
|
|
|
1359
1260
|
dbg(
|
|
@@ -1456,5 +1357,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
1456
1357
|
cwd,
|
|
1457
1358
|
);
|
|
1458
1359
|
}
|
|
1360
|
+
|
|
1361
|
+
// Clear fixed tracking so files can be fixed again on next turn
|
|
1362
|
+
fixedThisTurn.clear();
|
|
1363
|
+
|
|
1364
|
+
// --- LSP cleanup on turn end (Phase 3) ---
|
|
1365
|
+
// Only shutdown if no files are being actively edited
|
|
1366
|
+
if (pi.getFlag("lens-lsp") && files.length === 0) {
|
|
1367
|
+
resetLSPService();
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// --- Format service cleanup ---
|
|
1371
|
+
resetFormatService();
|
|
1459
1372
|
});
|
|
1460
1373
|
}
|