pi-lens 3.1.2 → 3.2.0

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 (154) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +16 -12
  3. package/clients/ast-grep-client.js +8 -1
  4. package/clients/ast-grep-client.ts +9 -1
  5. package/clients/biome-client.js +51 -38
  6. package/clients/biome-client.ts +60 -58
  7. package/clients/dependency-checker.js +30 -1
  8. package/clients/dependency-checker.ts +35 -1
  9. package/clients/dispatch/__tests__/runner-registration.test.ts +286 -282
  10. package/clients/dispatch/bus-dispatcher.js +15 -14
  11. package/clients/dispatch/bus-dispatcher.ts +32 -25
  12. package/clients/dispatch/dispatcher.js +18 -25
  13. package/clients/dispatch/dispatcher.test.ts +2 -1
  14. package/clients/dispatch/dispatcher.ts +17 -28
  15. package/clients/dispatch/plan.js +77 -32
  16. package/clients/dispatch/plan.ts +78 -32
  17. package/clients/dispatch/runners/ast-grep-napi.js +36 -376
  18. package/clients/dispatch/runners/ast-grep-napi.ts +60 -433
  19. package/clients/dispatch/runners/index.js +8 -4
  20. package/clients/dispatch/runners/index.ts +8 -4
  21. package/clients/dispatch/runners/lsp.js +65 -0
  22. package/clients/dispatch/runners/lsp.ts +125 -0
  23. package/clients/dispatch/runners/oxlint.js +2 -2
  24. package/clients/dispatch/runners/oxlint.ts +2 -2
  25. package/clients/dispatch/runners/pyright.js +24 -8
  26. package/clients/dispatch/runners/pyright.ts +28 -14
  27. package/clients/dispatch/runners/rust-clippy.js +2 -2
  28. package/clients/dispatch/runners/rust-clippy.ts +2 -4
  29. package/clients/dispatch/runners/tree-sitter.js +14 -2
  30. package/clients/dispatch/runners/tree-sitter.ts +15 -2
  31. package/clients/dispatch/runners/ts-lsp.js +3 -3
  32. package/clients/dispatch/runners/ts-lsp.ts +8 -5
  33. package/clients/dispatch/runners/yaml-rule-parser.js +292 -0
  34. package/clients/dispatch/runners/yaml-rule-parser.ts +338 -0
  35. package/clients/dispatch/types.js +3 -0
  36. package/clients/dispatch/types.ts +3 -0
  37. package/clients/formatters.js +67 -14
  38. package/clients/formatters.ts +68 -15
  39. package/clients/installer/index.js +78 -10
  40. package/clients/installer/index.ts +519 -426
  41. package/clients/jscpd-client.js +28 -0
  42. package/clients/jscpd-client.ts +41 -3
  43. package/clients/knip-client.js +30 -1
  44. package/clients/knip-client.ts +34 -2
  45. package/clients/lsp/__tests__/client.test.ts +64 -41
  46. package/clients/lsp/__tests__/config.test.ts +25 -17
  47. package/clients/lsp/__tests__/launch.test.ts +108 -43
  48. package/clients/lsp/__tests__/service.test.ts +76 -48
  49. package/clients/lsp/client.js +87 -2
  50. package/clients/lsp/client.ts +150 -6
  51. package/clients/lsp/config.js +8 -11
  52. package/clients/lsp/config.ts +24 -21
  53. package/clients/lsp/index.js +69 -0
  54. package/clients/lsp/index.ts +82 -0
  55. package/clients/lsp/interactive-install.js +19 -8
  56. package/clients/lsp/interactive-install.ts +52 -27
  57. package/clients/lsp/launch.js +182 -32
  58. package/clients/lsp/launch.ts +241 -38
  59. package/clients/lsp/path-utils.js +3 -46
  60. package/clients/lsp/path-utils.ts +11 -51
  61. package/clients/lsp/server.js +93 -71
  62. package/clients/lsp/server.ts +173 -131
  63. package/clients/path-utils.js +142 -0
  64. package/clients/path-utils.ts +153 -0
  65. package/clients/ruff-client.js +33 -4
  66. package/clients/ruff-client.ts +44 -13
  67. package/clients/safe-spawn.js +3 -1
  68. package/clients/safe-spawn.ts +3 -1
  69. package/clients/services/effect-integration.js +11 -7
  70. package/clients/services/effect-integration.ts +34 -26
  71. package/clients/sg-runner.js +51 -9
  72. package/clients/sg-runner.ts +58 -15
  73. package/clients/tree-sitter-client.js +12 -0
  74. package/clients/tree-sitter-client.ts +12 -0
  75. package/clients/typescript-client.js +6 -2
  76. package/clients/typescript-client.ts +9 -2
  77. package/commands/booboo.js +2 -4
  78. package/commands/booboo.ts +2 -4
  79. package/index.ts +377 -93
  80. package/package.json +2 -1
  81. package/rules/tree-sitter-queries/tsx/no-nested-links.yml +45 -0
  82. package/rules/tree-sitter-queries/typescript/constructor-super.yml +55 -0
  83. package/rules/tree-sitter-queries/typescript/debugger.yml +1 -1
  84. package/rules/tree-sitter-queries/typescript/no-dupe-class-members.yml +47 -0
  85. package/tsconfig.json +1 -1
  86. package/clients/__tests__/file-time.test.js +0 -216
  87. package/clients/__tests__/format-service.test.js +0 -245
  88. package/clients/__tests__/formatters.test.js +0 -271
  89. package/clients/agent-behavior-client.test.js +0 -94
  90. package/clients/ast-grep-client.test.js +0 -129
  91. package/clients/ast-grep-client.test.ts +0 -155
  92. package/clients/biome-client.test.js +0 -144
  93. package/clients/cache-manager.test.js +0 -197
  94. package/clients/complexity-client.test.js +0 -234
  95. package/clients/dependency-checker.test.js +0 -60
  96. package/clients/dispatch/__tests__/autofix-integration.test.js +0 -245
  97. package/clients/dispatch/__tests__/runner-registration.test.js +0 -236
  98. package/clients/dispatch/dispatcher.edge.test.js +0 -82
  99. package/clients/dispatch/dispatcher.format.test.js +0 -46
  100. package/clients/dispatch/dispatcher.inline.test.js +0 -74
  101. package/clients/dispatch/dispatcher.test.js +0 -115
  102. package/clients/dispatch/runners/architect.test.js +0 -138
  103. package/clients/dispatch/runners/ast-grep-napi.test.js +0 -106
  104. package/clients/dispatch/runners/oxlint.test.js +0 -230
  105. package/clients/dispatch/runners/pyright.test.js +0 -98
  106. package/clients/dispatch/runners/python-slop.test.js +0 -203
  107. package/clients/dispatch/runners/scan_codebase.test.js +0 -89
  108. package/clients/dispatch/runners/shellcheck.test.js +0 -98
  109. package/clients/dispatch/runners/spellcheck.test.js +0 -158
  110. package/clients/dispatch/runners/ts-slop.test.js +0 -180
  111. package/clients/dispatch/runners/ts-slop.test.ts +0 -230
  112. package/clients/dogfood.test.js +0 -201
  113. package/clients/file-kinds.test.js +0 -169
  114. package/clients/go-client.test.js +0 -127
  115. package/clients/jscpd-client.test.js +0 -127
  116. package/clients/knip-client.test.js +0 -112
  117. package/clients/lsp/__tests__/client.test.js +0 -325
  118. package/clients/lsp/__tests__/config.test.js +0 -166
  119. package/clients/lsp/__tests__/error-recovery.test.js +0 -213
  120. package/clients/lsp/__tests__/integration.test.js +0 -127
  121. package/clients/lsp/__tests__/launch.test.js +0 -260
  122. package/clients/lsp/__tests__/server.test.js +0 -259
  123. package/clients/lsp/__tests__/service.test.js +0 -417
  124. package/clients/metrics-client.test.js +0 -141
  125. package/clients/ruff-client.test.js +0 -132
  126. package/clients/rust-client.test.js +0 -108
  127. package/clients/sanitize.test.js +0 -177
  128. package/clients/secrets-scanner.test.js +0 -100
  129. package/clients/services/__tests__/effect-integration.test.js +0 -86
  130. package/clients/test-runner-client.test.js +0 -192
  131. package/clients/todo-scanner.test.js +0 -301
  132. package/clients/type-coverage-client.test.js +0 -105
  133. package/clients/typescript-client.codefix.test.js +0 -157
  134. package/clients/typescript-client.test.js +0 -105
  135. package/commands/clients/ast-grep-client.js +0 -250
  136. package/commands/clients/ast-grep-parser.js +0 -86
  137. package/commands/clients/ast-grep-rule-manager.js +0 -91
  138. package/commands/clients/ast-grep-types.js +0 -9
  139. package/commands/clients/biome-client.js +0 -380
  140. package/commands/clients/complexity-client.js +0 -667
  141. package/commands/clients/file-kinds.js +0 -177
  142. package/commands/clients/file-utils.js +0 -40
  143. package/commands/clients/jscpd-client.js +0 -169
  144. package/commands/clients/knip-client.js +0 -211
  145. package/commands/clients/ruff-client.js +0 -297
  146. package/commands/clients/safe-spawn.js +0 -88
  147. package/commands/clients/scan-utils.js +0 -83
  148. package/commands/clients/sg-runner.js +0 -190
  149. package/commands/clients/types.js +0 -11
  150. package/commands/clients/typescript-client.js +0 -505
  151. package/commands/rate.test.js +0 -119
  152. package/rules/ast-grep-rules/rules/no-dangerously-set-inner-html.yml +0 -13
  153. package/rules/ast-grep-rules/rules/no-debugger.yml +0 -12
  154. package/rules/ast-grep-rules/rules/no-eval.yml +0 -13
