pi-lens 3.6.2 → 3.6.4
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 +10 -2
- package/package.json +4 -4
- package/tsconfig.json +1 -1
- package/clients/__tests__/file-time.test.js +0 -216
- package/clients/__tests__/file-time.test.ts +0 -276
- package/clients/__tests__/format-service.test.js +0 -245
- package/clients/__tests__/format-service.test.ts +0 -339
- package/clients/__tests__/formatters.test.js +0 -271
- package/clients/__tests__/formatters.test.ts +0 -401
- package/clients/agent-behavior-client.js +0 -110
- package/clients/agent-behavior-client.test.js +0 -94
- package/clients/agent-behavior-client.test.ts +0 -116
- package/clients/amain-types.js +0 -164
- package/clients/architect-client.js +0 -291
- package/clients/ast-grep-client.js +0 -253
- package/clients/ast-grep-parser.js +0 -84
- package/clients/ast-grep-rule-manager.js +0 -89
- package/clients/ast-grep-types.js +0 -9
- package/clients/auto-loop.js +0 -131
- package/clients/biome-client.js +0 -420
- package/clients/biome-client.test.js +0 -144
- package/clients/biome-client.test.ts +0 -163
- package/clients/cache/rule-cache.js +0 -72
- package/clients/cache-manager.js +0 -245
- package/clients/cache-manager.test.js +0 -197
- package/clients/cache-manager.test.ts +0 -299
- package/clients/complexity-client.js +0 -675
- package/clients/complexity-client.test.js +0 -234
- package/clients/complexity-client.test.ts +0 -255
- package/clients/config-validator.js +0 -465
- package/clients/dependency-checker.js +0 -325
- package/clients/dependency-checker.test.js +0 -60
- package/clients/dependency-checker.test.ts +0 -71
- package/clients/dispatch/__tests__/autofix-integration.test.js +0 -245
- package/clients/dispatch/__tests__/autofix-integration.test.ts +0 -300
- package/clients/dispatch/__tests__/runner-registration.test.js +0 -234
- package/clients/dispatch/__tests__/runner-registration.test.ts +0 -286
- package/clients/dispatch/debug.log +0 -1
- package/clients/dispatch/dispatcher.edge.test.js +0 -82
- package/clients/dispatch/dispatcher.edge.test.ts +0 -100
- package/clients/dispatch/dispatcher.format.test.js +0 -46
- package/clients/dispatch/dispatcher.format.test.ts +0 -58
- package/clients/dispatch/dispatcher.inline.test.js +0 -74
- package/clients/dispatch/dispatcher.inline.test.ts +0 -93
- package/clients/dispatch/dispatcher.js +0 -381
- package/clients/dispatch/dispatcher.test.js +0 -116
- package/clients/dispatch/dispatcher.test.ts +0 -149
- package/clients/dispatch/integration.js +0 -108
- package/clients/dispatch/plan.js +0 -183
- package/clients/dispatch/runners/architect.js +0 -83
- package/clients/dispatch/runners/architect.test.js +0 -138
- package/clients/dispatch/runners/architect.test.ts +0 -162
- package/clients/dispatch/runners/ast-grep-napi.js +0 -405
- package/clients/dispatch/runners/ast-grep-napi.test.js +0 -107
- package/clients/dispatch/runners/ast-grep-napi.test.ts +0 -129
- package/clients/dispatch/runners/ast-grep.js +0 -157
- package/clients/dispatch/runners/biome.js +0 -55
- package/clients/dispatch/runners/config-validation.js +0 -67
- package/clients/dispatch/runners/go-vet.js +0 -48
- package/clients/dispatch/runners/index.js +0 -47
- package/clients/dispatch/runners/lsp.js +0 -102
- package/clients/dispatch/runners/oxlint.js +0 -67
- package/clients/dispatch/runners/oxlint.test.js +0 -230
- package/clients/dispatch/runners/oxlint.test.ts +0 -303
- package/clients/dispatch/runners/pyright.js +0 -100
- package/clients/dispatch/runners/pyright.test.js +0 -98
- package/clients/dispatch/runners/pyright.test.ts +0 -121
- package/clients/dispatch/runners/python-slop.js +0 -97
- package/clients/dispatch/runners/python-slop.test.js +0 -203
- package/clients/dispatch/runners/python-slop.test.ts +0 -298
- package/clients/dispatch/runners/ruff.js +0 -48
- package/clients/dispatch/runners/rust-clippy.js +0 -102
- package/clients/dispatch/runners/scan_codebase.test.js +0 -89
- package/clients/dispatch/runners/scan_codebase.test.ts +0 -105
- package/clients/dispatch/runners/shellcheck.js +0 -147
- package/clients/dispatch/runners/shellcheck.test.js +0 -98
- package/clients/dispatch/runners/shellcheck.test.ts +0 -129
- package/clients/dispatch/runners/similarity.js +0 -230
- package/clients/dispatch/runners/spellcheck.js +0 -106
- package/clients/dispatch/runners/spellcheck.test.js +0 -158
- package/clients/dispatch/runners/spellcheck.test.ts +0 -214
- package/clients/dispatch/runners/tree-sitter.js +0 -246
- package/clients/dispatch/runners/ts-lsp.js +0 -125
- package/clients/dispatch/runners/ts-slop.js +0 -113
- package/clients/dispatch/runners/type-safety.js +0 -142
- package/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -134
- package/clients/dispatch/runners/utils/runner-helpers.js +0 -115
- package/clients/dispatch/runners/utils.js +0 -51
- package/clients/dispatch/runners/yaml-rule-parser.js +0 -360
- package/clients/dispatch/types.js +0 -16
- package/clients/dispatch/utils/format-utils.js +0 -44
- package/clients/dogfood.test.js +0 -201
- package/clients/dogfood.test.ts +0 -269
- package/clients/file-kinds.js +0 -177
- package/clients/file-kinds.test.js +0 -169
- package/clients/file-kinds.test.ts +0 -210
- package/clients/file-time.js +0 -152
- package/clients/file-utils.js +0 -40
- package/clients/fix-scanners.js +0 -204
- package/clients/format-service.js +0 -184
- package/clients/formatters.js +0 -488
- package/clients/go-client.js +0 -203
- package/clients/go-client.test.js +0 -127
- package/clients/go-client.test.ts +0 -143
- package/clients/installer/index.js +0 -403
- package/clients/interviewer-templates.js +0 -75
- package/clients/interviewer.js +0 -173
- package/clients/jscpd-client.js +0 -196
- package/clients/jscpd-client.test.js +0 -127
- package/clients/jscpd-client.test.ts +0 -145
- package/clients/knip-client.js +0 -239
- package/clients/knip-client.test.js +0 -112
- package/clients/knip-client.test.ts +0 -128
- package/clients/latency-logger.js +0 -40
- package/clients/lsp/__tests__/client.test.js +0 -310
- package/clients/lsp/__tests__/client.test.ts +0 -412
- package/clients/lsp/__tests__/config.test.js +0 -167
- package/clients/lsp/__tests__/config.test.ts +0 -217
- package/clients/lsp/__tests__/error-recovery.test.js +0 -213
- package/clients/lsp/__tests__/error-recovery.test.ts +0 -279
- package/clients/lsp/__tests__/integration.test.js +0 -127
- package/clients/lsp/__tests__/integration.test.ts +0 -160
- package/clients/lsp/__tests__/launch.test.js +0 -313
- package/clients/lsp/__tests__/launch.test.ts +0 -394
- package/clients/lsp/__tests__/server.test.js +0 -259
- package/clients/lsp/__tests__/server.test.ts +0 -332
- package/clients/lsp/__tests__/service.test.js +0 -438
- package/clients/lsp/__tests__/service.test.ts +0 -530
- package/clients/lsp/client.js +0 -350
- package/clients/lsp/config.js +0 -112
- package/clients/lsp/index.js +0 -318
- package/clients/lsp/installer/index.js +0 -391
- package/clients/lsp/interactive-install.js +0 -221
- package/clients/lsp/language.js +0 -170
- package/clients/lsp/launch.js +0 -329
- package/clients/lsp/lsp/launch.js +0 -116
- package/clients/lsp/lsp/server.js +0 -532
- package/clients/lsp/lsp-index.js +0 -10
- package/clients/lsp/path-utils.js +0 -5
- package/clients/lsp/server.js +0 -725
- package/clients/lsp/test-py-spawn/requirements.txt +0 -1
- package/clients/lsp/test-py-spawn/test.py +0 -3
- package/clients/lsp/test-py-svc/requirements.txt +0 -1
- package/clients/lsp/test-py-svc/test.py +0 -3
- package/clients/lsp/test-python-project/requirements.txt +0 -1
- package/clients/lsp/test-python-project/test.py +0 -5
- package/clients/metrics-client.js +0 -107
- package/clients/metrics-client.test.js +0 -128
- package/clients/metrics-client.test.ts +0 -163
- package/clients/metrics-history.js +0 -367
- package/clients/path-utils.js +0 -142
- package/clients/pipeline.js +0 -272
- package/clients/production-readiness.js +0 -522
- package/clients/project-index.js +0 -255
- package/clients/project-metadata.js +0 -531
- package/clients/ruff-client.js +0 -325
- package/clients/ruff-client.test.js +0 -132
- package/clients/ruff-client.test.ts +0 -153
- package/clients/rules-scanner.js +0 -97
- package/clients/runner-tracker.js +0 -152
- package/clients/rust-client.js +0 -205
- package/clients/rust-client.test.js +0 -108
- package/clients/rust-client.test.ts +0 -130
- package/clients/safe-spawn-async.js +0 -163
- package/clients/safe-spawn.js +0 -241
- package/clients/sanitize.js +0 -291
- package/clients/sanitize.test.js +0 -177
- package/clients/sanitize.test.ts +0 -223
- package/clients/scan-architectural-debt.js +0 -167
- package/clients/scan-utils.js +0 -83
- package/clients/secrets-scanner.js +0 -119
- package/clients/secrets-scanner.test.js +0 -100
- package/clients/secrets-scanner.test.ts +0 -113
- package/clients/sg-runner.js +0 -292
- package/clients/state-matrix.js +0 -160
- package/clients/subprocess-client.js +0 -65
- package/clients/symbol-types.js +0 -5
- package/clients/test-runner-client.js +0 -523
- package/clients/test-runner-client.test.js +0 -192
- package/clients/test-runner-client.test.ts +0 -253
- package/clients/test-utils.js +0 -27
- package/clients/test-utils.ts +0 -36
- package/clients/todo-scanner.js +0 -200
- package/clients/todo-scanner.test.js +0 -301
- package/clients/todo-scanner.test.ts +0 -352
- package/clients/tool-availability.js +0 -207
- package/clients/tree-sitter-client.js +0 -601
- package/clients/tree-sitter-query-loader.js +0 -355
- package/clients/tree-sitter-symbol-extractor.js +0 -289
- package/clients/ts-service.js +0 -129
- package/clients/type-coverage-client.js +0 -127
- package/clients/type-coverage-client.test.js +0 -105
- package/clients/type-coverage-client.test.ts +0 -125
- package/clients/type-safety-client.js +0 -138
- package/clients/types.js +0 -11
- package/clients/typescript-client.codefix.test.js +0 -157
- package/clients/typescript-client.codefix.test.ts +0 -186
- package/clients/typescript-client.js +0 -509
- package/clients/typescript-client.test.js +0 -105
- package/clients/typescript-client.test.ts +0 -126
- package/commands/booboo.js +0 -1007
- package/commands/fix-from-booboo.js +0 -398
- package/commands/fix-simplified.js +0 -618
- package/commands/rate.js +0 -281
- package/commands/rate.test.js +0 -119
- package/commands/rate.test.ts +0 -131
- package/commands/refactor.js +0 -130
package/clients/fix-scanners.js
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Scanner functions for fix.ts
|
|
3
|
-
*
|
|
4
|
-
* Each scanner encapsulates one type of issue detection:
|
|
5
|
-
* - scanDuplicates: JSCPD duplicate code detection
|
|
6
|
-
* - scanDeadCode: Knip dead code detection
|
|
7
|
-
* - scanAstGrep: Structural linting via ast-grep
|
|
8
|
-
* - scanBiome: Remaining Biome lint issues
|
|
9
|
-
* - scanSlop: AI slop indicators (high complexity patterns)
|
|
10
|
-
*/
|
|
11
|
-
import * as nodeFs from "node:fs";
|
|
12
|
-
import * as path from "node:path";
|
|
13
|
-
import { EXCLUDED_DIRS } from "./file-utils.js";
|
|
14
|
-
import { safeSpawn } from "./safe-spawn.js";
|
|
15
|
-
import { shouldIgnoreFile } from "./scan-utils.js";
|
|
16
|
-
const DEBUG_LOG = path.join(process.env.HOME || process.env.USERPROFILE || ".", "pi-lens-debug.log");
|
|
17
|
-
function dbg(msg) {
|
|
18
|
-
const line = `[${new Date().toISOString()}] ${msg}\n`;
|
|
19
|
-
try {
|
|
20
|
-
nodeFs.appendFileSync(DEBUG_LOG, line);
|
|
21
|
-
}
|
|
22
|
-
catch (err) {
|
|
23
|
-
// Debug logging failed, silently ignore to avoid recursive errors
|
|
24
|
-
void err;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Scan for duplicate code blocks using JSCPD
|
|
29
|
-
*/
|
|
30
|
-
export function scanDuplicates(jscpd, targetPath, isTsProject) {
|
|
31
|
-
if (!jscpd.isAvailable())
|
|
32
|
-
return [];
|
|
33
|
-
const jscpdResult = jscpd.scan(targetPath);
|
|
34
|
-
return jscpdResult.clones.filter((c) => {
|
|
35
|
-
if (isTsProject && (c.fileA.endsWith(".js") || c.fileB.endsWith(".js")))
|
|
36
|
-
return false;
|
|
37
|
-
return path.resolve(c.fileA) !== path.resolve(c.fileB);
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Scan for dead code using Knip
|
|
42
|
-
*/
|
|
43
|
-
export function scanDeadCode(knip, targetPath, isTsProject) {
|
|
44
|
-
if (!knip.isAvailable())
|
|
45
|
-
return [];
|
|
46
|
-
const knipResult = knip.analyze(targetPath);
|
|
47
|
-
return knipResult.issues.filter((i) => {
|
|
48
|
-
if (!i.file)
|
|
49
|
-
return true;
|
|
50
|
-
return !shouldIgnoreFile(i.file, isTsProject);
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Scan for structural issues using ast-grep
|
|
55
|
-
*/
|
|
56
|
-
export function scanAstGrep(targetPath, isTsProject, configPath) {
|
|
57
|
-
const hasSg = nodeFs.existsSync(path.join(targetPath, "node_modules", ".bin", "sg")) ||
|
|
58
|
-
safeSpawn("npx", ["sg", "--version"], {
|
|
59
|
-
timeout: 5000,
|
|
60
|
-
}).status === 0;
|
|
61
|
-
if (!hasSg)
|
|
62
|
-
return [];
|
|
63
|
-
const result = safeSpawn("npx", [
|
|
64
|
-
"sg",
|
|
65
|
-
"scan",
|
|
66
|
-
"--config",
|
|
67
|
-
configPath,
|
|
68
|
-
"--json",
|
|
69
|
-
"--globs",
|
|
70
|
-
"!**/*.test.ts",
|
|
71
|
-
"--globs",
|
|
72
|
-
"!**/*.spec.ts",
|
|
73
|
-
"--globs",
|
|
74
|
-
"!**/test-utils.ts",
|
|
75
|
-
"--globs",
|
|
76
|
-
"!**/.pi-lens/**",
|
|
77
|
-
...(isTsProject ? ["--globs", "!**/*.js"] : []),
|
|
78
|
-
targetPath,
|
|
79
|
-
], {
|
|
80
|
-
timeout: 30000,
|
|
81
|
-
});
|
|
82
|
-
const raw = result.stdout?.trim() ?? "";
|
|
83
|
-
const items = raw.startsWith("[")
|
|
84
|
-
? (() => {
|
|
85
|
-
try {
|
|
86
|
-
return JSON.parse(raw);
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
return [];
|
|
90
|
-
}
|
|
91
|
-
})()
|
|
92
|
-
: raw.split("\n").flatMap((l) => {
|
|
93
|
-
try {
|
|
94
|
-
return [JSON.parse(l)];
|
|
95
|
-
}
|
|
96
|
-
catch {
|
|
97
|
-
return [];
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
const astIssues = [];
|
|
101
|
-
for (const item of items) {
|
|
102
|
-
const rule = item.ruleId || item.rule?.title || item.name || "unknown";
|
|
103
|
-
const line = (item.labels?.[0]?.range?.start?.line ?? item.range?.start?.line ?? 0) +
|
|
104
|
-
1;
|
|
105
|
-
const relFile = path
|
|
106
|
-
.relative(targetPath, item.file ?? "")
|
|
107
|
-
.replace(/\\/g, "/");
|
|
108
|
-
if (shouldIgnoreFile(relFile, isTsProject))
|
|
109
|
-
continue;
|
|
110
|
-
astIssues.push({
|
|
111
|
-
rule,
|
|
112
|
-
file: relFile,
|
|
113
|
-
line,
|
|
114
|
-
message: item.message ?? rule,
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
return astIssues;
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Scan for remaining Biome lint issues (couldn't be auto-fixed)
|
|
121
|
-
*/
|
|
122
|
-
export function scanBiomeIssues(biome, targetPath) {
|
|
123
|
-
if (!biome.isAvailable())
|
|
124
|
-
return [];
|
|
125
|
-
const checkResult = safeSpawn("npx", [
|
|
126
|
-
"@biomejs/biome",
|
|
127
|
-
"check",
|
|
128
|
-
"--reporter=json",
|
|
129
|
-
"--max-diagnostics=50",
|
|
130
|
-
targetPath,
|
|
131
|
-
], { timeout: 20000 });
|
|
132
|
-
const remainingBiome = [];
|
|
133
|
-
try {
|
|
134
|
-
const data = JSON.parse(checkResult.stdout ?? "{}");
|
|
135
|
-
for (const diag of (data.diagnostics ?? []).slice(0, 20)) {
|
|
136
|
-
if (!diag.category?.startsWith("lint/"))
|
|
137
|
-
continue;
|
|
138
|
-
const filePath = diag.location?.path?.file ?? "";
|
|
139
|
-
const line = diag.location?.span?.start?.line ?? 0;
|
|
140
|
-
const rule = diag.category ?? "lint";
|
|
141
|
-
remainingBiome.push({
|
|
142
|
-
file: path.relative(targetPath, filePath).replace(/\\/g, "/"),
|
|
143
|
-
line: line + 1,
|
|
144
|
-
rule,
|
|
145
|
-
message: diag.message ?? rule,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
catch (e) {
|
|
150
|
-
dbg(`biome lint parse failed: ${e}`);
|
|
151
|
-
}
|
|
152
|
-
return remainingBiome;
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Scan for AI slop indicators (high complexity patterns)
|
|
156
|
-
*/
|
|
157
|
-
export function scanSlop(complexity, targetPath, isTsProject) {
|
|
158
|
-
const slopFiles = [];
|
|
159
|
-
const scanDir = (dir) => {
|
|
160
|
-
if (!nodeFs.existsSync(dir))
|
|
161
|
-
return;
|
|
162
|
-
for (const entry of nodeFs.readdirSync(dir, { withFileTypes: true })) {
|
|
163
|
-
const fullPath = path.join(dir, entry.name);
|
|
164
|
-
if (entry.isDirectory()) {
|
|
165
|
-
if (EXCLUDED_DIRS.includes(entry.name))
|
|
166
|
-
continue;
|
|
167
|
-
scanDir(fullPath);
|
|
168
|
-
}
|
|
169
|
-
else if (complexity.isSupportedFile(fullPath)) {
|
|
170
|
-
const metrics = complexity.analyzeFile(fullPath);
|
|
171
|
-
if (metrics) {
|
|
172
|
-
const warnings = complexity
|
|
173
|
-
.checkThresholds(metrics)
|
|
174
|
-
.filter((w) => w.includes("AI-style") ||
|
|
175
|
-
w.includes("try/catch") ||
|
|
176
|
-
w.includes("single-use") ||
|
|
177
|
-
w.includes("Excessive comments"));
|
|
178
|
-
const relFile = path
|
|
179
|
-
.relative(targetPath, fullPath)
|
|
180
|
-
.replace(/\\/g, "/");
|
|
181
|
-
if (shouldIgnoreFile(relFile, isTsProject))
|
|
182
|
-
continue;
|
|
183
|
-
if (warnings.length >= 2) {
|
|
184
|
-
slopFiles.push({ file: relFile, warnings });
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
scanDir(targetPath);
|
|
191
|
-
return slopFiles;
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Run all scanners and return combined results
|
|
195
|
-
*/
|
|
196
|
-
export function scanAll(clients, targetPath, isTsProject, configPath) {
|
|
197
|
-
return {
|
|
198
|
-
duplicates: scanDuplicates(clients.jscpd, targetPath, isTsProject),
|
|
199
|
-
deadCode: scanDeadCode(clients.knip, targetPath, isTsProject),
|
|
200
|
-
astIssues: scanAstGrep(targetPath, isTsProject, configPath),
|
|
201
|
-
biomeIssues: scanBiomeIssues(clients.biome, targetPath),
|
|
202
|
-
slopFiles: scanSlop(clients.complexity, targetPath, isTsProject),
|
|
203
|
-
};
|
|
204
|
-
}
|
|
@@ -1,184 +0,0 @@
|
|
|
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 with concurrency limits
|
|
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 { FileTime } from "./file-time.js";
|
|
15
|
-
import { clearFormatterCache, formatFile, getFormattersForFile, } from "./formatters.js";
|
|
16
|
-
// --- Configuration ---
|
|
17
|
-
/** Maximum concurrent formatters to prevent resource contention */
|
|
18
|
-
const DEFAULT_FORMATTER_CONCURRENCY = 2;
|
|
19
|
-
// --- Format Service ---
|
|
20
|
-
export class FormatService {
|
|
21
|
-
constructor(sessionID, enabled = true) {
|
|
22
|
-
this.fileTime = new FileTime(sessionID);
|
|
23
|
-
this.enabled = enabled;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Format a file with all detected formatters
|
|
27
|
-
* Runs formatters with limited concurrency to prevent resource contention
|
|
28
|
-
*/
|
|
29
|
-
async formatFile(filePath, options = {}) {
|
|
30
|
-
const absolutePath = path.resolve(filePath);
|
|
31
|
-
const cwd = path.dirname(absolutePath);
|
|
32
|
-
// Skip if disabled
|
|
33
|
-
if (options.skip || !this.enabled) {
|
|
34
|
-
return {
|
|
35
|
-
filePath: absolutePath,
|
|
36
|
-
formatters: [],
|
|
37
|
-
anyChanged: false,
|
|
38
|
-
allSucceeded: true,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
// Check if file was modified externally (safety check)
|
|
42
|
-
if (this.fileTime.hasChanged(absolutePath)) {
|
|
43
|
-
console.warn(`[format] File ${absolutePath} modified externally, skipping format`);
|
|
44
|
-
return {
|
|
45
|
-
filePath: absolutePath,
|
|
46
|
-
formatters: [],
|
|
47
|
-
anyChanged: false,
|
|
48
|
-
allSucceeded: false,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
// Get formatters for this file
|
|
52
|
-
const formatters = options.formatters
|
|
53
|
-
? await this.getFormattersByName(options.formatters)
|
|
54
|
-
: await getFormattersForFile(absolutePath, cwd);
|
|
55
|
-
if (formatters.length === 0) {
|
|
56
|
-
return {
|
|
57
|
-
filePath: absolutePath,
|
|
58
|
-
formatters: [],
|
|
59
|
-
anyChanged: false,
|
|
60
|
-
allSucceeded: true,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
// Run formatters with limited concurrency
|
|
64
|
-
const results = await this.runFormattersWithConcurrency(absolutePath, formatters);
|
|
65
|
-
// Record new file state after formatting
|
|
66
|
-
this.fileTime.read(absolutePath);
|
|
67
|
-
// Build summary
|
|
68
|
-
const anyChanged = results.some((r) => r.changed);
|
|
69
|
-
const allSucceeded = results.every((r) => r.success);
|
|
70
|
-
return {
|
|
71
|
-
filePath: absolutePath,
|
|
72
|
-
formatters: results.map((r, i) => ({
|
|
73
|
-
name: formatters[i].name,
|
|
74
|
-
success: r.success,
|
|
75
|
-
changed: r.changed,
|
|
76
|
-
error: r.error,
|
|
77
|
-
})),
|
|
78
|
-
anyChanged,
|
|
79
|
-
allSucceeded,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Run formatters with limited concurrency to prevent resource contention
|
|
84
|
-
*
|
|
85
|
-
* Formatters are I/O bound so we can run 2-3 concurrently, but not unlimited.
|
|
86
|
-
* This prevents overwhelming the system when multiple formatters are configured.
|
|
87
|
-
*/
|
|
88
|
-
async runFormattersWithConcurrency(filePath, formatters, concurrency = DEFAULT_FORMATTER_CONCURRENCY) {
|
|
89
|
-
const results = [];
|
|
90
|
-
// Process in batches to limit concurrent formatters
|
|
91
|
-
for (let i = 0; i < formatters.length; i += concurrency) {
|
|
92
|
-
const batch = formatters.slice(i, i + concurrency);
|
|
93
|
-
const batchResults = await Promise.all(batch.map(async (formatter) => {
|
|
94
|
-
try {
|
|
95
|
-
// Add timeout to each formatter individually
|
|
96
|
-
const timeoutMs = 30000;
|
|
97
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
98
|
-
setTimeout(() => reject(new Error(`Formatter ${formatter.name} timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
99
|
-
});
|
|
100
|
-
return await Promise.race([
|
|
101
|
-
formatFile(filePath, formatter),
|
|
102
|
-
timeoutPromise,
|
|
103
|
-
]);
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
return {
|
|
107
|
-
success: false,
|
|
108
|
-
changed: false,
|
|
109
|
-
error: error instanceof Error ? error.message : String(error),
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
}));
|
|
113
|
-
results.push(...batchResults);
|
|
114
|
-
}
|
|
115
|
-
return results;
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Get formatters by name (for explicit formatter selection)
|
|
119
|
-
*/
|
|
120
|
-
async getFormattersByName(names) {
|
|
121
|
-
const { listAllFormatters, ...formatters } = await import("./formatters.js");
|
|
122
|
-
const allNames = listAllFormatters();
|
|
123
|
-
return names
|
|
124
|
-
.filter((name) => allNames.includes(name))
|
|
125
|
-
.map((name) => {
|
|
126
|
-
// Access formatter by name from the exports
|
|
127
|
-
const key = `${name}Formatter`;
|
|
128
|
-
return formatters[key];
|
|
129
|
-
})
|
|
130
|
-
.filter(Boolean);
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Assert file hasn't changed before editing
|
|
134
|
-
* Throws FileTimeError if file modified externally
|
|
135
|
-
*/
|
|
136
|
-
assertUnchanged(filePath) {
|
|
137
|
-
this.fileTime.assert(filePath);
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Check if file has changed externally
|
|
141
|
-
*/
|
|
142
|
-
hasChanged(filePath) {
|
|
143
|
-
return this.fileTime.hasChanged(filePath);
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Record file read (after agent reads file)
|
|
147
|
-
*/
|
|
148
|
-
recordRead(filePath) {
|
|
149
|
-
this.fileTime.read(filePath);
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Clear detection cache
|
|
153
|
-
*/
|
|
154
|
-
clearCache() {
|
|
155
|
-
clearFormatterCache();
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
// --- Singleton Instance ---
|
|
159
|
-
let globalFormatService = null;
|
|
160
|
-
let currentSessionID = null;
|
|
161
|
-
export function getFormatService(sessionID, enabled = true) {
|
|
162
|
-
// Create new instance if:
|
|
163
|
-
// 1. No service exists yet
|
|
164
|
-
// 2. Session ID changed (different session)
|
|
165
|
-
const shouldCreateNew = !globalFormatService || (sessionID && sessionID !== currentSessionID);
|
|
166
|
-
if (shouldCreateNew) {
|
|
167
|
-
globalFormatService = new FormatService(sessionID ?? "default", enabled);
|
|
168
|
-
currentSessionID = sessionID ?? "default";
|
|
169
|
-
}
|
|
170
|
-
return globalFormatService;
|
|
171
|
-
}
|
|
172
|
-
export function resetFormatService() {
|
|
173
|
-
globalFormatService = null;
|
|
174
|
-
currentSessionID = null;
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Reset format service and clear all file tracking state.
|
|
178
|
-
* Use this in tests to ensure complete isolation.
|
|
179
|
-
*/
|
|
180
|
-
export function clearFormatServiceAndFileState() {
|
|
181
|
-
resetFormatService();
|
|
182
|
-
}
|
|
183
|
-
// Re-export for convenience
|
|
184
|
-
export { clearAllSessions } from "./file-time.js";
|