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/clients/fix-scanners.js
CHANGED
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
* - scanBiome: Remaining Biome lint issues
|
|
9
9
|
* - scanSlop: AI slop indicators (high complexity patterns)
|
|
10
10
|
*/
|
|
11
|
-
import * as childProcess from "node:child_process";
|
|
12
11
|
import * as nodeFs from "node:fs";
|
|
13
12
|
import * as path from "node:path";
|
|
13
|
+
import { EXCLUDED_DIRS } from "./file-utils.js";
|
|
14
|
+
import { safeSpawn } from "./safe-spawn.js";
|
|
14
15
|
import { shouldIgnoreFile } from "./scan-utils.js";
|
|
15
16
|
const DEBUG_LOG = path.join(process.env.HOME || process.env.USERPROFILE || ".", "pi-lens-debug.log");
|
|
16
17
|
function dbg(msg) {
|
|
@@ -18,8 +19,9 @@ function dbg(msg) {
|
|
|
18
19
|
try {
|
|
19
20
|
nodeFs.appendFileSync(DEBUG_LOG, line);
|
|
20
21
|
}
|
|
21
|
-
catch {
|
|
22
|
-
//
|
|
22
|
+
catch (err) {
|
|
23
|
+
// Debug logging failed, silently ignore to avoid recursive errors
|
|
24
|
+
void err;
|
|
23
25
|
}
|
|
24
26
|
}
|
|
25
27
|
/**
|
|
@@ -53,14 +55,12 @@ export function scanDeadCode(knip, targetPath, isTsProject) {
|
|
|
53
55
|
*/
|
|
54
56
|
export function scanAstGrep(targetPath, isTsProject, configPath) {
|
|
55
57
|
const hasSg = nodeFs.existsSync(path.join(targetPath, "node_modules", ".bin", "sg")) ||
|
|
56
|
-
|
|
57
|
-
encoding: "utf-8",
|
|
58
|
+
safeSpawn("npx", ["sg", "--version"], {
|
|
58
59
|
timeout: 5000,
|
|
59
|
-
shell: true,
|
|
60
60
|
}).status === 0;
|
|
61
61
|
if (!hasSg)
|
|
62
62
|
return [];
|
|
63
|
-
const result =
|
|
63
|
+
const result = safeSpawn("npx", [
|
|
64
64
|
"sg",
|
|
65
65
|
"scan",
|
|
66
66
|
"--config",
|
|
@@ -77,10 +77,7 @@ export function scanAstGrep(targetPath, isTsProject, configPath) {
|
|
|
77
77
|
...(isTsProject ? ["--globs", "!**/*.js"] : []),
|
|
78
78
|
targetPath,
|
|
79
79
|
], {
|
|
80
|
-
encoding: "utf-8",
|
|
81
80
|
timeout: 30000,
|
|
82
|
-
shell: true,
|
|
83
|
-
maxBuffer: 32 * 1024 * 1024,
|
|
84
81
|
});
|
|
85
82
|
const raw = result.stdout?.trim() ?? "";
|
|
86
83
|
const items = raw.startsWith("[")
|
|
@@ -125,13 +122,13 @@ export function scanAstGrep(targetPath, isTsProject, configPath) {
|
|
|
125
122
|
export function scanBiomeIssues(biome, targetPath) {
|
|
126
123
|
if (!biome.isAvailable())
|
|
127
124
|
return [];
|
|
128
|
-
const checkResult =
|
|
125
|
+
const checkResult = safeSpawn("npx", [
|
|
129
126
|
"@biomejs/biome",
|
|
130
127
|
"check",
|
|
131
128
|
"--reporter=json",
|
|
132
129
|
"--max-diagnostics=50",
|
|
133
130
|
targetPath,
|
|
134
|
-
], {
|
|
131
|
+
], { timeout: 20000 });
|
|
135
132
|
const remainingBiome = [];
|
|
136
133
|
try {
|
|
137
134
|
const data = JSON.parse(checkResult.stdout ?? "{}");
|
|
@@ -165,14 +162,7 @@ export function scanSlop(complexity, targetPath, isTsProject) {
|
|
|
165
162
|
for (const entry of nodeFs.readdirSync(dir, { withFileTypes: true })) {
|
|
166
163
|
const fullPath = path.join(dir, entry.name);
|
|
167
164
|
if (entry.isDirectory()) {
|
|
168
|
-
if (
|
|
169
|
-
"node_modules",
|
|
170
|
-
".git",
|
|
171
|
-
"dist",
|
|
172
|
-
"build",
|
|
173
|
-
".next",
|
|
174
|
-
".pi-lens",
|
|
175
|
-
].includes(entry.name))
|
|
165
|
+
if (EXCLUDED_DIRS.includes(entry.name))
|
|
176
166
|
continue;
|
|
177
167
|
scanDir(fullPath);
|
|
178
168
|
}
|
package/clients/fix-scanners.ts
CHANGED
|
@@ -14,8 +14,10 @@ import * as nodeFs from "node:fs";
|
|
|
14
14
|
import * as path from "node:path";
|
|
15
15
|
import type { BiomeClient } from "./biome-client.js";
|
|
16
16
|
import type { ComplexityClient } from "./complexity-client.js";
|
|
17
|
+
import { EXCLUDED_DIRS } from "./file-utils.js";
|
|
17
18
|
import type { JscpdClient } from "./jscpd-client.js";
|
|
18
19
|
import type { KnipClient } from "./knip-client.js";
|
|
20
|
+
import { safeSpawn } from "./safe-spawn.js";
|
|
19
21
|
import { shouldIgnoreFile } from "./scan-utils.js";
|
|
20
22
|
|
|
21
23
|
export interface DuplicateClone {
|
|
@@ -68,8 +70,9 @@ function dbg(msg: string) {
|
|
|
68
70
|
const line = `[${new Date().toISOString()}] ${msg}\n`;
|
|
69
71
|
try {
|
|
70
72
|
nodeFs.appendFileSync(DEBUG_LOG, line);
|
|
71
|
-
} catch {
|
|
72
|
-
//
|
|
73
|
+
} catch (err) {
|
|
74
|
+
// Debug logging failed, silently ignore to avoid recursive errors
|
|
75
|
+
void err;
|
|
73
76
|
}
|
|
74
77
|
}
|
|
75
78
|
|
|
@@ -118,15 +121,13 @@ export function scanAstGrep(
|
|
|
118
121
|
): AstIssue[] {
|
|
119
122
|
const hasSg =
|
|
120
123
|
nodeFs.existsSync(path.join(targetPath, "node_modules", ".bin", "sg")) ||
|
|
121
|
-
|
|
122
|
-
encoding: "utf-8",
|
|
124
|
+
safeSpawn("npx", ["sg", "--version"], {
|
|
123
125
|
timeout: 5000,
|
|
124
|
-
shell: true,
|
|
125
126
|
}).status === 0;
|
|
126
127
|
|
|
127
128
|
if (!hasSg) return [];
|
|
128
129
|
|
|
129
|
-
const result =
|
|
130
|
+
const result = safeSpawn(
|
|
130
131
|
"npx",
|
|
131
132
|
[
|
|
132
133
|
"sg",
|
|
@@ -146,10 +147,7 @@ export function scanAstGrep(
|
|
|
146
147
|
targetPath,
|
|
147
148
|
],
|
|
148
149
|
{
|
|
149
|
-
encoding: "utf-8",
|
|
150
150
|
timeout: 30000,
|
|
151
|
-
shell: true,
|
|
152
|
-
maxBuffer: 32 * 1024 * 1024,
|
|
153
151
|
},
|
|
154
152
|
);
|
|
155
153
|
|
|
@@ -202,7 +200,7 @@ export function scanBiomeIssues(
|
|
|
202
200
|
): BiomeIssue[] {
|
|
203
201
|
if (!biome.isAvailable()) return [];
|
|
204
202
|
|
|
205
|
-
const checkResult =
|
|
203
|
+
const checkResult = safeSpawn(
|
|
206
204
|
"npx",
|
|
207
205
|
[
|
|
208
206
|
"@biomejs/biome",
|
|
@@ -211,7 +209,7 @@ export function scanBiomeIssues(
|
|
|
211
209
|
"--max-diagnostics=50",
|
|
212
210
|
targetPath,
|
|
213
211
|
],
|
|
214
|
-
{
|
|
212
|
+
{ timeout: 20000 },
|
|
215
213
|
);
|
|
216
214
|
|
|
217
215
|
const remainingBiome: BiomeIssue[] = [];
|
|
@@ -251,17 +249,7 @@ export function scanSlop(
|
|
|
251
249
|
for (const entry of nodeFs.readdirSync(dir, { withFileTypes: true })) {
|
|
252
250
|
const fullPath = path.join(dir, entry.name);
|
|
253
251
|
if (entry.isDirectory()) {
|
|
254
|
-
if (
|
|
255
|
-
[
|
|
256
|
-
"node_modules",
|
|
257
|
-
".git",
|
|
258
|
-
"dist",
|
|
259
|
-
"build",
|
|
260
|
-
".next",
|
|
261
|
-
".pi-lens",
|
|
262
|
-
].includes(entry.name)
|
|
263
|
-
)
|
|
264
|
-
continue;
|
|
252
|
+
if (EXCLUDED_DIRS.includes(entry.name)) continue;
|
|
265
253
|
scanDir(fullPath);
|
|
266
254
|
} else if (complexity.isSupportedFile(fullPath)) {
|
|
267
255
|
const metrics = complexity.analyzeFile(fullPath);
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format Service for pi-lens
|
|
3
|
+
*
|
|
4
|
+
* Concurrent formatter execution using Effect-TS.
|
|
5
|
+
* Auto-formats files on write with multiple formatters per file.
|
|
6
|
+
*
|
|
7
|
+
* Key features:
|
|
8
|
+
* - Auto-detects formatters based on project config
|
|
9
|
+
* - Runs multiple formatters concurrently via Effect.all
|
|
10
|
+
* - FileTime integration for safety
|
|
11
|
+
* - Multiple formatters per file (e.g., biome + prettier both run)
|
|
12
|
+
*/
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
import { Effect, pipe } from "effect";
|
|
15
|
+
import { FileTime } from "./file-time.js";
|
|
16
|
+
import { clearFormatterCache, formatFile, getFormattersForFile, } from "./formatters.js";
|
|
17
|
+
// --- Format Service ---
|
|
18
|
+
export class FormatService {
|
|
19
|
+
constructor(sessionID, enabled = true) {
|
|
20
|
+
this.fileTime = new FileTime(sessionID);
|
|
21
|
+
this.enabled = enabled;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Format a file with all detected formatters
|
|
25
|
+
* Runs formatters concurrently via Effect-TS
|
|
26
|
+
*/
|
|
27
|
+
async formatFile(filePath, options = {}) {
|
|
28
|
+
const absolutePath = path.resolve(filePath);
|
|
29
|
+
const cwd = path.dirname(absolutePath);
|
|
30
|
+
// Skip if disabled
|
|
31
|
+
if (options.skip || !this.enabled) {
|
|
32
|
+
return {
|
|
33
|
+
filePath: absolutePath,
|
|
34
|
+
formatters: [],
|
|
35
|
+
anyChanged: false,
|
|
36
|
+
allSucceeded: true,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// Check if file was modified externally (safety check)
|
|
40
|
+
if (this.fileTime.hasChanged(absolutePath)) {
|
|
41
|
+
console.warn(`[format] File ${absolutePath} modified externally, skipping format`);
|
|
42
|
+
return {
|
|
43
|
+
filePath: absolutePath,
|
|
44
|
+
formatters: [],
|
|
45
|
+
anyChanged: false,
|
|
46
|
+
allSucceeded: false,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// Get formatters for this file
|
|
50
|
+
const formatters = options.formatters
|
|
51
|
+
? await this.getFormattersByName(options.formatters)
|
|
52
|
+
: await getFormattersForFile(absolutePath, cwd);
|
|
53
|
+
if (formatters.length === 0) {
|
|
54
|
+
return {
|
|
55
|
+
filePath: absolutePath,
|
|
56
|
+
formatters: [],
|
|
57
|
+
anyChanged: false,
|
|
58
|
+
allSucceeded: true,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// Run all formatters concurrently via Effect-TS
|
|
62
|
+
const results = await this.runFormattersConcurrently(absolutePath, formatters);
|
|
63
|
+
// Record new file state after formatting
|
|
64
|
+
this.fileTime.read(absolutePath);
|
|
65
|
+
// Build summary
|
|
66
|
+
const anyChanged = results.some((r) => r.changed);
|
|
67
|
+
const allSucceeded = results.every((r) => r.success);
|
|
68
|
+
return {
|
|
69
|
+
filePath: absolutePath,
|
|
70
|
+
formatters: results.map((r, i) => ({
|
|
71
|
+
name: formatters[i].name,
|
|
72
|
+
success: r.success,
|
|
73
|
+
changed: r.changed,
|
|
74
|
+
error: r.error,
|
|
75
|
+
})),
|
|
76
|
+
anyChanged,
|
|
77
|
+
allSucceeded,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Run formatters concurrently using Effect-TS
|
|
82
|
+
*/
|
|
83
|
+
async runFormattersConcurrently(filePath, formatters) {
|
|
84
|
+
// Create Effect for each formatter
|
|
85
|
+
const effects = formatters.map((formatter) => Effect.tryPromise({
|
|
86
|
+
try: () => formatFile(filePath, formatter),
|
|
87
|
+
catch: (error) => ({
|
|
88
|
+
success: false,
|
|
89
|
+
changed: false,
|
|
90
|
+
error: error instanceof Error ? error.message : String(error),
|
|
91
|
+
}),
|
|
92
|
+
}));
|
|
93
|
+
// Run all concurrently with Effect.all
|
|
94
|
+
const program = pipe(Effect.all(effects, { concurrency: "unbounded" }), Effect.timeout(30000), // 30s total timeout for all formatters
|
|
95
|
+
Effect.catchAll((error) => {
|
|
96
|
+
console.error("[format] Concurrent formatting failed:", error);
|
|
97
|
+
return Effect.succeed(formatters.map(() => ({
|
|
98
|
+
success: false,
|
|
99
|
+
changed: false,
|
|
100
|
+
error: "Timeout or concurrent execution failed",
|
|
101
|
+
})));
|
|
102
|
+
}));
|
|
103
|
+
return Effect.runPromise(program);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get formatters by name (for explicit formatter selection)
|
|
107
|
+
*/
|
|
108
|
+
async getFormattersByName(names) {
|
|
109
|
+
const { listAllFormatters, ...formatters } = await import("./formatters.js");
|
|
110
|
+
const allNames = listAllFormatters();
|
|
111
|
+
return names
|
|
112
|
+
.filter((name) => allNames.includes(name))
|
|
113
|
+
.map((name) => {
|
|
114
|
+
// Access formatter by name from the exports
|
|
115
|
+
const key = `${name}Formatter`;
|
|
116
|
+
return formatters[key];
|
|
117
|
+
})
|
|
118
|
+
.filter(Boolean);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Assert file hasn't changed before editing
|
|
122
|
+
* Throws FileTimeError if file modified externally
|
|
123
|
+
*/
|
|
124
|
+
assertUnchanged(filePath) {
|
|
125
|
+
this.fileTime.assert(filePath);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Check if file has changed externally
|
|
129
|
+
*/
|
|
130
|
+
hasChanged(filePath) {
|
|
131
|
+
return this.fileTime.hasChanged(filePath);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Record file read (after agent reads file)
|
|
135
|
+
*/
|
|
136
|
+
recordRead(filePath) {
|
|
137
|
+
this.fileTime.read(filePath);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Clear detection cache
|
|
141
|
+
*/
|
|
142
|
+
clearCache() {
|
|
143
|
+
clearFormatterCache();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// --- Singleton Instance ---
|
|
147
|
+
let globalFormatService = null;
|
|
148
|
+
let currentSessionID = null;
|
|
149
|
+
export function getFormatService(sessionID, enabled = true) {
|
|
150
|
+
// Create new instance if:
|
|
151
|
+
// 1. No service exists yet
|
|
152
|
+
// 2. Session ID changed (different session)
|
|
153
|
+
const shouldCreateNew = !globalFormatService || (sessionID && sessionID !== currentSessionID);
|
|
154
|
+
if (shouldCreateNew) {
|
|
155
|
+
globalFormatService = new FormatService(sessionID ?? "default", enabled);
|
|
156
|
+
currentSessionID = sessionID ?? "default";
|
|
157
|
+
}
|
|
158
|
+
return globalFormatService;
|
|
159
|
+
}
|
|
160
|
+
export function resetFormatService() {
|
|
161
|
+
globalFormatService = null;
|
|
162
|
+
currentSessionID = null;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Reset format service and clear all file tracking state.
|
|
166
|
+
* Use this in tests to ensure complete isolation.
|
|
167
|
+
*/
|
|
168
|
+
export function clearFormatServiceAndFileState() {
|
|
169
|
+
resetFormatService();
|
|
170
|
+
}
|
|
171
|
+
// Re-export for convenience
|
|
172
|
+
export { clearAllSessions } from "./file-time.js";
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format Service for pi-lens
|
|
3
|
+
*
|
|
4
|
+
* Concurrent formatter execution using Effect-TS.
|
|
5
|
+
* Auto-formats files on write with multiple formatters per file.
|
|
6
|
+
*
|
|
7
|
+
* Key features:
|
|
8
|
+
* - Auto-detects formatters based on project config
|
|
9
|
+
* - Runs multiple formatters concurrently via Effect.all
|
|
10
|
+
* - FileTime integration for safety
|
|
11
|
+
* - Multiple formatters per file (e.g., biome + prettier both run)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import * as path from "node:path";
|
|
15
|
+
import { Effect, pipe } from "effect";
|
|
16
|
+
import { FileTime } from "./file-time.js";
|
|
17
|
+
import {
|
|
18
|
+
clearFormatterCache,
|
|
19
|
+
type FormatterInfo,
|
|
20
|
+
type FormatterResult,
|
|
21
|
+
formatFile,
|
|
22
|
+
getFormattersForFile,
|
|
23
|
+
} from "./formatters.js";
|
|
24
|
+
|
|
25
|
+
// --- Types ---
|
|
26
|
+
|
|
27
|
+
export interface FormatOptions {
|
|
28
|
+
/** Skip auto-format even if enabled (manual mode) */
|
|
29
|
+
skip?: boolean;
|
|
30
|
+
/** Specific formatters to use (overrides detection) */
|
|
31
|
+
formatters?: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface FormatSummary {
|
|
35
|
+
filePath: string;
|
|
36
|
+
formatters: Array<{
|
|
37
|
+
name: string;
|
|
38
|
+
success: boolean;
|
|
39
|
+
changed: boolean;
|
|
40
|
+
error?: string;
|
|
41
|
+
}>;
|
|
42
|
+
anyChanged: boolean;
|
|
43
|
+
allSucceeded: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// --- Format Service ---
|
|
47
|
+
|
|
48
|
+
export class FormatService {
|
|
49
|
+
private fileTime: FileTime;
|
|
50
|
+
private enabled: boolean;
|
|
51
|
+
|
|
52
|
+
constructor(sessionID: string, enabled: boolean = true) {
|
|
53
|
+
this.fileTime = new FileTime(sessionID);
|
|
54
|
+
this.enabled = enabled;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Format a file with all detected formatters
|
|
59
|
+
* Runs formatters concurrently via Effect-TS
|
|
60
|
+
*/
|
|
61
|
+
async formatFile(
|
|
62
|
+
filePath: string,
|
|
63
|
+
options: FormatOptions = {},
|
|
64
|
+
): Promise<FormatSummary> {
|
|
65
|
+
const absolutePath = path.resolve(filePath);
|
|
66
|
+
const cwd = path.dirname(absolutePath);
|
|
67
|
+
|
|
68
|
+
// Skip if disabled
|
|
69
|
+
if (options.skip || !this.enabled) {
|
|
70
|
+
return {
|
|
71
|
+
filePath: absolutePath,
|
|
72
|
+
formatters: [],
|
|
73
|
+
anyChanged: false,
|
|
74
|
+
allSucceeded: true,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check if file was modified externally (safety check)
|
|
79
|
+
if (this.fileTime.hasChanged(absolutePath)) {
|
|
80
|
+
console.warn(
|
|
81
|
+
`[format] File ${absolutePath} modified externally, skipping format`,
|
|
82
|
+
);
|
|
83
|
+
return {
|
|
84
|
+
filePath: absolutePath,
|
|
85
|
+
formatters: [],
|
|
86
|
+
anyChanged: false,
|
|
87
|
+
allSucceeded: false,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Get formatters for this file
|
|
92
|
+
const formatters = options.formatters
|
|
93
|
+
? await this.getFormattersByName(options.formatters)
|
|
94
|
+
: await getFormattersForFile(absolutePath, cwd);
|
|
95
|
+
|
|
96
|
+
if (formatters.length === 0) {
|
|
97
|
+
return {
|
|
98
|
+
filePath: absolutePath,
|
|
99
|
+
formatters: [],
|
|
100
|
+
anyChanged: false,
|
|
101
|
+
allSucceeded: true,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Run all formatters concurrently via Effect-TS
|
|
106
|
+
const results = await this.runFormattersConcurrently(
|
|
107
|
+
absolutePath,
|
|
108
|
+
formatters,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// Record new file state after formatting
|
|
112
|
+
this.fileTime.read(absolutePath);
|
|
113
|
+
|
|
114
|
+
// Build summary
|
|
115
|
+
const anyChanged = results.some((r) => r.changed);
|
|
116
|
+
const allSucceeded = results.every((r) => r.success);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
filePath: absolutePath,
|
|
120
|
+
formatters: results.map((r, i) => ({
|
|
121
|
+
name: formatters[i].name,
|
|
122
|
+
success: r.success,
|
|
123
|
+
changed: r.changed,
|
|
124
|
+
error: r.error,
|
|
125
|
+
})),
|
|
126
|
+
anyChanged,
|
|
127
|
+
allSucceeded,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Run formatters concurrently using Effect-TS
|
|
133
|
+
*/
|
|
134
|
+
private async runFormattersConcurrently(
|
|
135
|
+
filePath: string,
|
|
136
|
+
formatters: FormatterInfo[],
|
|
137
|
+
): Promise<FormatterResult[]> {
|
|
138
|
+
// Create Effect for each formatter
|
|
139
|
+
const effects = formatters.map((formatter) =>
|
|
140
|
+
Effect.tryPromise({
|
|
141
|
+
try: () => formatFile(filePath, formatter),
|
|
142
|
+
catch: (error): FormatterResult => ({
|
|
143
|
+
success: false,
|
|
144
|
+
changed: false,
|
|
145
|
+
error: error instanceof Error ? error.message : String(error),
|
|
146
|
+
}),
|
|
147
|
+
}),
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// Run all concurrently with Effect.all
|
|
151
|
+
const program = pipe(
|
|
152
|
+
Effect.all(effects, { concurrency: "unbounded" }),
|
|
153
|
+
Effect.timeout(30000), // 30s total timeout for all formatters
|
|
154
|
+
Effect.catchAll((error): Effect.Effect<FormatterResult[]> => {
|
|
155
|
+
console.error("[format] Concurrent formatting failed:", error);
|
|
156
|
+
return Effect.succeed(
|
|
157
|
+
formatters.map(() => ({
|
|
158
|
+
success: false,
|
|
159
|
+
changed: false,
|
|
160
|
+
error: "Timeout or concurrent execution failed",
|
|
161
|
+
})),
|
|
162
|
+
);
|
|
163
|
+
}),
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
return Effect.runPromise(program);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get formatters by name (for explicit formatter selection)
|
|
171
|
+
*/
|
|
172
|
+
private async getFormattersByName(names: string[]): Promise<FormatterInfo[]> {
|
|
173
|
+
const { listAllFormatters, ...formatters } = await import(
|
|
174
|
+
"./formatters.js"
|
|
175
|
+
);
|
|
176
|
+
const allNames = listAllFormatters();
|
|
177
|
+
|
|
178
|
+
return names
|
|
179
|
+
.filter((name) => allNames.includes(name))
|
|
180
|
+
.map((name) => {
|
|
181
|
+
// Access formatter by name from the exports
|
|
182
|
+
const key = `${name}Formatter` as keyof typeof formatters;
|
|
183
|
+
return formatters[key] as FormatterInfo;
|
|
184
|
+
})
|
|
185
|
+
.filter(Boolean);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Assert file hasn't changed before editing
|
|
190
|
+
* Throws FileTimeError if file modified externally
|
|
191
|
+
*/
|
|
192
|
+
assertUnchanged(filePath: string): void {
|
|
193
|
+
this.fileTime.assert(filePath);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Check if file has changed externally
|
|
198
|
+
*/
|
|
199
|
+
hasChanged(filePath: string): boolean {
|
|
200
|
+
return this.fileTime.hasChanged(filePath);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Record file read (after agent reads file)
|
|
205
|
+
*/
|
|
206
|
+
recordRead(filePath: string): void {
|
|
207
|
+
this.fileTime.read(filePath);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Clear detection cache
|
|
212
|
+
*/
|
|
213
|
+
clearCache(): void {
|
|
214
|
+
clearFormatterCache();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// --- Singleton Instance ---
|
|
219
|
+
|
|
220
|
+
let globalFormatService: FormatService | null = null;
|
|
221
|
+
let currentSessionID: string | null = null;
|
|
222
|
+
|
|
223
|
+
export function getFormatService(
|
|
224
|
+
sessionID?: string,
|
|
225
|
+
enabled: boolean = true,
|
|
226
|
+
): FormatService {
|
|
227
|
+
// Create new instance if:
|
|
228
|
+
// 1. No service exists yet
|
|
229
|
+
// 2. Session ID changed (different session)
|
|
230
|
+
const shouldCreateNew =
|
|
231
|
+
!globalFormatService || (sessionID && sessionID !== currentSessionID);
|
|
232
|
+
|
|
233
|
+
if (shouldCreateNew) {
|
|
234
|
+
globalFormatService = new FormatService(sessionID ?? "default", enabled);
|
|
235
|
+
currentSessionID = sessionID ?? "default";
|
|
236
|
+
}
|
|
237
|
+
return globalFormatService!;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function resetFormatService(): void {
|
|
241
|
+
globalFormatService = null;
|
|
242
|
+
currentSessionID = null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Reset format service and clear all file tracking state.
|
|
247
|
+
* Use this in tests to ensure complete isolation.
|
|
248
|
+
*/
|
|
249
|
+
export function clearFormatServiceAndFileState(): void {
|
|
250
|
+
resetFormatService();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Re-export for convenience
|
|
254
|
+
export { clearAllSessions } from "./file-time.js";
|