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