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,325 +0,0 @@
1
- /**
2
- * Ruff Client for pi-lens
3
- *
4
- * Fast Python linting and formatting via Ruff CLI.
5
- * Replaces flake8, pylint, isort, black, pyupgrade.
6
- *
7
- * Requires: pip install ruff
8
- * Docs: https://docs.astral.sh/ruff/
9
- */
10
- import * as fs from "node:fs";
11
- import * as path from "node:path";
12
- import { isFileKind } from "./file-kinds.js";
13
- import { safeSpawn } from "./safe-spawn.js";
14
- // --- Client ---
15
- export class RuffClient {
16
- constructor(verbose = false) {
17
- this.ruffAvailable = null;
18
- this.log = verbose
19
- ? (msg) => console.error(`[ruff] ${msg}`)
20
- : () => { };
21
- }
22
- /**
23
- * Check if ruff CLI is available, auto-install if not
24
- */
25
- async ensureAvailable() {
26
- // Fast path: already checked
27
- if (this.ruffAvailable !== null)
28
- return this.ruffAvailable;
29
- // Check if available in PATH
30
- const result = safeSpawn("ruff", ["--version"], {
31
- timeout: 5000,
32
- });
33
- this.ruffAvailable = !result.error && result.status === 0;
34
- if (this.ruffAvailable) {
35
- this.log(`Ruff found: ${result.stdout.trim()}`);
36
- return true;
37
- }
38
- // Auto-install via pi-lens installer
39
- this.log("Ruff not found, attempting auto-install...");
40
- const { ensureTool } = await import("./installer/index.js");
41
- const installedPath = await ensureTool("ruff");
42
- if (installedPath) {
43
- this.log(`Ruff auto-installed: ${installedPath}`);
44
- this.ruffAvailable = true;
45
- return true;
46
- }
47
- this.log("Ruff auto-install failed");
48
- return false;
49
- }
50
- /**
51
- * Check if ruff CLI is available (legacy sync method)
52
- * Prefer ensureAvailable() for auto-install behavior
53
- */
54
- isAvailable() {
55
- if (this.ruffAvailable !== null)
56
- return this.ruffAvailable;
57
- try {
58
- const result = safeSpawn("ruff", ["--version"], {
59
- timeout: 5000,
60
- });
61
- this.ruffAvailable = !result.error && result.status === 0;
62
- if (this.ruffAvailable) {
63
- this.log(`Ruff found: ${result.stdout.trim()}`);
64
- }
65
- }
66
- catch (err) {
67
- void err;
68
- this.ruffAvailable = false;
69
- }
70
- return this.ruffAvailable;
71
- }
72
- /**
73
- * Check if a file is a Python file
74
- */
75
- isPythonFile(filePath) {
76
- return isFileKind(filePath, "python");
77
- }
78
- /**
79
- * Lint a Python file
80
- */
81
- checkFile(filePath) {
82
- if (!this.isAvailable())
83
- return [];
84
- const absolutePath = path.resolve(filePath);
85
- if (!fs.existsSync(absolutePath))
86
- return [];
87
- try {
88
- const result = safeSpawn("ruff", [
89
- "check",
90
- "--output-format",
91
- "json",
92
- "--target-version",
93
- "py310",
94
- absolutePath,
95
- ], {
96
- timeout: 10000,
97
- });
98
- // ruff exits 1 when it finds issues (normal)
99
- const output = result.stdout || "";
100
- if (!output.trim())
101
- return [];
102
- return this.parseOutput(output, absolutePath);
103
- }
104
- catch (err) {
105
- this.log(`Check error: ${err.message}`);
106
- return [];
107
- }
108
- }
109
- /**
110
- * Check if file has formatting issues (ruff format --check)
111
- */
112
- checkFormatting(filePath) {
113
- if (!this.isAvailable())
114
- return "";
115
- const absolutePath = path.resolve(filePath);
116
- if (!fs.existsSync(absolutePath))
117
- return "";
118
- try {
119
- const result = safeSpawn("ruff", ["format", "--check", "--diff", absolutePath], {
120
- timeout: 10000,
121
- });
122
- // ruff format --check exits 1 when changes needed
123
- if (result.status === 0)
124
- return "";
125
- const diff = result.stdout || "";
126
- if (!diff.trim())
127
- return "";
128
- // Count lines that would change
129
- const diffLines = diff
130
- .split("\n")
131
- .filter((l) => l.startsWith("+") || l.startsWith("-")).length;
132
- return `[Ruff Format] ${diffLines} line(s) would change — run 'ruff format ${path.basename(filePath)}' to fix`;
133
- }
134
- catch (err) {
135
- void err;
136
- return "";
137
- } // Intentionally return empty string on diff failure
138
- }
139
- /**
140
- * Auto-fix linting issues (writes to disk)
141
- */
142
- fixFile(filePath) {
143
- if (!this.isAvailable())
144
- return {
145
- success: false,
146
- changed: false,
147
- fixed: 0,
148
- error: "Ruff not available",
149
- };
150
- const absolutePath = path.resolve(filePath);
151
- if (!fs.existsSync(absolutePath))
152
- return {
153
- success: false,
154
- changed: false,
155
- fixed: 0,
156
- error: "File not found",
157
- };
158
- const content = fs.readFileSync(absolutePath, "utf-8");
159
- try {
160
- const beforeDiags = this.checkFile(filePath);
161
- const fixableCount = beforeDiags.filter((d) => d.fixable).length;
162
- const result = safeSpawn("ruff", ["check", "--fix", absolutePath], {
163
- timeout: 15000,
164
- });
165
- if (result.error) {
166
- return {
167
- success: false,
168
- changed: false,
169
- fixed: 0,
170
- error: result.error.message,
171
- };
172
- }
173
- const fixed = fs.readFileSync(absolutePath, "utf-8");
174
- const changed = content !== fixed;
175
- if (changed) {
176
- this.log(`Fixed ${fixableCount} issue(s) in ${path.basename(filePath)}`);
177
- }
178
- return { success: true, changed, fixed: fixableCount };
179
- }
180
- catch (err) {
181
- return { success: false, changed: false, fixed: 0, error: err.message };
182
- }
183
- }
184
- /**
185
- * Fix multiple Python files at once (much faster than file-by-file)
186
- */
187
- fixFiles(filePaths) {
188
- if (!this.isAvailable()) {
189
- return {
190
- success: false,
191
- fixed: 0,
192
- changed: 0,
193
- error: "Ruff not available",
194
- };
195
- }
196
- // Filter to existing Python files
197
- const validFiles = filePaths
198
- .map((f) => path.resolve(f))
199
- .filter((f) => fs.existsSync(f) && f.endsWith(".py"));
200
- if (validFiles.length === 0) {
201
- return { success: true, fixed: 0, changed: 0 };
202
- }
203
- try {
204
- // Count fixable issues before fixing
205
- let totalFixable = 0;
206
- for (const file of validFiles) {
207
- const diags = this.checkFile(file);
208
- totalFixable += diags.filter((d) => d.fixable).length;
209
- }
210
- // Run ruff once on all files - much faster than per file
211
- const result = safeSpawn("ruff", ["check", "--fix", ...validFiles], {
212
- timeout: 60000, // Longer timeout for batch
213
- });
214
- if (result.error) {
215
- return {
216
- success: false,
217
- fixed: 0,
218
- changed: 0,
219
- error: result.error.message,
220
- };
221
- }
222
- this.log(`Fixed ${totalFixable} issue(s) in ${validFiles.length} file(s)`);
223
- return { success: true, fixed: totalFixable, changed: validFiles.length };
224
- }
225
- catch (err) {
226
- return {
227
- success: false,
228
- fixed: 0,
229
- changed: 0,
230
- error: err.message,
231
- };
232
- }
233
- }
234
- /**
235
- * Format a Python file (writes to disk)
236
- */
237
- formatFile(filePath) {
238
- if (!this.isAvailable())
239
- return { success: false, changed: false, error: "Ruff not available" };
240
- const absolutePath = path.resolve(filePath);
241
- if (!fs.existsSync(absolutePath))
242
- return { success: false, changed: false, error: "File not found" };
243
- const content = fs.readFileSync(absolutePath, "utf-8");
244
- try {
245
- const result = safeSpawn("ruff", ["format", absolutePath], {
246
- timeout: 10000,
247
- });
248
- if (result.error) {
249
- return { success: false, changed: false, error: result.error.message };
250
- }
251
- const formatted = fs.readFileSync(absolutePath, "utf-8");
252
- const changed = content !== formatted;
253
- if (changed) {
254
- this.log(`Formatted ${path.basename(filePath)}`);
255
- }
256
- return { success: true, changed };
257
- }
258
- catch (err) {
259
- return { success: false, changed: false, error: err.message };
260
- }
261
- }
262
- /**
263
- * Format diagnostics for LLM consumption
264
- */
265
- formatDiagnostics(diags) {
266
- if (diags.length === 0)
267
- return "";
268
- const errors = diags.filter((d) => d.severity === "error");
269
- const warnings = diags.filter((d) => d.severity === "warning");
270
- const fixable = diags.filter((d) => d.fixable);
271
- let result = `[Ruff] ${diags.length} issue(s)`;
272
- if (errors.length)
273
- result += ` — ${errors.length} error(s)`;
274
- if (warnings.length)
275
- result += ` — ${warnings.length} warning(s)`;
276
- if (fixable.length)
277
- result += ` — ${fixable.length} auto-fixable`;
278
- result += ":\n";
279
- for (const d of diags.slice(0, 15)) {
280
- const loc = d.line === d.endLine
281
- ? `L${d.line}:${d.column}-${d.endColumn}`
282
- : `L${d.line}:${d.column}-L${d.endLine}:${d.endColumn}`;
283
- const fix = d.fixable ? " [fixable]" : "";
284
- result += ` [${d.rule}] ${loc} ${d.message}${fix}\n`;
285
- }
286
- if (diags.length > 15) {
287
- result += ` ... and ${diags.length - 15} more\n`;
288
- }
289
- if (fixable.length > 0) {
290
- result += `\n Run 'ruff check --fix ${path.basename(diags[0].file)}' to auto-fix ${fixable.length} issue(s)\n`;
291
- }
292
- return result;
293
- }
294
- // --- Internal ---
295
- parseOutput(output, filterFile) {
296
- if (!output.trim())
297
- return [];
298
- try {
299
- const items = JSON.parse(output);
300
- const diagnostics = [];
301
- for (const item of items) {
302
- // Filter to single file if requested
303
- if (filterFile && path.resolve(item.filename) !== filterFile)
304
- continue;
305
- diagnostics.push({
306
- line: item.location.row - 1, // ruff is 1-indexed
307
- column: item.location.column - 1,
308
- endLine: item.end_location.row - 1,
309
- endColumn: item.end_location.column - 1,
310
- severity: item.code?.startsWith("E") ? "error" : "warning",
311
- message: item.message,
312
- rule: item.code || "unknown",
313
- file: item.filename,
314
- fixable: item.fix !== null,
315
- });
316
- }
317
- return diagnostics;
318
- }
319
- catch (err) {
320
- void err;
321
- this.log("Failed to parse ruff JSON output");
322
- return [];
323
- }
324
- }
325
- }
@@ -1,132 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
- import { RuffClient } from "./ruff-client.js";
3
- import { createTempFile, setupTestEnvironment } from "./test-utils.js";
4
- describe("RuffClient", () => {
5
- let client;
6
- let tmpDir;
7
- let cleanup;
8
- beforeEach(() => {
9
- client = new RuffClient();
10
- ({ tmpDir, cleanup } = setupTestEnvironment("pi-lens-ruff-test-"));
11
- });
12
- afterEach(() => {
13
- cleanup();
14
- });
15
- afterEach(() => {
16
- cleanup();
17
- });
18
- describe("isPythonFile", () => {
19
- it("should recognize Python files", () => {
20
- expect(client.isPythonFile("test.py")).toBe(true);
21
- expect(client.isPythonFile("module.py")).toBe(true);
22
- });
23
- it("should not recognize non-Python files", () => {
24
- expect(client.isPythonFile("test.ts")).toBe(false);
25
- expect(client.isPythonFile("test.js")).toBe(false);
26
- expect(client.isPythonFile("test.txt")).toBe(false);
27
- });
28
- });
29
- describe("isAvailable", () => {
30
- it("should check ruff availability", () => {
31
- const available = client.isAvailable();
32
- expect(typeof available).toBe("boolean");
33
- });
34
- });
35
- describe("checkFile", () => {
36
- it("should return empty array for non-existent files", () => {
37
- if (!client.isAvailable())
38
- return;
39
- const result = client.checkFile("/nonexistent/file.py");
40
- expect(result).toEqual([]);
41
- });
42
- it("should detect lint issues in Python code", () => {
43
- if (!client.isAvailable())
44
- return;
45
- const content = `
46
- import os
47
- import sys
48
-
49
- x = 1
50
- `;
51
- const filePath = createTempFile(tmpDir, "test.py", content);
52
- const result = client.checkFile(filePath);
53
- // Should detect unused imports
54
- expect(result.some((d) => d.rule === "F401" || d.message.includes("unused"))).toBe(true);
55
- });
56
- it("should return array of diagnostics", () => {
57
- if (!client.isAvailable())
58
- return;
59
- const content = `
60
- def foo():
61
- x = undefined_variable
62
- return x
63
- `;
64
- const filePath = createTempFile(tmpDir, "test.py", content);
65
- const result = client.checkFile(filePath);
66
- // Should return an array
67
- expect(Array.isArray(result)).toBe(true);
68
- });
69
- });
70
- describe("formatDiagnostics", () => {
71
- it("should format diagnostics for display", () => {
72
- const diags = [
73
- {
74
- line: 1,
75
- column: 0,
76
- endLine: 1,
77
- endColumn: 10,
78
- severity: "error",
79
- message: "Undefined name 'x'",
80
- rule: "F821",
81
- file: "test.py",
82
- fixable: false,
83
- },
84
- ];
85
- const formatted = client.formatDiagnostics(diags);
86
- expect(formatted).toContain("Ruff");
87
- expect(formatted).toContain("F821");
88
- expect(formatted).toContain("Undefined name");
89
- });
90
- it("should show fixable count", () => {
91
- const diags = [
92
- {
93
- line: 1,
94
- column: 0,
95
- endLine: 1,
96
- endColumn: 10,
97
- severity: "warning",
98
- message: "Unused import",
99
- rule: "F401",
100
- file: "test.py",
101
- fixable: true,
102
- },
103
- ];
104
- const formatted = client.formatDiagnostics(diags);
105
- expect(formatted).toContain("fixable");
106
- });
107
- });
108
- describe("checkFormatting", () => {
109
- it("should detect formatting issues", () => {
110
- if (!client.isAvailable())
111
- return;
112
- const content = `x=1
113
- y=2
114
- `;
115
- const filePath = createTempFile(tmpDir, "test.py", content);
116
- const result = client.checkFormatting(filePath);
117
- // Should suggest formatting (missing spaces around =)
118
- expect(typeof result).toBe("string");
119
- });
120
- it("should return empty string for well-formatted code", () => {
121
- if (!client.isAvailable())
122
- return;
123
- const content = `x = 1
124
- y = 2
125
- `;
126
- const filePath = createTempFile(tmpDir, "test.py", content);
127
- const result = client.checkFormatting(filePath);
128
- // Well-formatted code should return empty or minimal output
129
- expect(result).toBe("");
130
- });
131
- });
132
- });
@@ -1,153 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
- import { RuffClient } from "./ruff-client.js";
3
- import { createTempFile, setupTestEnvironment } from "./test-utils.js";
4
-
5
- describe("RuffClient", () => {
6
- let client: RuffClient;
7
- let tmpDir: string;
8
- let cleanup: () => void;
9
-
10
- beforeEach(() => {
11
- client = new RuffClient();
12
- ({ tmpDir, cleanup } = setupTestEnvironment("pi-lens-ruff-test-"));
13
- });
14
-
15
- afterEach(() => {
16
- cleanup();
17
- });
18
-
19
- afterEach(() => {
20
- cleanup();
21
- });
22
-
23
- describe("isPythonFile", () => {
24
- it("should recognize Python files", () => {
25
- expect(client.isPythonFile("test.py")).toBe(true);
26
- expect(client.isPythonFile("module.py")).toBe(true);
27
- });
28
-
29
- it("should not recognize non-Python files", () => {
30
- expect(client.isPythonFile("test.ts")).toBe(false);
31
- expect(client.isPythonFile("test.js")).toBe(false);
32
- expect(client.isPythonFile("test.txt")).toBe(false);
33
- });
34
- });
35
-
36
- describe("isAvailable", () => {
37
- it("should check ruff availability", () => {
38
- const available = client.isAvailable();
39
- expect(typeof available).toBe("boolean");
40
- });
41
- });
42
-
43
- describe("checkFile", () => {
44
- it("should return empty array for non-existent files", () => {
45
- if (!client.isAvailable()) return;
46
- const result = client.checkFile("/nonexistent/file.py");
47
- expect(result).toEqual([]);
48
- });
49
-
50
- it("should detect lint issues in Python code", () => {
51
- if (!client.isAvailable()) return;
52
-
53
- const content = `
54
- import os
55
- import sys
56
-
57
- x = 1
58
- `;
59
- const filePath = createTempFile(tmpDir, "test.py", content);
60
- const result = client.checkFile(filePath);
61
-
62
- // Should detect unused imports
63
- expect(
64
- result.some((d) => d.rule === "F401" || d.message.includes("unused")),
65
- ).toBe(true);
66
- });
67
-
68
- it("should return array of diagnostics", () => {
69
- if (!client.isAvailable()) return;
70
-
71
- const content = `
72
- def foo():
73
- x = undefined_variable
74
- return x
75
- `;
76
- const filePath = createTempFile(tmpDir, "test.py", content);
77
- const result = client.checkFile(filePath);
78
-
79
- // Should return an array
80
- expect(Array.isArray(result)).toBe(true);
81
- });
82
- });
83
-
84
- describe("formatDiagnostics", () => {
85
- it("should format diagnostics for display", () => {
86
- const diags = [
87
- {
88
- line: 1,
89
- column: 0,
90
- endLine: 1,
91
- endColumn: 10,
92
- severity: "error" as const,
93
- message: "Undefined name 'x'",
94
- rule: "F821",
95
- file: "test.py",
96
- fixable: false,
97
- },
98
- ];
99
-
100
- const formatted = client.formatDiagnostics(diags);
101
- expect(formatted).toContain("Ruff");
102
- expect(formatted).toContain("F821");
103
- expect(formatted).toContain("Undefined name");
104
- });
105
-
106
- it("should show fixable count", () => {
107
- const diags = [
108
- {
109
- line: 1,
110
- column: 0,
111
- endLine: 1,
112
- endColumn: 10,
113
- severity: "warning" as const,
114
- message: "Unused import",
115
- rule: "F401",
116
- file: "test.py",
117
- fixable: true,
118
- },
119
- ];
120
-
121
- const formatted = client.formatDiagnostics(diags);
122
- expect(formatted).toContain("fixable");
123
- });
124
- });
125
-
126
- describe("checkFormatting", () => {
127
- it("should detect formatting issues", () => {
128
- if (!client.isAvailable()) return;
129
-
130
- const content = `x=1
131
- y=2
132
- `;
133
- const filePath = createTempFile(tmpDir, "test.py", content);
134
- const result = client.checkFormatting(filePath);
135
-
136
- // Should suggest formatting (missing spaces around =)
137
- expect(typeof result).toBe("string");
138
- });
139
-
140
- it("should return empty string for well-formatted code", () => {
141
- if (!client.isAvailable()) return;
142
-
143
- const content = `x = 1
144
- y = 2
145
- `;
146
- const filePath = createTempFile(tmpDir, "test.py", content);
147
- const result = client.checkFormatting(filePath);
148
-
149
- // Well-formatted code should return empty or minimal output
150
- expect(result).toBe("");
151
- });
152
- });
153
- });
@@ -1,97 +0,0 @@
1
- /**
2
- * Project rules scanner for pi-lens.
3
- *
4
- * Scans for rule files that other tools/agents may have left:
5
- * - .claude/rules/ — Claude Code rule files
6
- * - .agents/rules/ — Generic agent rule files
7
- * - .cursorrules — Cursor IDE rules
8
- * - CLAUDE.md — Claude Code project context
9
- * - AGENTS.md — Generic agent context
10
- *
11
- * These are surfaced in the system prompt so the agent knows
12
- * to read them when relevant. pi-lens architect.yaml handles
13
- * the automated regex-based checks separately.
14
- */
15
- import * as fs from "node:fs";
16
- import * as path from "node:path";
17
- const RULE_DIRS = [
18
- { dir: ".claude/rules", source: ".claude/rules" },
19
- { dir: ".agents/rules", source: ".agents/rules" },
20
- ];
21
- const RULE_FILES = [
22
- { file: "CLAUDE.md", source: "root" },
23
- { file: "AGENTS.md", source: "root" },
24
- { file: ".cursorrules", source: "root" },
25
- ];
26
- function findMarkdownFiles(dir, baseDir) {
27
- const results = [];
28
- if (!fs.existsSync(dir))
29
- return results;
30
- const entries = fs.readdirSync(dir, { withFileTypes: true });
31
- for (const entry of entries) {
32
- const fullPath = path.join(dir, entry.name);
33
- if (entry.isDirectory()) {
34
- results.push(...findMarkdownFiles(fullPath, baseDir));
35
- }
36
- else if (entry.isFile() && entry.name.endsWith(".md")) {
37
- results.push({
38
- source: path.relative(baseDir, dir) || path.basename(baseDir),
39
- name: entry.name,
40
- filePath: fullPath,
41
- relativePath: path.relative(baseDir, fullPath).replace(/\\/g, "/"),
42
- });
43
- }
44
- }
45
- return results;
46
- }
47
- export function scanProjectRules(cwd) {
48
- const rules = [];
49
- // Scan rule directories
50
- for (const { dir, source } of RULE_DIRS) {
51
- const dirPath = path.join(cwd, dir);
52
- if (fs.existsSync(dirPath)) {
53
- const found = findMarkdownFiles(dirPath, path.join(cwd, dir));
54
- for (const rule of found) {
55
- rules.push({
56
- source,
57
- name: rule.name,
58
- filePath: rule.filePath,
59
- relativePath: `${dir}/${rule.relativePath}`,
60
- });
61
- }
62
- }
63
- }
64
- // Check for root-level rule files
65
- for (const { file, source } of RULE_FILES) {
66
- const filePath = path.join(cwd, file);
67
- if (fs.existsSync(filePath)) {
68
- rules.push({
69
- source,
70
- name: file,
71
- filePath,
72
- relativePath: file,
73
- });
74
- }
75
- }
76
- return {
77
- rules,
78
- hasCustomRules: rules.length > 0,
79
- };
80
- }
81
- export function formatRulesForPrompt(result) {
82
- if (!result.hasCustomRules)
83
- return "";
84
- // Group by source
85
- const bySource = new Map();
86
- for (const rule of result.rules) {
87
- const existing = bySource.get(rule.source) ?? [];
88
- existing.push(rule);
89
- bySource.set(rule.source, existing);
90
- }
91
- const sections = [];
92
- for (const [source, rules] of bySource) {
93
- const list = rules.map((r) => `- \`${r.relativePath}\``).join("\n");
94
- sections.push(`From ${source}/:\n${list}`);
95
- }
96
- return sections.join("\n\n");
97
- }