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,141 +0,0 @@
1
- import * as fs from "node:fs";
2
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
3
- import { MetricsClient } from "./metrics-client.js";
4
- import { createTempFile, setupTestEnvironment } from "./test-utils.js";
5
- describe("MetricsClient", () => {
6
- let client;
7
- let tmpDir;
8
- let cleanup;
9
- beforeEach(() => {
10
- client = new MetricsClient();
11
- ({ tmpDir, cleanup } = setupTestEnvironment("pi-lens-metrics-test-"));
12
- });
13
- afterEach(() => {
14
- cleanup();
15
- });
16
- afterEach(() => {
17
- cleanup();
18
- });
19
- describe("calculateEntropy", () => {
20
- it("should return 0 for empty string", () => {
21
- expect(client.calculateEntropy("")).toBe(0);
22
- });
23
- it("should return 0 for single repeated character", () => {
24
- expect(client.calculateEntropy("aaaaaa")).toBe(0);
25
- });
26
- it("should return 1 for two equally likely characters", () => {
27
- expect(client.calculateEntropy("ababab")).toBe(1);
28
- });
29
- it("should return higher entropy for more diverse content", () => {
30
- const lowEntropy = "aaaaaaaaaaaaaaaaaaaaaaaaaa";
31
- const highEntropy = "abcdefghijklmnopqrstuvwxyz";
32
- expect(client.calculateEntropy(highEntropy)).toBeGreaterThan(client.calculateEntropy(lowEntropy));
33
- });
34
- it("should match expected Shannon entropy for known input", () => {
35
- // "aabb" has p(a)=0.5, p(b)=0.5, entropy = -2*(0.5*log2(0.5)) = 1
36
- expect(client.calculateEntropy("aabb")).toBeCloseTo(1, 5);
37
- });
38
- });
39
- describe("recordBaseline", () => {
40
- it("should record baseline for existing file", () => {
41
- const content = "const x = 1;\nconst y = 2;";
42
- const filePath = createTempFile(tmpDir, "test.ts", content);
43
- client.recordBaseline(filePath);
44
- const metrics = client.getFileMetrics(filePath);
45
- expect(metrics).not.toBeNull();
46
- expect(metrics?.totalLines).toBeGreaterThanOrEqual(2);
47
- });
48
- it("should not record baseline for non-existent file", () => {
49
- client.recordBaseline("/nonexistent/file.ts");
50
- // Should not throw, just silently skip
51
- });
52
- it("should not overwrite existing baseline", () => {
53
- const content1 = "const x = 1;\n";
54
- const content2 = "const x = 1;\nconst y = 2;\nconst z = 3;\n";
55
- const filePath = createTempFile(tmpDir, "test.ts", content1);
56
- client.recordBaseline(filePath);
57
- // Modify file
58
- fs.writeFileSync(filePath, content2);
59
- // Record again - should not update baseline
60
- client.recordBaseline(filePath);
61
- const metrics = client.getFileMetrics(filePath);
62
- expect(metrics?.entropyStart).toBe(client.calculateEntropy(content1));
63
- });
64
- });
65
- describe("recordWrite", () => {
66
- it("should track agent-written lines", () => {
67
- const original = "const x = 1;\n";
68
- const filePath = createTempFile(tmpDir, "test.ts", original);
69
- client.recordBaseline(filePath);
70
- const modified = "const x = 1;\nconst y = 2;\nconst z = 3;\n";
71
- fs.writeFileSync(filePath, modified);
72
- client.recordWrite(filePath, modified);
73
- const aiRatio = client.getAICodeRatio();
74
- expect(aiRatio.agentLines).toBeGreaterThan(0);
75
- });
76
- it("should calculate AI code ratio", () => {
77
- const file1 = createTempFile(tmpDir, "file1.ts", "original content line 1\noriginal content line 2\n");
78
- const file2 = createTempFile(tmpDir, "file2.ts", "original\n");
79
- client.recordBaseline(file1);
80
- client.recordBaseline(file2);
81
- // Simulate agent writing new content
82
- const newContent1 = "original content line 1\noriginal content line 2\nagent line 3\nagent line 4\n";
83
- fs.writeFileSync(file1, newContent1);
84
- client.recordWrite(file1, newContent1);
85
- const aiRatio = client.getAICodeRatio();
86
- expect(aiRatio.fileCount).toBe(2);
87
- expect(aiRatio.ratio).toBeGreaterThanOrEqual(0);
88
- expect(aiRatio.ratio).toBeLessThanOrEqual(1);
89
- });
90
- });
91
- describe("getEntropyDeltas", () => {
92
- it("should track entropy changes", () => {
93
- const simple = "const x = 1;\n";
94
- const filePath = createTempFile(tmpDir, "test.ts", simple);
95
- client.recordBaseline(filePath);
96
- // Make file more complex
97
- const complex = `
98
- function complex(a: number, b: number, c: number): number {
99
- if (a > 0) {
100
- if (b > 0) {
101
- if (c > 0) {
102
- return a + b + c;
103
- }
104
- }
105
- }
106
- return 0;
107
- }
108
- `;
109
- fs.writeFileSync(filePath, complex);
110
- client.recordWrite(filePath, complex);
111
- const deltas = client.getEntropyDeltas();
112
- expect(deltas.length).toBe(1);
113
- expect(deltas[0].delta).not.toBe(0);
114
- });
115
- });
116
- describe("formatSessionSummary", () => {
117
- it("should return empty string when no files touched", () => {
118
- expect(client.formatSessionSummary()).toBe("");
119
- });
120
- it("should format AI code ratio when files are modified", () => {
121
- const filePath = createTempFile(tmpDir, "test.ts", "original\n");
122
- client.recordBaseline(filePath);
123
- const modified = "original\nnew line 1\nnew line 2\n";
124
- fs.writeFileSync(filePath, modified);
125
- client.recordWrite(filePath, modified);
126
- const summary = client.formatSessionSummary();
127
- expect(summary).toContain("AI Code");
128
- expect(summary).toContain("file(s)");
129
- });
130
- });
131
- describe("reset", () => {
132
- it("should clear all tracked data", () => {
133
- const filePath = createTempFile(tmpDir, "test.ts", "content\n");
134
- client.recordBaseline(filePath);
135
- client.reset();
136
- const aiRatio = client.getAICodeRatio();
137
- expect(aiRatio.fileCount).toBe(0);
138
- expect(client.formatSessionSummary()).toBe("");
139
- });
140
- });
141
- });
@@ -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,108 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
4
- import { RustClient } from "./rust-client.js";
5
- import { setupTestEnvironment } from "./test-utils.js";
6
- describe("RustClient", () => {
7
- let client;
8
- let tmpDir;
9
- let cleanup;
10
- beforeEach(() => {
11
- client = new RustClient();
12
- ({ tmpDir, cleanup } = setupTestEnvironment("pi-lens-rust-test-"));
13
- });
14
- afterEach(() => {
15
- cleanup();
16
- });
17
- afterEach(() => {
18
- cleanup();
19
- });
20
- describe("isRustFile", () => {
21
- it("should recognize Rust files", () => {
22
- expect(client.isRustFile("main.rs")).toBe(true);
23
- expect(client.isRustFile("lib.rs")).toBe(true);
24
- });
25
- it("should not recognize non-Rust files", () => {
26
- expect(client.isRustFile("main.ts")).toBe(false);
27
- expect(client.isRustFile("main.py")).toBe(false);
28
- });
29
- });
30
- describe("isAvailable", () => {
31
- it("should check cargo availability", () => {
32
- const available = client.isAvailable();
33
- expect(typeof available).toBe("boolean");
34
- });
35
- });
36
- describe("checkFile", () => {
37
- it("should return empty array for non-existent files", () => {
38
- if (!client.isAvailable())
39
- return;
40
- const result = client.checkFile("/nonexistent/file.rs", tmpDir);
41
- expect(result).toEqual([]);
42
- });
43
- it("should return array for valid Rust files", () => {
44
- if (!client.isAvailable())
45
- return;
46
- // Need a Cargo.toml for cargo to work
47
- fs.writeFileSync(path.join(tmpDir, "Cargo.toml"), `
48
- [package]
49
- name = "test"
50
- version = "0.1.0"
51
- edition = "2021"
52
- `);
53
- fs.mkdirSync(path.join(tmpDir, "src"), { recursive: true });
54
- fs.writeFileSync(path.join(tmpDir, "src", "main.rs"), `
55
- fn main() {
56
- println!("Hello, world!");
57
- }
58
- `);
59
- const result = client.checkFile(path.join(tmpDir, "src", "main.rs"), tmpDir);
60
- expect(Array.isArray(result)).toBe(true);
61
- });
62
- });
63
- describe("formatDiagnostics", () => {
64
- it("should format diagnostics for display", () => {
65
- const diags = [
66
- {
67
- line: 3,
68
- column: 0,
69
- endLine: 3,
70
- endColumn: 10,
71
- severity: "error",
72
- message: "cannot find value `x` in this scope",
73
- code: "E0425",
74
- file: "main.rs",
75
- },
76
- ];
77
- const formatted = client.formatDiagnostics(diags);
78
- expect(formatted).toContain("Rust");
79
- expect(formatted).toContain("1 issue");
80
- expect(formatted).toContain("E0425");
81
- });
82
- it("should show error and warning counts", () => {
83
- const diags = [
84
- {
85
- line: 1,
86
- column: 0,
87
- endLine: 1,
88
- endColumn: 10,
89
- severity: "error",
90
- message: "Error",
91
- file: "test.rs",
92
- },
93
- {
94
- line: 2,
95
- column: 0,
96
- endLine: 2,
97
- endColumn: 10,
98
- severity: "warning",
99
- message: "Warning",
100
- file: "test.rs",
101
- },
102
- ];
103
- const formatted = client.formatDiagnostics(diags);
104
- expect(formatted).toContain("1 error(s)");
105
- expect(formatted).toContain("1 warning(s)");
106
- });
107
- });
108
- });
@@ -1,177 +0,0 @@
1
- /**
2
- * Tests for sanitize.ts
3
- * Tool output sanitization
4
- */
5
- import { describe, expect, it } from "vitest";
6
- import { extractErrorMessage, normalizeWhitespace, sanitizeBiomeOutput, sanitizeLine, sanitizeOutput, sanitizeRuffOutput, sanitizeToolOutput, stripAnsi, truncateMessage, } from "./sanitize.js";
7
- describe("stripAnsi", () => {
8
- it("should remove ANSI color codes", () => {
9
- expect(stripAnsi("\x1b[31mError\x1b[0m")).toBe("Error");
10
- expect(stripAnsi("\x1b[1;32mSuccess\x1b[0m")).toBe("Success");
11
- });
12
- it("should remove extended ANSI codes", () => {
13
- expect(stripAnsi("\x1b[1;2;3mText\x1b[0m")).toBe("Text");
14
- });
15
- it("should return original string if no ANSI codes", () => {
16
- expect(stripAnsi("Plain text")).toBe("Plain text");
17
- });
18
- });
19
- describe("normalizeWhitespace", () => {
20
- it("should collapse multiple spaces", () => {
21
- expect(normalizeWhitespace("Hello World")).toBe("Hello World");
22
- });
23
- it("should trim lines", () => {
24
- expect(normalizeWhitespace(" Hello \n World ")).toBe("Hello\nWorld");
25
- });
26
- it("should remove empty lines", () => {
27
- expect(normalizeWhitespace("Hello\n\n\nWorld")).toBe("Hello\nWorld");
28
- });
29
- });
30
- describe("sanitizeLine", () => {
31
- it("should remove common error prefixes", () => {
32
- expect(sanitizeLine("[error] Something went wrong")).toBe("Something went wrong");
33
- expect(sanitizeLine("error: Something went wrong")).toBe("Something went wrong");
34
- });
35
- it("should remove check/cross marks", () => {
36
- expect(sanitizeLine("× Error message")).toBe("Error message");
37
- expect(sanitizeLine("✓ Success message")).toBe("Success message");
38
- });
39
- it("should normalize whitespace", () => {
40
- expect(sanitizeLine(" Error message ")).toBe("Error message");
41
- });
42
- });
43
- describe("sanitizeOutput", () => {
44
- it("should return empty string for empty input", () => {
45
- expect(sanitizeOutput("")).toBe("");
46
- expect(sanitizeOutput(" ")).toBe("");
47
- });
48
- it("should filter out detail lines", () => {
49
- const output = "Error message\n at line 10\n at function foo";
50
- const result = sanitizeOutput(output);
51
- expect(result).toBe("Error message");
52
- });
53
- it("should remove ANSI codes and normalize", () => {
54
- const output = "\x1b[31mError\x1b[0m\n\x1b[32mSuccess\x1b[0m";
55
- const result = sanitizeOutput(output);
56
- expect(result).toBe("Error\nSuccess");
57
- });
58
- });
59
- describe("extractErrorMessage", () => {
60
- it("should return undefined for empty input", () => {
61
- expect(extractErrorMessage("")).toBeUndefined();
62
- expect(extractErrorMessage(" ")).toBeUndefined();
63
- });
64
- it("should return first error line containing error indicator", () => {
65
- // Note: extractErrorMessage sanitizes lines first, then checks isErrorLine
66
- // Since "error:" prefix is removed by sanitizeLine, we need to use a keyword
67
- // that remains in the sanitized line, like "failed" or "syntax"
68
- const output = "Some info\nOperation failed: disk full\nMore info";
69
- const result = extractErrorMessage(output);
70
- expect(result).toBe("Operation failed: disk full");
71
- });
72
- it("should fall back to first non-empty line", () => {
73
- const output = "info line\nanother info\nand more";
74
- expect(extractErrorMessage(output)).toBe("info line");
75
- });
76
- it("should find error by keyword", () => {
77
- const result = extractErrorMessage("warning: low severity\nfailed: operation");
78
- // extractErrorMessage returns the first line with error indicators
79
- // "failed:" contains "failed" which matches ERROR_INDICATORS
80
- expect(result).toBe("failed: operation");
81
- });
82
- });
83
- describe("truncateMessage", () => {
84
- it("should not truncate short messages", () => {
85
- const msg = "Short message";
86
- expect(truncateMessage(msg, 100)).toBe(msg);
87
- });
88
- it("should truncate long messages", () => {
89
- const msg = "A".repeat(200);
90
- const result = truncateMessage(msg, 50);
91
- expect(result).toBe(`${"A".repeat(49)}…`);
92
- });
93
- it("should use default max length of 140", () => {
94
- const msg = "A".repeat(200);
95
- const result = truncateMessage(msg);
96
- expect(result.length).toBe(140);
97
- });
98
- });
99
- describe("sanitizeToolOutput", () => {
100
- it("should return empty result for empty input", () => {
101
- const result = sanitizeToolOutput("");
102
- expect(result.summary).toBeUndefined();
103
- expect(result.details).toBeUndefined();
104
- expect(result.truncated).toBe(false);
105
- });
106
- it("should extract summary and details", () => {
107
- const output = "error: something failed\nline 2\nline 3\nline 4";
108
- const result = sanitizeToolOutput(output);
109
- // sanitizeLine removes "error:" prefix
110
- expect(result.summary).toContain("something failed");
111
- expect(result.details).toBeDefined();
112
- });
113
- it("should mark as truncated when too many lines", () => {
114
- const lines = [];
115
- for (let i = 0; i < 30; i++) {
116
- lines.push(`error line ${i}`);
117
- }
118
- const result = sanitizeToolOutput(lines.join("\n"));
119
- expect(result.truncated).toBe(true);
120
- expect(result.details).toContain("more lines");
121
- });
122
- it("should respect max summary length", () => {
123
- const output = `error: ${"x".repeat(200)}`;
124
- const result = sanitizeToolOutput(output, 50);
125
- expect(result.summary?.length).toBeLessThanOrEqual(50);
126
- });
127
- });
128
- describe("sanitizeBiomeOutput", () => {
129
- it("should parse JSON diagnostics", () => {
130
- const json = JSON.stringify({
131
- diagnostics: [
132
- {
133
- location: {
134
- path: "file.ts",
135
- span: { start: { line: 10, column: 0 } },
136
- },
137
- message: "Unexpected token",
138
- },
139
- ],
140
- });
141
- const result = sanitizeBiomeOutput(json);
142
- expect(result).toContain("file.ts");
143
- expect(result).toContain("11"); // line + 1
144
- expect(result).toContain("Unexpected token");
145
- });
146
- it("should handle text output with errors", () => {
147
- const output = "error: something went wrong\ninfo: some info";
148
- const result = sanitizeBiomeOutput(output);
149
- expect(result).toContain("error");
150
- });
151
- it("should return empty for empty input", () => {
152
- expect(sanitizeBiomeOutput("")).toBe("");
153
- });
154
- });
155
- describe("sanitizeRuffOutput", () => {
156
- it("should parse JSON diagnostics", () => {
157
- const json = JSON.stringify([
158
- {
159
- location: { row: 10, column: 5 },
160
- code: "E501",
161
- message: "Line too long",
162
- },
163
- ]);
164
- const result = sanitizeRuffOutput(json);
165
- expect(result).toContain("10:5");
166
- expect(result).toContain("[E501]");
167
- expect(result).toContain("Line too long");
168
- });
169
- it("should handle text output", () => {
170
- const output = "file.py:10:5 [E501] Line too long";
171
- const result = sanitizeRuffOutput(output);
172
- expect(result).toContain("E501");
173
- });
174
- it("should return empty for empty input", () => {
175
- expect(sanitizeRuffOutput("")).toBe("");
176
- });
177
- });
@@ -1,100 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { formatSecrets, scanForSecrets } from "./secrets-scanner.js";
3
- describe("scanForSecrets", () => {
4
- it("should detect Stripe/OpenAI keys (sk-*)", () => {
5
- const content = `const apiKey = "sk-live-1234567890abcdefghij";`;
6
- const findings = scanForSecrets(content);
7
- expect(findings.length).toBe(1);
8
- expect(findings[0].message).toContain("Stripe or OpenAI");
9
- });
10
- it("should detect GitHub personal tokens (ghp_*)", () => {
11
- const content = `token = "ghp_1234567890abcdefghijklmnopqrstuvwxyz";`;
12
- const findings = scanForSecrets(content);
13
- expect(findings.length).toBe(1);
14
- expect(findings[0].message).toContain("GitHub personal");
15
- });
16
- it("should detect AWS access keys (AKIA*)", () => {
17
- const content = `const AWS_KEY = "AKIAIOSFODNN7EXAMPLE";`;
18
- const findings = scanForSecrets(content);
19
- expect(findings.length).toBe(1);
20
- expect(findings[0].message).toContain("AWS access key");
21
- });
22
- it("should detect private key material", () => {
23
- const content = `-----BEGIN RSA PRIVATE KEY-----
24
- MIIEpAIBAAKCAQEA...`;
25
- const findings = scanForSecrets(content);
26
- expect(findings.length).toBe(1);
27
- expect(findings[0].message).toContain("Private key");
28
- });
29
- it("should detect hardcoded passwords", () => {
30
- const content = `const config = { password: "hunter2" };`;
31
- const findings = scanForSecrets(content);
32
- expect(findings.length).toBe(1);
33
- expect(findings[0].message).toContain("password");
34
- });
35
- it("should detect secrets in .env format", () => {
36
- const content = `API_KEY=sk-live-1234567890abcdefghij
37
- DATABASE_URL=postgres://localhost`;
38
- const findings = scanForSecrets(content);
39
- expect(findings.length).toBe(1);
40
- // sk-* pattern catches this first (more specific)
41
- expect(findings[0].message).toContain("Stripe or OpenAI");
42
- });
43
- it("should NOT flag safe content", () => {
44
- const content = `
45
- const name = "test";
46
- const url = "https://example.com";
47
- const port = 3000;
48
- const message = "Hello world";
49
- `;
50
- const findings = scanForSecrets(content);
51
- expect(findings.length).toBe(0);
52
- });
53
- it("should NOT flag env var references", () => {
54
- const content = `const key = process.env.API_KEY;`;
55
- const findings = scanForSecrets(content);
56
- expect(findings.length).toBe(0);
57
- });
58
- it("should detect multiple secrets", () => {
59
- const content = `
60
- const sk = "sk-live-1234567890abcdefghij";
61
- const gh = "ghp_1234567890abcdefghijklmnopqrstuvwxyz";
62
- `;
63
- const findings = scanForSecrets(content);
64
- expect(findings.length).toBe(2);
65
- });
66
- it("should report correct line numbers", () => {
67
- const content = `line 1
68
- line 2
69
- const secret = "sk-live-1234567890abcdefghij";
70
- line 4`;
71
- const findings = scanForSecrets(content);
72
- expect(findings.length).toBe(1);
73
- expect(findings[0].line).toBe(3);
74
- });
75
- });
76
- describe("formatSecrets", () => {
77
- it("should format findings for terminal output", () => {
78
- const findings = [
79
- { line: 5, message: "Possible Stripe or OpenAI API key (sk-*)" },
80
- ];
81
- const output = formatSecrets(findings, "src/config.ts");
82
- expect(output).toContain("STOP");
83
- expect(output).toContain("1 potential secret(s)");
84
- expect(output).toContain("L5");
85
- expect(output).toContain("src/config.ts");
86
- });
87
- it("should return empty string for no findings", () => {
88
- const output = formatSecrets([], "src/config.ts");
89
- expect(output).toBe("");
90
- });
91
- it("should truncate at 5 findings", () => {
92
- const findings = Array.from({ length: 10 }, (_, i) => ({
93
- line: i + 1,
94
- message: "Test secret",
95
- }));
96
- const output = formatSecrets(findings, "src/config.ts");
97
- expect(output).toContain("10 potential secret(s)");
98
- expect(output).toContain("... and 5 more");
99
- });
100
- });