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
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Safe async cross-platform spawn utilities
|
|
3
|
-
*
|
|
4
|
-
* Replaces blocking spawnSync with async spawn + proper timeout handling.
|
|
5
|
-
* Ensures processes are killed on timeout to prevent zombie processes.
|
|
6
|
-
*/
|
|
7
|
-
import { spawn } from "node:child_process";
|
|
8
|
-
/**
|
|
9
|
-
* Async spawn with timeout and proper process cleanup.
|
|
10
|
-
*
|
|
11
|
-
* Unlike spawnSync, this:
|
|
12
|
-
* - Doesn't block the event loop
|
|
13
|
-
* - Kills the process on timeout (preventing zombies)
|
|
14
|
-
* - Supports cancellation via AbortSignal
|
|
15
|
-
*/
|
|
16
|
-
export async function safeSpawnAsync(command, args, options) {
|
|
17
|
-
const timeout = options?.timeout ?? 30000;
|
|
18
|
-
const abortSignal = options?.signal;
|
|
19
|
-
return new Promise((resolve) => {
|
|
20
|
-
// Check for early abort
|
|
21
|
-
if (abortSignal?.aborted) {
|
|
22
|
-
resolve({
|
|
23
|
-
stdout: "",
|
|
24
|
-
stderr: "",
|
|
25
|
-
status: null,
|
|
26
|
-
error: new Error("Spawn aborted before start"),
|
|
27
|
-
});
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
let stdout = "";
|
|
31
|
-
let stderr = "";
|
|
32
|
-
let timedOut = false;
|
|
33
|
-
// Spawn the process (non-blocking)
|
|
34
|
-
const child = spawn(command, args, {
|
|
35
|
-
cwd: options?.cwd,
|
|
36
|
-
env: options?.env,
|
|
37
|
-
windowsHide: true,
|
|
38
|
-
shell: false, // Always use args array (safer, no shell injection)
|
|
39
|
-
});
|
|
40
|
-
// Handle abort signal
|
|
41
|
-
const onAbort = () => {
|
|
42
|
-
if (!child.killed) {
|
|
43
|
-
child.kill("SIGTERM");
|
|
44
|
-
// Force kill after 1s if still running
|
|
45
|
-
setTimeout(() => {
|
|
46
|
-
if (!child.killed) {
|
|
47
|
-
child.kill("SIGKILL");
|
|
48
|
-
}
|
|
49
|
-
}, 1000);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
53
|
-
// Collect output
|
|
54
|
-
child.stdout?.setEncoding("utf-8");
|
|
55
|
-
child.stderr?.setEncoding("utf-8");
|
|
56
|
-
child.stdout?.on("data", (data) => (stdout += data));
|
|
57
|
-
child.stderr?.on("data", (data) => (stderr += data));
|
|
58
|
-
// Timeout handling - KILL the process, don't just abandon it
|
|
59
|
-
const timeoutId = setTimeout(() => {
|
|
60
|
-
timedOut = true;
|
|
61
|
-
if (!child.killed) {
|
|
62
|
-
child.kill("SIGTERM");
|
|
63
|
-
// Force kill after 1s grace period
|
|
64
|
-
setTimeout(() => {
|
|
65
|
-
if (!child.killed) {
|
|
66
|
-
child.kill("SIGKILL");
|
|
67
|
-
}
|
|
68
|
-
}, 1000);
|
|
69
|
-
}
|
|
70
|
-
}, timeout);
|
|
71
|
-
// Process completion
|
|
72
|
-
child.on("close", (code, signal) => {
|
|
73
|
-
clearTimeout(timeoutId);
|
|
74
|
-
abortSignal?.removeEventListener("abort", onAbort);
|
|
75
|
-
if (timedOut) {
|
|
76
|
-
resolve({
|
|
77
|
-
stdout,
|
|
78
|
-
stderr,
|
|
79
|
-
status: null,
|
|
80
|
-
error: new Error(`Process timed out after ${timeout}ms (signal: ${signal || "none"})`),
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
else if (signal) {
|
|
84
|
-
resolve({
|
|
85
|
-
stdout,
|
|
86
|
-
stderr,
|
|
87
|
-
status: null,
|
|
88
|
-
error: new Error(`Process killed by signal: ${signal}`),
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
resolve({ stdout, stderr, status: code });
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
child.on("error", (err) => {
|
|
96
|
-
clearTimeout(timeoutId);
|
|
97
|
-
abortSignal?.removeEventListener("abort", onAbort);
|
|
98
|
-
resolve({ stdout, stderr, status: null, error: err });
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Backward-compatible wrapper - for gradual migration.
|
|
104
|
-
*
|
|
105
|
-
* ⚠️ Deprecated: Use safeSpawnAsync instead to avoid blocking.
|
|
106
|
-
* This maintains the old signature but internally uses async spawn.
|
|
107
|
-
*/
|
|
108
|
-
export function safeSpawn(command, args, options) {
|
|
109
|
-
// For now, keep the old behavior during migration
|
|
110
|
-
// We'll replace callers gradually to avoid breaking changes
|
|
111
|
-
const { spawnSync } = require("node:child_process");
|
|
112
|
-
const result = spawnSync(command, args, {
|
|
113
|
-
cwd: options?.cwd,
|
|
114
|
-
env: options?.env,
|
|
115
|
-
timeout: options?.timeout,
|
|
116
|
-
encoding: "utf-8",
|
|
117
|
-
shell: false,
|
|
118
|
-
windowsHide: true,
|
|
119
|
-
// Windows-specific: kill tree on timeout
|
|
120
|
-
killSignal: process.platform === "win32" ? "SIGTERM" : undefined,
|
|
121
|
-
});
|
|
122
|
-
return {
|
|
123
|
-
stdout: result.stdout?.toString() || "",
|
|
124
|
-
stderr: result.stderr?.toString() || "",
|
|
125
|
-
status: result.status,
|
|
126
|
-
error: result.error,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Check if a command is available in PATH (async version)
|
|
131
|
-
*/
|
|
132
|
-
export async function isCommandAvailableAsync(command) {
|
|
133
|
-
const finder = process.platform === "win32" ? "where" : "which";
|
|
134
|
-
const result = await safeSpawnAsync(finder, [command], { timeout: 5000 });
|
|
135
|
-
return result.status === 0 && !result.error;
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Find the full path to a command (async version)
|
|
139
|
-
*/
|
|
140
|
-
export async function findCommandAsync(command) {
|
|
141
|
-
const finder = process.platform === "win32" ? "where" : "which";
|
|
142
|
-
const result = await safeSpawnAsync(finder, [command], { timeout: 5000 });
|
|
143
|
-
if (result.status !== 0 || result.error)
|
|
144
|
-
return null;
|
|
145
|
-
// Take first line (first match)
|
|
146
|
-
return result.stdout.trim().split("\n")[0] || null;
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Run multiple commands concurrently with limited concurrency.
|
|
150
|
-
*
|
|
151
|
-
* This is the key function for preventing resource contention.
|
|
152
|
-
* Uses async spawn with concurrency limiting built-in.
|
|
153
|
-
*/
|
|
154
|
-
export async function safeSpawnBatch(commands, concurrency = 3) {
|
|
155
|
-
const results = [];
|
|
156
|
-
// Process in batches to limit concurrent processes
|
|
157
|
-
for (let i = 0; i < commands.length; i += concurrency) {
|
|
158
|
-
const batch = commands.slice(i, i + concurrency);
|
|
159
|
-
const batchResults = await Promise.all(batch.map(({ command, args, options }) => safeSpawnAsync(command, args, options)));
|
|
160
|
-
results.push(...batchResults);
|
|
161
|
-
}
|
|
162
|
-
return results;
|
|
163
|
-
}
|
package/clients/safe-spawn.js
DELETED
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Safe cross-platform spawn utilities
|
|
3
|
-
*
|
|
4
|
-
* Provides both sync (deprecated) and async versions for gradual migration.
|
|
5
|
-
*
|
|
6
|
-
* Async version features:
|
|
7
|
-
* - Non-blocking execution
|
|
8
|
-
* - Proper process cleanup on timeout (no zombies)
|
|
9
|
-
* - Batch execution with concurrency limits
|
|
10
|
-
* - AbortSignal support for cancellation
|
|
11
|
-
*
|
|
12
|
-
* Migration guide:
|
|
13
|
-
* - Change: safeSpawn(cmd, args, opts)
|
|
14
|
-
* - To: await safeSpawnAsync(cmd, args, opts)
|
|
15
|
-
*/
|
|
16
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
17
|
-
// ============================================================================
|
|
18
|
-
// ASYNC VERSION (Recommended - Non-blocking)
|
|
19
|
-
// ============================================================================
|
|
20
|
-
/**
|
|
21
|
-
* Async spawn with timeout and proper process cleanup.
|
|
22
|
-
*
|
|
23
|
-
* Unlike spawnSync, this:
|
|
24
|
-
* - Doesn't block the event loop
|
|
25
|
-
* - Kills the process on timeout (preventing zombies)
|
|
26
|
-
* - Supports cancellation via AbortSignal
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* const result = await safeSpawnAsync("npm", ["test"], { timeout: 30000 });
|
|
30
|
-
* if (result.error) console.error("Failed:", result.error);
|
|
31
|
-
*/
|
|
32
|
-
export async function safeSpawnAsync(command, args, options) {
|
|
33
|
-
const timeout = options?.timeout ?? 30000;
|
|
34
|
-
const abortSignal = options?.signal;
|
|
35
|
-
return new Promise((resolve) => {
|
|
36
|
-
// Check for early abort
|
|
37
|
-
if (abortSignal?.aborted) {
|
|
38
|
-
resolve({
|
|
39
|
-
stdout: "",
|
|
40
|
-
stderr: "",
|
|
41
|
-
status: null,
|
|
42
|
-
error: new Error("Spawn aborted before start"),
|
|
43
|
-
});
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
let stdout = "";
|
|
47
|
-
let stderr = "";
|
|
48
|
-
let timedOut = false;
|
|
49
|
-
let killed = false;
|
|
50
|
-
// Spawn the process (non-blocking)
|
|
51
|
-
// On Windows, use shell mode for .cmd files (like pyright, biome)
|
|
52
|
-
const isWindows = process.platform === "win32";
|
|
53
|
-
const child = spawn(command, args, {
|
|
54
|
-
cwd: options?.cwd,
|
|
55
|
-
env: { ...process.env, ...options?.env },
|
|
56
|
-
windowsHide: true,
|
|
57
|
-
shell: isWindows,
|
|
58
|
-
});
|
|
59
|
-
// Handle abort signal
|
|
60
|
-
const onAbort = () => {
|
|
61
|
-
if (!killed && !child.killed) {
|
|
62
|
-
killed = true;
|
|
63
|
-
child.kill("SIGTERM");
|
|
64
|
-
// Force kill after 1s if still running
|
|
65
|
-
setTimeout(() => {
|
|
66
|
-
if (!child.killed) {
|
|
67
|
-
child.kill("SIGKILL");
|
|
68
|
-
}
|
|
69
|
-
}, 1000);
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
73
|
-
// Collect output
|
|
74
|
-
child.stdout?.setEncoding("utf-8");
|
|
75
|
-
child.stderr?.setEncoding("utf-8");
|
|
76
|
-
child.stdout?.on("data", (data) => (stdout += data));
|
|
77
|
-
child.stderr?.on("data", (data) => (stderr += data));
|
|
78
|
-
// Timeout handling - KILL the process, don't just abandon it
|
|
79
|
-
const timeoutId = setTimeout(() => {
|
|
80
|
-
timedOut = true;
|
|
81
|
-
if (!killed && !child.killed) {
|
|
82
|
-
killed = true;
|
|
83
|
-
child.kill("SIGTERM");
|
|
84
|
-
// Force kill after 1s grace period
|
|
85
|
-
setTimeout(() => {
|
|
86
|
-
if (!child.killed) {
|
|
87
|
-
child.kill("SIGKILL");
|
|
88
|
-
}
|
|
89
|
-
}, 1000);
|
|
90
|
-
}
|
|
91
|
-
}, timeout);
|
|
92
|
-
// Process completion
|
|
93
|
-
child.on("close", (code, signal) => {
|
|
94
|
-
clearTimeout(timeoutId);
|
|
95
|
-
abortSignal?.removeEventListener("abort", onAbort);
|
|
96
|
-
if (timedOut) {
|
|
97
|
-
resolve({
|
|
98
|
-
stdout,
|
|
99
|
-
stderr,
|
|
100
|
-
status: null,
|
|
101
|
-
error: new Error(`Process timed out after ${timeout}ms (killed with ${signal || "SIGTERM"})`),
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
else if (signal) {
|
|
105
|
-
resolve({
|
|
106
|
-
stdout,
|
|
107
|
-
stderr,
|
|
108
|
-
status: null,
|
|
109
|
-
error: new Error(`Process killed by signal: ${signal}`),
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
resolve({ stdout, stderr, status: code });
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
child.on("error", (err) => {
|
|
117
|
-
clearTimeout(timeoutId);
|
|
118
|
-
abortSignal?.removeEventListener("abort", onAbort);
|
|
119
|
-
resolve({ stdout, stderr, status: null, error: err });
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Run multiple commands concurrently with limited concurrency.
|
|
125
|
-
*
|
|
126
|
-
* This prevents resource contention when running many linters.
|
|
127
|
-
* Uses async spawn with concurrency limiting built-in.
|
|
128
|
-
*
|
|
129
|
-
* @example
|
|
130
|
-
* const results = await safeSpawnBatch([
|
|
131
|
-
* { command: "biome", args: ["check", "file.ts"] },
|
|
132
|
-
* { command: "ruff", args: ["check", "file.py"] },
|
|
133
|
-
* ], 3); // Max 3 concurrent
|
|
134
|
-
*/
|
|
135
|
-
export async function safeSpawnBatch(commands, concurrency = 3) {
|
|
136
|
-
const results = [];
|
|
137
|
-
// Process in batches to limit concurrent processes
|
|
138
|
-
for (let i = 0; i < commands.length; i += concurrency) {
|
|
139
|
-
const batch = commands.slice(i, i + concurrency);
|
|
140
|
-
const batchResults = await Promise.all(batch.map(({ command, args, options }) => safeSpawnAsync(command, args, options)));
|
|
141
|
-
results.push(...batchResults);
|
|
142
|
-
}
|
|
143
|
-
return results;
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Check if a command is available in PATH (async version)
|
|
147
|
-
*/
|
|
148
|
-
export async function isCommandAvailableAsync(command) {
|
|
149
|
-
const finder = process.platform === "win32" ? "where" : "which";
|
|
150
|
-
const result = await safeSpawnAsync(finder, [command], { timeout: 5000 });
|
|
151
|
-
return result.status === 0 && !result.error;
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Find the full path to a command (async version)
|
|
155
|
-
*/
|
|
156
|
-
export async function findCommandAsync(command) {
|
|
157
|
-
const finder = process.platform === "win32" ? "where" : "which";
|
|
158
|
-
const result = await safeSpawnAsync(finder, [command], { timeout: 5000 });
|
|
159
|
-
if (result.status !== 0 || result.error)
|
|
160
|
-
return null;
|
|
161
|
-
// Take first line (first match)
|
|
162
|
-
return result.stdout.trim().split("\n")[0] || null;
|
|
163
|
-
}
|
|
164
|
-
// ============================================================================
|
|
165
|
-
// SYNC VERSION (Deprecated - Blocking, for backward compatibility)
|
|
166
|
-
// ============================================================================
|
|
167
|
-
/**
|
|
168
|
-
* Escape an argument for Windows shell execution.
|
|
169
|
-
* Handles spaces, quotes, $variables, and special characters.
|
|
170
|
-
*/
|
|
171
|
-
function escapeWindowsArg(arg) {
|
|
172
|
-
if (arg.includes("$")) {
|
|
173
|
-
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
174
|
-
}
|
|
175
|
-
if (!/[\s"]/.test(arg))
|
|
176
|
-
return arg;
|
|
177
|
-
return `"${arg.replace(/"/g, '""')}"`;
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Construct a command string for Windows shell execution.
|
|
181
|
-
*/
|
|
182
|
-
function buildWindowsCommand(command, args) {
|
|
183
|
-
const escapedArgs = args.map(escapeWindowsArg).join(" ");
|
|
184
|
-
return `${command} ${escapedArgs}`;
|
|
185
|
-
}
|
|
186
|
-
/**
|
|
187
|
-
* ⚠️ DEPRECATED: Use safeSpawnAsync instead.
|
|
188
|
-
*
|
|
189
|
-
* This blocks the entire Node.js event loop until the process exits.
|
|
190
|
-
* If the process hangs, pi will freeze.
|
|
191
|
-
*
|
|
192
|
-
* Kept for backward compatibility during migration.
|
|
193
|
-
*/
|
|
194
|
-
export function safeSpawn(command, args, options) {
|
|
195
|
-
if (process.platform === "win32") {
|
|
196
|
-
const fullCommand = buildWindowsCommand(command, args);
|
|
197
|
-
const result = spawnSync(fullCommand, {
|
|
198
|
-
...options,
|
|
199
|
-
encoding: "utf-8",
|
|
200
|
-
shell: true,
|
|
201
|
-
windowsHide: true,
|
|
202
|
-
});
|
|
203
|
-
return {
|
|
204
|
-
stdout: result.stdout?.toString() || "",
|
|
205
|
-
stderr: result.stderr?.toString() || "",
|
|
206
|
-
status: result.status,
|
|
207
|
-
error: result.error,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
const result = spawnSync(command, args, {
|
|
211
|
-
...options,
|
|
212
|
-
encoding: "utf-8",
|
|
213
|
-
shell: false,
|
|
214
|
-
windowsHide: true,
|
|
215
|
-
});
|
|
216
|
-
return {
|
|
217
|
-
stdout: result.stdout?.toString() || "",
|
|
218
|
-
stderr: result.stderr?.toString() || "",
|
|
219
|
-
status: result.status,
|
|
220
|
-
error: result.error,
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Check if a command is available in PATH (sync version - deprecated)
|
|
225
|
-
* @deprecated Use isCommandAvailableAsync
|
|
226
|
-
*/
|
|
227
|
-
export function isCommandAvailable(command) {
|
|
228
|
-
const result = safeSpawn(process.platform === "win32" ? "where" : "which", [command], { timeout: 5000 });
|
|
229
|
-
return result.status === 0;
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* Find the full path to a command (sync version - deprecated)
|
|
233
|
-
* @deprecated Use findCommandAsync
|
|
234
|
-
*/
|
|
235
|
-
export function findCommand(command) {
|
|
236
|
-
const finder = process.platform === "win32" ? "where" : "which";
|
|
237
|
-
const result = safeSpawn(finder, [command], { timeout: 5000 });
|
|
238
|
-
if (result.status !== 0)
|
|
239
|
-
return null;
|
|
240
|
-
return result.stdout.trim().split("\n")[0] || null;
|
|
241
|
-
}
|
package/clients/sanitize.js
DELETED
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool Output Sanitization for pi-lens
|
|
3
|
-
*
|
|
4
|
-
* Cleans and normalizes tool output for display to users.
|
|
5
|
-
* Removes ANSI codes, extracts key error messages, etc.
|
|
6
|
-
*/
|
|
7
|
-
// --- Constants ---
|
|
8
|
-
// ANSI escape codes for colors and formatting
|
|
9
|
-
const ANSI_ESCAPE = /\x1b\[[0-9;]*m/g;
|
|
10
|
-
const ANSI_ESCAPE_EXTENDED = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
11
|
-
// Common error patterns from different tools
|
|
12
|
-
const ERROR_INDICATORS = [
|
|
13
|
-
/\berror\b/i,
|
|
14
|
-
/\bfailed\b/i,
|
|
15
|
-
/\bfatal\b/i,
|
|
16
|
-
/\binvalid\b/i,
|
|
17
|
-
/\bunexpected\b/i,
|
|
18
|
-
/\bexpected\b/i,
|
|
19
|
-
/\bsyntax\b/i,
|
|
20
|
-
/\bcannot find\b/i,
|
|
21
|
-
/\bnot found\b/i,
|
|
22
|
-
/\bno such\b/i,
|
|
23
|
-
];
|
|
24
|
-
// Patterns that indicate a line is a "detail" rather than an error
|
|
25
|
-
const DETAIL_PATTERNS = [
|
|
26
|
-
/^help wanted/i,
|
|
27
|
-
/^note:/i,
|
|
28
|
-
/^hint:/i,
|
|
29
|
-
/^→/,
|
|
30
|
-
/^\s*at\s+/, // Stack traces
|
|
31
|
-
/^ {4}/, // Indented continuation
|
|
32
|
-
];
|
|
33
|
-
// --- Core Sanitization Functions ---
|
|
34
|
-
/**
|
|
35
|
-
* Remove ANSI escape sequences from a string.
|
|
36
|
-
*/
|
|
37
|
-
export function stripAnsi(text) {
|
|
38
|
-
return text.replace(ANSI_ESCAPE, "").replace(ANSI_ESCAPE_EXTENDED, "");
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Normalize whitespace in a string.
|
|
42
|
-
* Collapses multiple spaces/tabs to single space, trims lines.
|
|
43
|
-
*/
|
|
44
|
-
export function normalizeWhitespace(text) {
|
|
45
|
-
return text
|
|
46
|
-
.split(/\r?\n/)
|
|
47
|
-
.map((line) => line.replace(/\s+/g, " ").trim())
|
|
48
|
-
.filter((line) => line.length > 0)
|
|
49
|
-
.join("\n");
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Check if a line contains error indicators.
|
|
53
|
-
*/
|
|
54
|
-
function isErrorLine(line) {
|
|
55
|
-
const cleanLine = stripAnsi(line).trim();
|
|
56
|
-
return ERROR_INDICATORS.some((pattern) => pattern.test(cleanLine));
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Check if a line is a "detail" line (continuation, stack trace, etc.)
|
|
60
|
-
* that shouldn't be shown as a standalone error.
|
|
61
|
-
*/
|
|
62
|
-
function isDetailLine(line) {
|
|
63
|
-
const cleanLine = stripAnsi(line).trim();
|
|
64
|
-
return DETAIL_PATTERNS.some((pattern) => pattern.test(cleanLine));
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Sanitize a single line of tool output.
|
|
68
|
-
* Removes ANSI codes and normalizes common patterns.
|
|
69
|
-
*/
|
|
70
|
-
export function sanitizeLine(line) {
|
|
71
|
-
return stripAnsi(line)
|
|
72
|
-
.replace(/^\s*\[error\]\s*/i, "")
|
|
73
|
-
.replace(/^\s*error:\s*/i, "")
|
|
74
|
-
.replace(/^\s*[×✖✘✗]\s*/u, "")
|
|
75
|
-
.replace(/^\s*[✓✔]\s*/u, "")
|
|
76
|
-
.replace(/\s+/g, " ")
|
|
77
|
-
.trim();
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Sanitize multi-line tool output.
|
|
81
|
-
* Returns cleaned lines, filtered to relevant content.
|
|
82
|
-
*/
|
|
83
|
-
export function sanitizeOutput(output) {
|
|
84
|
-
if (!output || typeof output !== "string") {
|
|
85
|
-
return "";
|
|
86
|
-
}
|
|
87
|
-
const lines = output.split(/\r?\n/);
|
|
88
|
-
const sanitized = lines
|
|
89
|
-
.map((line) => sanitizeLine(line))
|
|
90
|
-
.filter((line) => line.length > 0)
|
|
91
|
-
.filter((line) => !isDetailLine(line));
|
|
92
|
-
return sanitized.join("\n");
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Extract the most relevant error message from tool output.
|
|
96
|
-
* Returns the first line that contains error indicators, or the first non-empty line.
|
|
97
|
-
*/
|
|
98
|
-
export function extractErrorMessage(output) {
|
|
99
|
-
if (!output || typeof output !== "string") {
|
|
100
|
-
return undefined;
|
|
101
|
-
}
|
|
102
|
-
const lines = output
|
|
103
|
-
.split(/\r?\n/)
|
|
104
|
-
.map((line) => sanitizeLine(line))
|
|
105
|
-
.filter((line) => line.length > 0);
|
|
106
|
-
if (lines.length === 0) {
|
|
107
|
-
return undefined;
|
|
108
|
-
}
|
|
109
|
-
// Find first line with error indicators
|
|
110
|
-
const errorLine = lines.find((line) => isErrorLine(line));
|
|
111
|
-
if (errorLine) {
|
|
112
|
-
return errorLine;
|
|
113
|
-
}
|
|
114
|
-
// Fall back to first non-detail line
|
|
115
|
-
const firstMain = lines.find((line) => !isDetailLine(line));
|
|
116
|
-
return firstMain;
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Truncate a message to a maximum length, adding ellipsis if needed.
|
|
120
|
-
*/
|
|
121
|
-
export function truncateMessage(message, maxLength = 140) {
|
|
122
|
-
if (message.length <= maxLength) {
|
|
123
|
-
return message;
|
|
124
|
-
}
|
|
125
|
-
return `${message.slice(0, maxLength - 1)}…`;
|
|
126
|
-
}
|
|
127
|
-
// --- Tool-Specific Sanitizers ---
|
|
128
|
-
/**
|
|
129
|
-
* Sanitize TypeScript/LSP diagnostic output.
|
|
130
|
-
*/
|
|
131
|
-
export function sanitizeTsDiagnostic(output) {
|
|
132
|
-
if (!output)
|
|
133
|
-
return "";
|
|
134
|
-
// TypeScript errors often look like:
|
|
135
|
-
// error TS2322: Type 'string' is not assignable to type 'number'.
|
|
136
|
-
// or:
|
|
137
|
-
// src/file.ts(10,5): error TS2322: ...
|
|
138
|
-
const lines = output
|
|
139
|
-
.split(/\r?\n/)
|
|
140
|
-
.map((line) => stripAnsi(line))
|
|
141
|
-
.filter((line) => line.length > 0)
|
|
142
|
-
// Filter out non-error lines
|
|
143
|
-
.filter((line) => line.includes("error TS") || line.includes("warning TS"));
|
|
144
|
-
// If we have file:line:col format errors, extract those
|
|
145
|
-
const fileErrors = lines
|
|
146
|
-
.filter((line) => /^.+?\([\d,]+\):/.test(line))
|
|
147
|
-
.map((line) => {
|
|
148
|
-
// Extract file:line:col from the beginning
|
|
149
|
-
const match = line.match(/^(.+?\([\d,]+\):)\s*(.+)/);
|
|
150
|
-
if (match) {
|
|
151
|
-
return `${match[1]} ${match[2].trim()}`;
|
|
152
|
-
}
|
|
153
|
-
return line.trim();
|
|
154
|
-
});
|
|
155
|
-
if (fileErrors.length > 0) {
|
|
156
|
-
return fileErrors.slice(0, 10).join("\n");
|
|
157
|
-
}
|
|
158
|
-
// Otherwise just return error lines
|
|
159
|
-
return lines.slice(0, 5).join("\n");
|
|
160
|
-
}
|
|
161
|
-
// --- Helper Functions ---
|
|
162
|
-
/**
|
|
163
|
-
* Extract error/warning lines from tool output.
|
|
164
|
-
* Filters and formats lines containing diagnostic information.
|
|
165
|
-
*/
|
|
166
|
-
function extractDiagnosticLines(output, predicate, maxLines = 10) {
|
|
167
|
-
if (!output)
|
|
168
|
-
return "";
|
|
169
|
-
return output
|
|
170
|
-
.split(/\r?\n/)
|
|
171
|
-
.map((line) => stripAnsi(line))
|
|
172
|
-
.filter((line) => line.length > 0)
|
|
173
|
-
.filter(predicate)
|
|
174
|
-
.slice(0, maxLines)
|
|
175
|
-
.join("\n");
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Format JSON diagnostics array into readable output.
|
|
179
|
-
*/
|
|
180
|
-
function formatJsonDiagnostics(diags, formatter) {
|
|
181
|
-
return diags.slice(0, 10).map(formatter).join("\n");
|
|
182
|
-
}
|
|
183
|
-
// --- Tool-Specific Sanitizers ---
|
|
184
|
-
/**
|
|
185
|
-
* Sanitize Rust cargo output.
|
|
186
|
-
*/
|
|
187
|
-
export function sanitizeRustOutput(output) {
|
|
188
|
-
if (!output)
|
|
189
|
-
return "";
|
|
190
|
-
return extractDiagnosticLines(output, (line) => {
|
|
191
|
-
const clean = stripAnsi(line);
|
|
192
|
-
return (isErrorLine(clean) || /^\s*-->\s+/.test(clean) // rustc source locations
|
|
193
|
-
);
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Sanitize Go vet output.
|
|
198
|
-
*/
|
|
199
|
-
export function sanitizeGoOutput(output) {
|
|
200
|
-
if (!output)
|
|
201
|
-
return "";
|
|
202
|
-
return extractDiagnosticLines(output, (line) => {
|
|
203
|
-
const clean = stripAnsi(line);
|
|
204
|
-
return /^\.\/|\.go:/.test(clean) || isErrorLine(clean);
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Sanitize Biome output.
|
|
209
|
-
*/
|
|
210
|
-
export function sanitizeBiomeOutput(output) {
|
|
211
|
-
if (!output)
|
|
212
|
-
return "";
|
|
213
|
-
try {
|
|
214
|
-
const data = JSON.parse(output);
|
|
215
|
-
if (data.diagnostics && Array.isArray(data.diagnostics)) {
|
|
216
|
-
return formatJsonDiagnostics(data.diagnostics, (d) => {
|
|
217
|
-
const loc = d.location;
|
|
218
|
-
const file = loc?.path ? `${loc.path}` : "";
|
|
219
|
-
const line = loc?.span?.start?.line ?? 0;
|
|
220
|
-
const msg = d.message || "";
|
|
221
|
-
return file ? `${file}:${line + 1} ${msg}` : msg;
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
catch (err) {
|
|
226
|
-
void err;
|
|
227
|
-
// Not JSON, fall through to text processing
|
|
228
|
-
}
|
|
229
|
-
// Text output processing
|
|
230
|
-
return extractDiagnosticLines(output, (line) => {
|
|
231
|
-
const clean = stripAnsi(line);
|
|
232
|
-
return isErrorLine(clean) || clean.includes("hint");
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Sanitize Ruff output.
|
|
237
|
-
*/
|
|
238
|
-
export function sanitizeRuffOutput(output) {
|
|
239
|
-
if (!output)
|
|
240
|
-
return "";
|
|
241
|
-
try {
|
|
242
|
-
const data = JSON.parse(output);
|
|
243
|
-
if (Array.isArray(data)) {
|
|
244
|
-
return formatJsonDiagnostics(data, (d) => {
|
|
245
|
-
const row = d.location?.row ?? 0;
|
|
246
|
-
const col = d.location?.column ?? 0;
|
|
247
|
-
const code = d.code || "";
|
|
248
|
-
const msg = d.message || "";
|
|
249
|
-
return `${row}:${col} [${code}] ${msg}`;
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
catch (err) {
|
|
254
|
-
void err;
|
|
255
|
-
// Not JSON, fall through
|
|
256
|
-
}
|
|
257
|
-
// Text output
|
|
258
|
-
return extractDiagnosticLines(output, (line) => {
|
|
259
|
-
const clean = stripAnsi(line);
|
|
260
|
-
return isErrorLine(clean) || /\[(E|W)\d+\]/.test(clean);
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Sanitize tool output and return both a summary and full details.
|
|
265
|
-
* Summary is the first error line, details is the full cleaned output.
|
|
266
|
-
*/
|
|
267
|
-
export function sanitizeToolOutput(output, maxSummaryLength = 140) {
|
|
268
|
-
if (!output || typeof output !== "string") {
|
|
269
|
-
return { summary: undefined, details: undefined, truncated: false };
|
|
270
|
-
}
|
|
271
|
-
const sanitized = sanitizeOutput(output);
|
|
272
|
-
const lines = sanitized.split("\n").filter((l) => l.length > 0);
|
|
273
|
-
if (lines.length === 0) {
|
|
274
|
-
return { summary: undefined, details: undefined, truncated: false };
|
|
275
|
-
}
|
|
276
|
-
// Summary: first line with error indicators, or first line
|
|
277
|
-
const summary = extractErrorMessage(output);
|
|
278
|
-
const truncatedSummary = truncateMessage(summary ?? lines[0], maxSummaryLength);
|
|
279
|
-
// Details: all lines up to a reasonable limit
|
|
280
|
-
const MAX_DETAIL_LINES = 20;
|
|
281
|
-
const detailsLines = lines.slice(0, MAX_DETAIL_LINES);
|
|
282
|
-
const details = detailsLines.join("\n");
|
|
283
|
-
const truncated = lines.length > MAX_DETAIL_LINES;
|
|
284
|
-
return {
|
|
285
|
-
summary: truncatedSummary,
|
|
286
|
-
details: truncated
|
|
287
|
-
? `${details}\n... and ${lines.length - MAX_DETAIL_LINES} more lines`
|
|
288
|
-
: details,
|
|
289
|
-
truncated,
|
|
290
|
-
};
|
|
291
|
-
}
|