@@ -1,271 +0,0 @@
1
- /**
2
- * Formatter Detection Tests
3
- *
4
- * Tests smart detection of formatters based on:
5
- * - Config files (biome.json, .prettierrc, etc.)
6
- * - Dependencies (package.json, requirements.txt)
7
- * - Binary availability
8
- */
9
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
10
- import * as fs from "node:fs";
11
- import * as path from "node:path";
12
- import { biomeFormatter, prettierFormatter, ruffFormatter, blackFormatter, gofmtFormatter, rustfmtFormatter, getFormattersForFile, clearFormatterCache, formatFile, } from "../formatters.js";
13
- import { fileURLToPath } from "url";
14
- import { dirname } from "path";
15
- const __filename = fileURLToPath(import.meta.url);
16
- const __dirname = dirname(__filename);
17
- const TEST_DIR = path.join(__dirname, "..", "..", "test-formatters");
18
- describe("Formatter Detection", () => {
19
- beforeEach(() => {
20
- clearFormatterCache();
21
- if (fs.existsSync(TEST_DIR)) {
22
- fs.rmSync(TEST_DIR, { recursive: true });
23
- }
24
- fs.mkdirSync(TEST_DIR, { recursive: true });
25
- });
26
- afterEach(() => {
27
- clearFormatterCache();
28
- if (fs.existsSync(TEST_DIR)) {
29
- fs.rmSync(TEST_DIR, { recursive: true });
30
- }
31
- });
32
- describe("biomeFormatter.detect()", () => {
33
- it("should detect biome.json config file", async () => {
34
- fs.writeFileSync(path.join(TEST_DIR, "biome.json"), '{"formatter": {}}');
35
- const detected = await biomeFormatter.detect(TEST_DIR);
36
- expect(detected).toBe(true);
37
- });
38
- it("should detect biome.jsonc config file", async () => {
39
- fs.writeFileSync(path.join(TEST_DIR, "biome.jsonc"), "{}");
40
- const detected = await biomeFormatter.detect(TEST_DIR);
41
- expect(detected).toBe(true);
42
- });
43
- it("should detect @biomejs/biome in devDependencies", async () => {
44
- fs.writeFileSync(path.join(TEST_DIR, "package.json"), JSON.stringify({ devDependencies: { "@biomejs/biome": "^1.0.0" } }));
45
- const detected = await biomeFormatter.detect(TEST_DIR);
46
- expect(detected).toBe(true);
47
- });
48
- it("should return false when no biome config", async () => {
49
- const detected = await biomeFormatter.detect(TEST_DIR);
50
- expect(detected).toBe(false);
51
- });
52
- it("should find biome.json in parent directory", async () => {
53
- const subDir = path.join(TEST_DIR, "src", "components");
54
- fs.mkdirSync(subDir, { recursive: true });
55
- fs.writeFileSync(path.join(TEST_DIR, "biome.json"), "{}");
56
- const detected = await biomeFormatter.detect(subDir);
57
- expect(detected).toBe(true);
58
- });
59
- });
60
- describe("prettierFormatter.detect()", () => {
61
- it("should detect .prettierrc config file", async () => {
62
- fs.writeFileSync(path.join(TEST_DIR, ".prettierrc"), "{}");
63
- const detected = await prettierFormatter.detect(TEST_DIR);
64
- expect(detected).toBe(true);
65
- });
66
- it("should detect prettier in devDependencies", async () => {
67
- fs.writeFileSync(path.join(TEST_DIR, "package.json"), JSON.stringify({ devDependencies: { prettier: "^3.0.0" } }));
68
- const detected = await prettierFormatter.detect(TEST_DIR);
69
- expect(detected).toBe(true);
70
- });
71
- it("should detect prettier field in package.json", async () => {
72
- fs.writeFileSync(path.join(TEST_DIR, "package.json"), JSON.stringify({ prettier: { semi: false } }));
73
- const detected = await prettierFormatter.detect(TEST_DIR);
74
- expect(detected).toBe(true);
75
- });
76
- it("should return false when no prettier config", async () => {
77
- const detected = await prettierFormatter.detect(TEST_DIR);
78
- expect(detected).toBe(false);
79
- });
80
- });
81
- describe("ruffFormatter.detect()", () => {
82
- it("should detect [tool.ruff] in pyproject.toml", async () => {
83
- fs.writeFileSync(path.join(TEST_DIR, "pyproject.toml"), "[tool.ruff]\nline-length = 100");
84
- const detected = await ruffFormatter.detect(TEST_DIR);
85
- expect(detected).toBe(true);
86
- });
87
- it("should detect ruff.toml config file", async () => {
88
- fs.writeFileSync(path.join(TEST_DIR, "ruff.toml"), "line-length = 100");
89
- const detected = await ruffFormatter.detect(TEST_DIR);
90
- expect(detected).toBe(true);
91
- });
92
- it("should detect .ruff.toml config file", async () => {
93
- fs.writeFileSync(path.join(TEST_DIR, ".ruff.toml"), "line-length = 100");
94
- const detected = await ruffFormatter.detect(TEST_DIR);
95
- expect(detected).toBe(true);
96
- });
97
- it("should detect ruff in requirements.txt", async () => {
98
- fs.writeFileSync(path.join(TEST_DIR, "requirements.txt"), "ruff==0.1.0\n");
99
- const detected = await ruffFormatter.detect(TEST_DIR);
100
- expect(detected).toBe(true);
101
- });
102
- it("should detect ruff even if [tool.black] exists (no preference logic)", async () => {
103
- // Create pyproject.toml with black config
104
- fs.writeFileSync(path.join(TEST_DIR, "pyproject.toml"), "[tool.black]\nline-length = 100");
105
- // Also write requirements with ruff
106
- fs.writeFileSync(path.join(TEST_DIR, "requirements.txt"), "ruff\n");
107
- // The current implementation doesn't have preference logic
108
- // Both black and ruff would be detected if their configs exist
109
- // This is intentional - users can disable one if needed
110
- const detected = await blackFormatter.detect(TEST_DIR);
111
- expect(detected).toBe(true);
112
- });
113
- });
114
- describe("blackFormatter.detect()", () => {
115
- it("should detect [tool.black] in pyproject.toml", async () => {
116
- fs.writeFileSync(path.join(TEST_DIR, "pyproject.toml"), "[tool.black]\nline-length = 100");
117
- const detected = await blackFormatter.detect(TEST_DIR);
118
- expect(detected).toBe(true);
119
- });
120
- it("should detect black in requirements.txt", async () => {
121
- fs.writeFileSync(path.join(TEST_DIR, "requirements.txt"), "black==23.0.0\n");
122
- const detected = await blackFormatter.detect(TEST_DIR);
123
- expect(detected).toBe(true);
124
- });
125
- it("should return false when no black config", async () => {
126
- const detected = await blackFormatter.detect(TEST_DIR);
127
- expect(detected).toBe(false);
128
- });
129
- });
130
- describe("gofmtFormatter.detect()", () => {
131
- it("should detect gofmt binary availability", async () => {
132
- // This test depends on whether gofmt is installed
133
- // We can't reliably test this in CI, but we can verify the logic
134
- const detected = await gofmtFormatter.detect(TEST_DIR);
135
- // Should return boolean based on binary availability
136
- expect(typeof detected).toBe("boolean");
137
- });
138
- });
139
- describe("rustfmtFormatter.detect()", () => {
140
- it("should detect rustfmt binary availability", async () => {
141
- const detected = await rustfmtFormatter.detect(TEST_DIR);
142
- expect(typeof detected).toBe("boolean");
143
- });
144
- });
145
- describe("getFormattersForFile()", () => {
146
- it("should return formatters for TypeScript file with biome config", async () => {
147
- fs.writeFileSync(path.join(TEST_DIR, "biome.json"), "{}");
148
- const tsFile = path.join(TEST_DIR, "test.ts");
149
- const formatters = await getFormattersForFile(tsFile, TEST_DIR);
150
- expect(formatters.map(f => f.name)).toContain("biome");
151
- });
152
- it("should return formatters for TypeScript file with prettier", async () => {
153
- fs.writeFileSync(path.join(TEST_DIR, "package.json"), JSON.stringify({ devDependencies: { prettier: "^3.0.0" } }));
154
- const tsFile = path.join(TEST_DIR, "test.ts");
155
- const formatters = await getFormattersForFile(tsFile, TEST_DIR);
156
- expect(formatters.map(f => f.name)).toContain("prettier");
157
- });
158
- it("should return multiple formatters for TypeScript file", async () => {
159
- fs.writeFileSync(path.join(TEST_DIR, "biome.json"), "{}");
160
- fs.writeFileSync(path.join(TEST_DIR, "package.json"), JSON.stringify({ devDependencies: { prettier: "^3.0.0" } }));
161
- const tsFile = path.join(TEST_DIR, "test.ts");
162
- const formatters = await getFormattersForFile(tsFile, TEST_DIR);
163
- // Both biome and prettier should be returned
164
- expect(formatters.length).toBeGreaterThanOrEqual(1);
165
- const names = formatters.map(f => f.name);
166
- expect(names).toContain("biome");
167
- });
168
- it("should return ruff for Python file with pyproject.toml", async () => {
169
- fs.writeFileSync(path.join(TEST_DIR, "pyproject.toml"), "[tool.ruff]\nline-length = 100");
170
- const pyFile = path.join(TEST_DIR, "test.py");
171
- const formatters = await getFormattersForFile(pyFile, TEST_DIR);
172
- expect(formatters.map(f => f.name)).toContain("ruff");
173
- });
174
- it("should return black for Python file with black config", async () => {
175
- fs.writeFileSync(path.join(TEST_DIR, "pyproject.toml"), "[tool.black]\nline-length = 100");
176
- const pyFile = path.join(TEST_DIR, "test.py");
177
- const formatters = await getFormattersForFile(pyFile, TEST_DIR);
178
- // Should prefer black over ruff
179
- expect(formatters.map(f => f.name)).toContain("black");
180
- });
181
- it("should return empty array for unsupported extensions", async () => {
182
- const txtFile = path.join(TEST_DIR, "test.txt");
183
- fs.writeFileSync(txtFile, "content");
184
- const formatters = await getFormattersForFile(txtFile, TEST_DIR);
185
- expect(formatters).toEqual([]);
186
- });
187
- it("should cache detection results", async () => {
188
- fs.writeFileSync(path.join(TEST_DIR, "biome.json"), "{}");
189
- const tsFile = path.join(TEST_DIR, "test.ts");
190
- // First call
191
- await getFormattersForFile(tsFile, TEST_DIR);
192
- // Second call should use cache
193
- const formatters = await getFormattersForFile(tsFile, TEST_DIR);
194
- expect(formatters.map(f => f.name)).toContain("biome");
195
- });
196
- });
197
- describe("clearFormatterCache()", () => {
198
- it("should clear cached detection results", async () => {
199
- fs.writeFileSync(path.join(TEST_DIR, "biome.json"), "{}");
200
- const tsFile = path.join(TEST_DIR, "test.ts");
201
- // First detection
202
- await getFormattersForFile(tsFile, TEST_DIR);
203
- // Clear cache
204
- clearFormatterCache();
205
- // Delete config
206
- fs.rmSync(path.join(TEST_DIR, "biome.json"));
207
- // Should re-detect (now without biome)
208
- const formatters = await getFormattersForFile(tsFile, TEST_DIR);
209
- expect(formatters.map(f => f.name)).not.toContain("biome");
210
- });
211
- });
212
- describe("formatFile()", () => {
213
- it("should format file and report changes", async () => {
214
- // Create a simple test - we'll skip actual formatter execution
215
- // because we can't guarantee formatters are installed
216
- const testFile = path.join(TEST_DIR, "test.txt");
217
- fs.writeFileSync(testFile, "unchanged");
218
- const mockFormatter = {
219
- name: "mock",
220
- command: ["echo", "$FILE"],
221
- extensions: [".txt"],
222
- detect: async () => true,
223
- };
224
- const result = await formatFile(testFile, mockFormatter);
225
- // echo command should succeed but not change file
226
- expect(result.success).toBe(true);
227
- });
228
- it("should handle formatter execution with valid command", async () => {
229
- const testFile = path.join(TEST_DIR, "valid.txt");
230
- fs.writeFileSync(testFile, "content");
231
- // Use a valid command that succeeds but doesn't modify file
232
- const mockFormatter = {
233
- name: "valid",
234
- command: process.platform === "win32" ? ["cmd", "/c", "echo", "$FILE"] : ["echo", "$FILE"],
235
- extensions: [".txt"],
236
- detect: async () => true,
237
- };
238
- const result = await formatFile(testFile, mockFormatter);
239
- // Should not throw, completes with success
240
- expect(result).toBeDefined();
241
- expect(typeof result.success).toBe("boolean");
242
- });
243
- });
244
- describe("Formatter extensions", () => {
245
- it("biome should handle TS/JS/JSON/CSS/Vue/Svelte", () => {
246
- expect(biomeFormatter.extensions).toContain(".ts");
247
- expect(biomeFormatter.extensions).toContain(".tsx");
248
- expect(biomeFormatter.extensions).toContain(".js");
249
- expect(biomeFormatter.extensions).toContain(".json");
250
- expect(biomeFormatter.extensions).toContain(".css");
251
- expect(biomeFormatter.extensions).toContain(".vue");
252
- expect(biomeFormatter.extensions).toContain(".svelte");
253
- });
254
- it("prettier should handle Markdown and YAML", () => {
255
- expect(prettierFormatter.extensions).toContain(".md");
256
- expect(prettierFormatter.extensions).toContain(".mdx");
257
- expect(prettierFormatter.extensions).toContain(".yaml");
258
- expect(prettierFormatter.extensions).toContain(".yml");
259
- });
260
- it("ruff should handle Python files", () => {
261
- expect(ruffFormatter.extensions).toContain(".py");
262
- expect(ruffFormatter.extensions).toContain(".pyi");
263
- });
264
- it("gofmt should handle Go files", () => {
265
- expect(gofmtFormatter.extensions).toContain(".go");
266
- });
267
- it("rustfmt should handle Rust files", () => {
268
- expect(rustfmtFormatter.extensions).toContain(".rs");
269
- });
270
- });
271
- });
@@ -1,94 +0,0 @@
1
- import { beforeEach, describe, expect, it } from "vitest";
2
- import { AgentBehaviorClient } from "./agent-behavior-client.js";
3
- describe("AgentBehaviorClient", () => {
4
- let client;
5
- beforeEach(() => {
6
- client = new AgentBehaviorClient();
7
- client.reset();
8
- });
9
- describe("blind write detection", () => {
10
- it("should NOT warn when read precedes write", () => {
11
- client.recordToolCall("read", "src/file.ts");
12
- client.recordToolCall("edit", "src/file.ts");
13
- const warnings = client.recordToolCall("write", "src/file.ts");
14
- expect(warnings).toHaveLength(0);
15
- });
16
- it("should warn when multiple writes happen without reads", () => {
17
- // First write is OK (no history)
18
- client.recordToolCall("write", "src/file1.ts");
19
- // Second write - still in window, accumulates
20
- client.recordToolCall("edit", "src/file2.ts");
21
- // Third write without any read - now we have a pattern
22
- const warnings = client.recordToolCall("edit", "src/file3.ts");
23
- expect(warnings).toHaveLength(1);
24
- expect(warnings[0].type).toBe("blind-write");
25
- });
26
- it("should not warn for single write with no history", () => {
27
- const warnings = client.recordToolCall("write", "src/file.ts");
28
- expect(warnings).toHaveLength(0);
29
- });
30
- });
31
- describe("thrashing detection", () => {
32
- it("should warn after 3 consecutive identical tool calls", () => {
33
- client.recordToolCall("bash", undefined);
34
- // Second call - no warning yet
35
- let warnings = client.recordToolCall("bash", undefined);
36
- expect(warnings).toHaveLength(0);
37
- // Third consecutive - should warn
38
- warnings = client.recordToolCall("bash", undefined);
39
- expect(warnings).toHaveLength(1);
40
- expect(warnings[0].type).toBe("thrashing");
41
- expect(warnings[0].details.callCount).toBe(3);
42
- });
43
- it("should NOT warn for different tool calls", () => {
44
- client.recordToolCall("read", "src/file.ts");
45
- client.recordToolCall("bash", "npm test");
46
- const warnings = client.recordToolCall("edit", "src/file.ts");
47
- expect(warnings).toHaveLength(0);
48
- });
49
- it("should reset count when tool changes", () => {
50
- client.recordToolCall("bash", undefined);
51
- client.recordToolCall("bash", undefined);
52
- // Different tool resets the count
53
- client.recordToolCall("read", "src/file.ts");
54
- // Now start new consecutive sequence
55
- client.recordToolCall("bash", undefined);
56
- const warnings = client.recordToolCall("bash", undefined);
57
- expect(warnings).toHaveLength(0); // Only 2 consecutive, not 3
58
- });
59
- });
60
- describe("edit counting", () => {
61
- it("should track edit count per file", () => {
62
- client.recordToolCall("edit", "src/a.ts");
63
- client.recordToolCall("edit", "src/a.ts");
64
- client.recordToolCall("edit", "src/b.ts");
65
- expect(client.getEditCount("src/a.ts")).toBe(2);
66
- expect(client.getEditCount("src/b.ts")).toBe(1);
67
- expect(client.getEditCount("src/c.ts")).toBe(0);
68
- });
69
- });
70
- describe("formatWarnings", () => {
71
- it("should format multiple warnings", () => {
72
- const warnings = [
73
- {
74
- type: "blind-write",
75
- message: "⚠ BLIND WRITE — editing file",
76
- severity: "warning",
77
- details: {},
78
- },
79
- {
80
- type: "thrashing",
81
- message: "🔴 THRASHING — 3 consecutive calls",
82
- severity: "error",
83
- details: {},
84
- },
85
- ];
86
- const formatted = client.formatWarnings(warnings);
87
- expect(formatted).toContain("BLIND WRITE");
88
- expect(formatted).toContain("THRASHING");
89
- });
90
- it("should return empty string for no warnings", () => {
91
- expect(client.formatWarnings([])).toBe("");
92
- });
93
- });
94
- });
@@ -1,129 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
- import { AstGrepClient } from "./ast-grep-client.js";
3
- import { createTempFile, setupTestEnvironment } from "./test-utils.js";
4
- describe("AstGrepClient", () => {
5
- let client;
6
- let tmpDir;
7
- let cleanup;
8
- beforeEach(() => {
9
- client = new AstGrepClient();
10
- ({ tmpDir, cleanup } = setupTestEnvironment("pi-lens-astgrep-test-"));
11
- });
12
- afterEach(() => {
13
- cleanup();
14
- });
15
- describe("isAvailable", () => {
16
- it("should check ast-grep availability", () => {
17
- const available = client.isAvailable();
18
- expect(typeof available).toBe("boolean");
19
- });
20
- });
21
- describe("scanFile", () => {
22
- it("should return empty array for non-existent files", () => {
23
- if (!client.isAvailable())
24
- return;
25
- const result = client.scanFile("/nonexistent/file.ts");
26
- expect(result).toEqual([]);
27
- });
28
- it("should detect console.log usage", () => {
29
- if (!client.isAvailable())
30
- return;
31
- const content = `
32
- console.log("test");
33
- `;
34
- const filePath = createTempFile(tmpDir, "test.ts", content);
35
- const result = client.scanFile(filePath);
36
- // Should detect console.log
37
- expect(result.some((d) => d.rule === "no-console-log")).toBe(true);
38
- });
39
- });
40
- describe("formatDiagnostics", () => {
41
- it("should format diagnostics for display", () => {
42
- const diags = [
43
- {
44
- line: 1,
45
- column: 0,
46
- endLine: 1,
47
- endColumn: 10,
48
- severity: "warning",
49
- message: "Unexpected var, use let or const instead",
50
- rule: "no-var",
51
- file: "test.ts",
52
- },
53
- ];
54
- const formatted = client.formatDiagnostics(diags);
55
- expect(formatted).toContain("ast-grep");
56
- expect(formatted).toContain("no-var");
57
- });
58
- it("should categorize by severity", () => {
59
- const diags = [
60
- {
61
- line: 1,
62
- column: 0,
63
- endLine: 1,
64
- endColumn: 10,
65
- severity: "warning",
66
- message: "Warning",
67
- rule: "rule1",
68
- file: "test.ts",
69
- },
70
- {
71
- line: 2,
72
- column: 0,
73
- endLine: 2,
74
- endColumn: 10,
75
- severity: "error",
76
- message: "Error",
77
- rule: "rule2",
78
- file: "test.ts",
79
- },
80
- ];
81
- const formatted = client.formatDiagnostics(diags);
82
- expect(formatted).toContain("warning(s)");
83
- expect(formatted).toContain("error(s)");
84
- });
85
- it("should show fixable indicator", () => {
86
- const diags = [
87
- {
88
- line: 1,
89
- column: 0,
90
- endLine: 1,
91
- endColumn: 10,
92
- severity: "warning",
93
- message: "Use const",
94
- rule: "prefer-const",
95
- file: "test.ts",
96
- fix: "const",
97
- },
98
- ];
99
- const formatted = client.formatDiagnostics(diags);
100
- expect(formatted).toContain("fixable");
101
- });
102
- });
103
- describe("search", () => {
104
- it("should search for patterns", async () => {
105
- if (!client.isAvailable())
106
- return;
107
- createTempFile(tmpDir, "test.ts", `
108
- function test() {
109
- console.log("hello");
110
- }
111
- `);
112
- const result = await client.search("console.log($MSG)", "typescript", [
113
- tmpDir,
114
- ]);
115
- expect(result.matches.length).toBeGreaterThan(0);
116
- });
117
- it("should return empty matches for no match", async () => {
118
- if (!client.isAvailable())
119
- return;
120
- createTempFile(tmpDir, "test.ts", `
121
- const x = 1;
122
- `);
123
- const result = await client.search("console.log($MSG)", "typescript", [
124
- tmpDir,
125
- ]);
126
- expect(result.matches.length).toBe(0);
127
- });
128
- });
129
- });
@@ -1,155 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
- import { AstGrepClient } from "./ast-grep-client.js";
3
- import { createTempFile, setupTestEnvironment } from "./test-utils.js";
4
-
5
- describe("AstGrepClient", () => {
6
- let client: AstGrepClient;
7
- let tmpDir: string;
8
- let cleanup: () => void;
9
-
10
- beforeEach(() => {
11
- client = new AstGrepClient();
12
- ({ tmpDir, cleanup } = setupTestEnvironment("pi-lens-astgrep-test-"));
13
- });
14
-
15
- afterEach(() => {
16
- cleanup();
17
- });
18
-
19
- describe("isAvailable", () => {
20
- it("should check ast-grep availability", () => {
21
- const available = client.isAvailable();
22
- expect(typeof available).toBe("boolean");
23
- });
24
- });
25
-
26
- describe("scanFile", () => {
27
- it("should return empty array for non-existent files", () => {
28
- if (!client.isAvailable()) return;
29
- const result = client.scanFile("/nonexistent/file.ts");
30
- expect(result).toEqual([]);
31
- });
32
-
33
- it("should detect console.log usage", () => {
34
- if (!client.isAvailable()) return;
35
-
36
- const content = `
37
- console.log("test");
38
- `;
39
- const filePath = createTempFile(tmpDir, "test.ts", content);
40
- const result = client.scanFile(filePath);
41
-
42
- // Should detect console.log
43
- expect(result.some((d) => d.rule === "no-console-log")).toBe(true);
44
- });
45
- });
46
-
47
- describe("formatDiagnostics", () => {
48
- it("should format diagnostics for display", () => {
49
- const diags = [
50
- {
51
- line: 1,
52
- column: 0,
53
- endLine: 1,
54
- endColumn: 10,
55
- severity: "warning" as const,
56
- message: "Unexpected var, use let or const instead",
57
- rule: "no-var",
58
- file: "test.ts",
59
- },
60
- ];
61
-
62
- const formatted = client.formatDiagnostics(diags);
63
- expect(formatted).toContain("ast-grep");
64
- expect(formatted).toContain("no-var");
65
- });
66
-
67
- it("should categorize by severity", () => {
68
- const diags = [
69
- {
70
- line: 1,
71
- column: 0,
72
- endLine: 1,
73
- endColumn: 10,
74
- severity: "warning" as const,
75
- message: "Warning",
76
- rule: "rule1",
77
- file: "test.ts",
78
- },
79
- {
80
- line: 2,
81
- column: 0,
82
- endLine: 2,
83
- endColumn: 10,
84
- severity: "error" as const,
85
- message: "Error",
86
- rule: "rule2",
87
- file: "test.ts",
88
- },
89
- ];
90
-
91
- const formatted = client.formatDiagnostics(diags);
92
- expect(formatted).toContain("warning(s)");
93
- expect(formatted).toContain("error(s)");
94
- });
95
-
96
- it("should show fixable indicator", () => {
97
- const diags = [
98
- {
99
- line: 1,
100
- column: 0,
101
- endLine: 1,
102
- endColumn: 10,
103
- severity: "warning" as const,
104
- message: "Use const",
105
- rule: "prefer-const",
106
- file: "test.ts",
107
- fix: "const",
108
- },
109
- ];
110
-
111
- const formatted = client.formatDiagnostics(diags);
112
- expect(formatted).toContain("fixable");
113
- });
114
- });
115
-
116
- describe("search", () => {
117
- it("should search for patterns", async () => {
118
- if (!client.isAvailable()) return;
119
-
120
- createTempFile(
121
- tmpDir,
122
- "test.ts",
123
- `
124
- function test() {
125
- console.log("hello");
126
- }
127
- `,
128
- );
129
-
130
- const result = await client.search("console.log($MSG)", "typescript", [
131
- tmpDir,
132
- ]);
133
-
134
- expect(result.matches.length).toBeGreaterThan(0);
135
- });
136
-
137
- it("should return empty matches for no match", async () => {
138
- if (!client.isAvailable()) return;
139
-
140
- createTempFile(
141
- tmpDir,
142
- "test.ts",
143
- `
144
- const x = 1;
145
- `,
146
- );
147
-
148
- const result = await client.search("console.log($MSG)", "typescript", [
149
- tmpDir,
150
- ]);
151
-
152
- expect(result.matches.length).toBe(0);
153
- });
154
- });
155
- });