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.
Files changed (207) hide show
  1. package/CHANGELOG.md +10 -2
  2. package/package.json +4 -4
  3. package/tsconfig.json +1 -1
  4. package/clients/__tests__/file-time.test.js +0 -216
  5. package/clients/__tests__/file-time.test.ts +0 -276
  6. package/clients/__tests__/format-service.test.js +0 -245
  7. package/clients/__tests__/format-service.test.ts +0 -339
  8. package/clients/__tests__/formatters.test.js +0 -271
  9. package/clients/__tests__/formatters.test.ts +0 -401
  10. package/clients/agent-behavior-client.js +0 -110
  11. package/clients/agent-behavior-client.test.js +0 -94
  12. package/clients/agent-behavior-client.test.ts +0 -116
  13. package/clients/amain-types.js +0 -164
  14. package/clients/architect-client.js +0 -291
  15. package/clients/ast-grep-client.js +0 -253
  16. package/clients/ast-grep-parser.js +0 -84
  17. package/clients/ast-grep-rule-manager.js +0 -89
  18. package/clients/ast-grep-types.js +0 -9
  19. package/clients/auto-loop.js +0 -131
  20. package/clients/biome-client.js +0 -420
  21. package/clients/biome-client.test.js +0 -144
  22. package/clients/biome-client.test.ts +0 -163
  23. package/clients/cache/rule-cache.js +0 -72
  24. package/clients/cache-manager.js +0 -245
  25. package/clients/cache-manager.test.js +0 -197
  26. package/clients/cache-manager.test.ts +0 -299
  27. package/clients/complexity-client.js +0 -675
  28. package/clients/complexity-client.test.js +0 -234
  29. package/clients/complexity-client.test.ts +0 -255
  30. package/clients/config-validator.js +0 -465
  31. package/clients/dependency-checker.js +0 -325
  32. package/clients/dependency-checker.test.js +0 -60
  33. package/clients/dependency-checker.test.ts +0 -71
  34. package/clients/dispatch/__tests__/autofix-integration.test.js +0 -245
  35. package/clients/dispatch/__tests__/autofix-integration.test.ts +0 -300
  36. package/clients/dispatch/__tests__/runner-registration.test.js +0 -234
  37. package/clients/dispatch/__tests__/runner-registration.test.ts +0 -286
  38. package/clients/dispatch/debug.log +0 -1
  39. package/clients/dispatch/dispatcher.edge.test.js +0 -82
  40. package/clients/dispatch/dispatcher.edge.test.ts +0 -100
  41. package/clients/dispatch/dispatcher.format.test.js +0 -46
  42. package/clients/dispatch/dispatcher.format.test.ts +0 -58
  43. package/clients/dispatch/dispatcher.inline.test.js +0 -74
  44. package/clients/dispatch/dispatcher.inline.test.ts +0 -93
  45. package/clients/dispatch/dispatcher.js +0 -381
  46. package/clients/dispatch/dispatcher.test.js +0 -116
  47. package/clients/dispatch/dispatcher.test.ts +0 -149
  48. package/clients/dispatch/integration.js +0 -108
  49. package/clients/dispatch/plan.js +0 -183
  50. package/clients/dispatch/runners/architect.js +0 -83
  51. package/clients/dispatch/runners/architect.test.js +0 -138
  52. package/clients/dispatch/runners/architect.test.ts +0 -162
  53. package/clients/dispatch/runners/ast-grep-napi.js +0 -405
  54. package/clients/dispatch/runners/ast-grep-napi.test.js +0 -107
  55. package/clients/dispatch/runners/ast-grep-napi.test.ts +0 -129
  56. package/clients/dispatch/runners/ast-grep.js +0 -157
  57. package/clients/dispatch/runners/biome.js +0 -55
  58. package/clients/dispatch/runners/config-validation.js +0 -67
  59. package/clients/dispatch/runners/go-vet.js +0 -48
  60. package/clients/dispatch/runners/index.js +0 -47
  61. package/clients/dispatch/runners/lsp.js +0 -102
  62. package/clients/dispatch/runners/oxlint.js +0 -67
  63. package/clients/dispatch/runners/oxlint.test.js +0 -230
  64. package/clients/dispatch/runners/oxlint.test.ts +0 -303
  65. package/clients/dispatch/runners/pyright.js +0 -100
  66. package/clients/dispatch/runners/pyright.test.js +0 -98
  67. package/clients/dispatch/runners/pyright.test.ts +0 -121
  68. package/clients/dispatch/runners/python-slop.js +0 -97
  69. package/clients/dispatch/runners/python-slop.test.js +0 -203
  70. package/clients/dispatch/runners/python-slop.test.ts +0 -298
  71. package/clients/dispatch/runners/ruff.js +0 -48
  72. package/clients/dispatch/runners/rust-clippy.js +0 -102
  73. package/clients/dispatch/runners/scan_codebase.test.js +0 -89
  74. package/clients/dispatch/runners/scan_codebase.test.ts +0 -105
  75. package/clients/dispatch/runners/shellcheck.js +0 -147
  76. package/clients/dispatch/runners/shellcheck.test.js +0 -98
  77. package/clients/dispatch/runners/shellcheck.test.ts +0 -129
  78. package/clients/dispatch/runners/similarity.js +0 -230
  79. package/clients/dispatch/runners/spellcheck.js +0 -106
  80. package/clients/dispatch/runners/spellcheck.test.js +0 -158
  81. package/clients/dispatch/runners/spellcheck.test.ts +0 -214
  82. package/clients/dispatch/runners/tree-sitter.js +0 -246
  83. package/clients/dispatch/runners/ts-lsp.js +0 -125
  84. package/clients/dispatch/runners/ts-slop.js +0 -113
  85. package/clients/dispatch/runners/type-safety.js +0 -142
  86. package/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -134
  87. package/clients/dispatch/runners/utils/runner-helpers.js +0 -115
  88. package/clients/dispatch/runners/utils.js +0 -51
  89. package/clients/dispatch/runners/yaml-rule-parser.js +0 -360
  90. package/clients/dispatch/types.js +0 -16
  91. package/clients/dispatch/utils/format-utils.js +0 -44
  92. package/clients/dogfood.test.js +0 -201
  93. package/clients/dogfood.test.ts +0 -269
  94. package/clients/file-kinds.js +0 -177
  95. package/clients/file-kinds.test.js +0 -169
  96. package/clients/file-kinds.test.ts +0 -210
  97. package/clients/file-time.js +0 -152
  98. package/clients/file-utils.js +0 -40
  99. package/clients/fix-scanners.js +0 -204
  100. package/clients/format-service.js +0 -184
  101. package/clients/formatters.js +0 -488
  102. package/clients/go-client.js +0 -203
  103. package/clients/go-client.test.js +0 -127
  104. package/clients/go-client.test.ts +0 -143
  105. package/clients/installer/index.js +0 -403
  106. package/clients/interviewer-templates.js +0 -75
  107. package/clients/interviewer.js +0 -173
  108. package/clients/jscpd-client.js +0 -196
  109. package/clients/jscpd-client.test.js +0 -127
  110. package/clients/jscpd-client.test.ts +0 -145
  111. package/clients/knip-client.js +0 -239
  112. package/clients/knip-client.test.js +0 -112
  113. package/clients/knip-client.test.ts +0 -128
  114. package/clients/latency-logger.js +0 -40
  115. package/clients/lsp/__tests__/client.test.js +0 -310
  116. package/clients/lsp/__tests__/client.test.ts +0 -412
  117. package/clients/lsp/__tests__/config.test.js +0 -167
  118. package/clients/lsp/__tests__/config.test.ts +0 -217
  119. package/clients/lsp/__tests__/error-recovery.test.js +0 -213
  120. package/clients/lsp/__tests__/error-recovery.test.ts +0 -279
  121. package/clients/lsp/__tests__/integration.test.js +0 -127
  122. package/clients/lsp/__tests__/integration.test.ts +0 -160
  123. package/clients/lsp/__tests__/launch.test.js +0 -313
  124. package/clients/lsp/__tests__/launch.test.ts +0 -394
  125. package/clients/lsp/__tests__/server.test.js +0 -259
  126. package/clients/lsp/__tests__/server.test.ts +0 -332
  127. package/clients/lsp/__tests__/service.test.js +0 -438
  128. package/clients/lsp/__tests__/service.test.ts +0 -530
  129. package/clients/lsp/client.js +0 -350
  130. package/clients/lsp/config.js +0 -112
  131. package/clients/lsp/index.js +0 -318
  132. package/clients/lsp/installer/index.js +0 -391
  133. package/clients/lsp/interactive-install.js +0 -221
  134. package/clients/lsp/language.js +0 -170
  135. package/clients/lsp/launch.js +0 -329
  136. package/clients/lsp/lsp/launch.js +0 -116
  137. package/clients/lsp/lsp/server.js +0 -532
  138. package/clients/lsp/lsp-index.js +0 -10
  139. package/clients/lsp/path-utils.js +0 -5
  140. package/clients/lsp/server.js +0 -725
  141. package/clients/lsp/test-py-spawn/requirements.txt +0 -1
  142. package/clients/lsp/test-py-spawn/test.py +0 -3
  143. package/clients/lsp/test-py-svc/requirements.txt +0 -1
  144. package/clients/lsp/test-py-svc/test.py +0 -3
  145. package/clients/lsp/test-python-project/requirements.txt +0 -1
  146. package/clients/lsp/test-python-project/test.py +0 -5
  147. package/clients/metrics-client.js +0 -107
  148. package/clients/metrics-client.test.js +0 -128
  149. package/clients/metrics-client.test.ts +0 -163
  150. package/clients/metrics-history.js +0 -367
  151. package/clients/path-utils.js +0 -142
  152. package/clients/pipeline.js +0 -272
  153. package/clients/production-readiness.js +0 -522
  154. package/clients/project-index.js +0 -255
  155. package/clients/project-metadata.js +0 -531
  156. package/clients/ruff-client.js +0 -325
  157. package/clients/ruff-client.test.js +0 -132
  158. package/clients/ruff-client.test.ts +0 -153
  159. package/clients/rules-scanner.js +0 -97
  160. package/clients/runner-tracker.js +0 -152
  161. package/clients/rust-client.js +0 -205
  162. package/clients/rust-client.test.js +0 -108
  163. package/clients/rust-client.test.ts +0 -130
  164. package/clients/safe-spawn-async.js +0 -163
  165. package/clients/safe-spawn.js +0 -241
  166. package/clients/sanitize.js +0 -291
  167. package/clients/sanitize.test.js +0 -177
  168. package/clients/sanitize.test.ts +0 -223
  169. package/clients/scan-architectural-debt.js +0 -167
  170. package/clients/scan-utils.js +0 -83
  171. package/clients/secrets-scanner.js +0 -119
  172. package/clients/secrets-scanner.test.js +0 -100
  173. package/clients/secrets-scanner.test.ts +0 -113
  174. package/clients/sg-runner.js +0 -292
  175. package/clients/state-matrix.js +0 -160
  176. package/clients/subprocess-client.js +0 -65
  177. package/clients/symbol-types.js +0 -5
  178. package/clients/test-runner-client.js +0 -523
  179. package/clients/test-runner-client.test.js +0 -192
  180. package/clients/test-runner-client.test.ts +0 -253
  181. package/clients/test-utils.js +0 -27
  182. package/clients/test-utils.ts +0 -36
  183. package/clients/todo-scanner.js +0 -200
  184. package/clients/todo-scanner.test.js +0 -301
  185. package/clients/todo-scanner.test.ts +0 -352
  186. package/clients/tool-availability.js +0 -207
  187. package/clients/tree-sitter-client.js +0 -601
  188. package/clients/tree-sitter-query-loader.js +0 -355
  189. package/clients/tree-sitter-symbol-extractor.js +0 -289
  190. package/clients/ts-service.js +0 -129
  191. package/clients/type-coverage-client.js +0 -127
  192. package/clients/type-coverage-client.test.js +0 -105
  193. package/clients/type-coverage-client.test.ts +0 -125
  194. package/clients/type-safety-client.js +0 -138
  195. package/clients/types.js +0 -11
  196. package/clients/typescript-client.codefix.test.js +0 -157
  197. package/clients/typescript-client.codefix.test.ts +0 -186
  198. package/clients/typescript-client.js +0 -509
  199. package/clients/typescript-client.test.js +0 -105
  200. package/clients/typescript-client.test.ts +0 -126
  201. package/commands/booboo.js +0 -1007
  202. package/commands/fix-from-booboo.js +0 -398
  203. package/commands/fix-simplified.js +0 -618
  204. package/commands/rate.js +0 -281
  205. package/commands/rate.test.js +0 -119
  206. package/commands/rate.test.ts +0 -131
  207. 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
- }
@@ -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
- }
@@ -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
- }