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.
- package/CHANGELOG.md +10 -2
- package/package.json +4 -4
- package/tsconfig.json +1 -1
- package/clients/__tests__/file-time.test.js +0 -216
- package/clients/__tests__/file-time.test.ts +0 -276
- package/clients/__tests__/format-service.test.js +0 -245
- package/clients/__tests__/format-service.test.ts +0 -339
- package/clients/__tests__/formatters.test.js +0 -271
- package/clients/__tests__/formatters.test.ts +0 -401
- package/clients/agent-behavior-client.js +0 -110
- package/clients/agent-behavior-client.test.js +0 -94
- package/clients/agent-behavior-client.test.ts +0 -116
- package/clients/amain-types.js +0 -164
- package/clients/architect-client.js +0 -291
- package/clients/ast-grep-client.js +0 -253
- package/clients/ast-grep-parser.js +0 -84
- package/clients/ast-grep-rule-manager.js +0 -89
- package/clients/ast-grep-types.js +0 -9
- package/clients/auto-loop.js +0 -131
- package/clients/biome-client.js +0 -420
- package/clients/biome-client.test.js +0 -144
- package/clients/biome-client.test.ts +0 -163
- package/clients/cache/rule-cache.js +0 -72
- package/clients/cache-manager.js +0 -245
- package/clients/cache-manager.test.js +0 -197
- package/clients/cache-manager.test.ts +0 -299
- package/clients/complexity-client.js +0 -675
- package/clients/complexity-client.test.js +0 -234
- package/clients/complexity-client.test.ts +0 -255
- package/clients/config-validator.js +0 -465
- package/clients/dependency-checker.js +0 -325
- package/clients/dependency-checker.test.js +0 -60
- package/clients/dependency-checker.test.ts +0 -71
- package/clients/dispatch/__tests__/autofix-integration.test.js +0 -245
- package/clients/dispatch/__tests__/autofix-integration.test.ts +0 -300
- package/clients/dispatch/__tests__/runner-registration.test.js +0 -234
- package/clients/dispatch/__tests__/runner-registration.test.ts +0 -286
- package/clients/dispatch/debug.log +0 -1
- package/clients/dispatch/dispatcher.edge.test.js +0 -82
- package/clients/dispatch/dispatcher.edge.test.ts +0 -100
- package/clients/dispatch/dispatcher.format.test.js +0 -46
- package/clients/dispatch/dispatcher.format.test.ts +0 -58
- package/clients/dispatch/dispatcher.inline.test.js +0 -74
- package/clients/dispatch/dispatcher.inline.test.ts +0 -93
- package/clients/dispatch/dispatcher.js +0 -381
- package/clients/dispatch/dispatcher.test.js +0 -116
- package/clients/dispatch/dispatcher.test.ts +0 -149
- package/clients/dispatch/integration.js +0 -108
- package/clients/dispatch/plan.js +0 -183
- package/clients/dispatch/runners/architect.js +0 -83
- package/clients/dispatch/runners/architect.test.js +0 -138
- package/clients/dispatch/runners/architect.test.ts +0 -162
- package/clients/dispatch/runners/ast-grep-napi.js +0 -405
- package/clients/dispatch/runners/ast-grep-napi.test.js +0 -107
- package/clients/dispatch/runners/ast-grep-napi.test.ts +0 -129
- package/clients/dispatch/runners/ast-grep.js +0 -157
- package/clients/dispatch/runners/biome.js +0 -55
- package/clients/dispatch/runners/config-validation.js +0 -67
- package/clients/dispatch/runners/go-vet.js +0 -48
- package/clients/dispatch/runners/index.js +0 -47
- package/clients/dispatch/runners/lsp.js +0 -102
- package/clients/dispatch/runners/oxlint.js +0 -67
- package/clients/dispatch/runners/oxlint.test.js +0 -230
- package/clients/dispatch/runners/oxlint.test.ts +0 -303
- package/clients/dispatch/runners/pyright.js +0 -100
- package/clients/dispatch/runners/pyright.test.js +0 -98
- package/clients/dispatch/runners/pyright.test.ts +0 -121
- package/clients/dispatch/runners/python-slop.js +0 -97
- package/clients/dispatch/runners/python-slop.test.js +0 -203
- package/clients/dispatch/runners/python-slop.test.ts +0 -298
- package/clients/dispatch/runners/ruff.js +0 -48
- package/clients/dispatch/runners/rust-clippy.js +0 -102
- package/clients/dispatch/runners/scan_codebase.test.js +0 -89
- package/clients/dispatch/runners/scan_codebase.test.ts +0 -105
- package/clients/dispatch/runners/shellcheck.js +0 -147
- package/clients/dispatch/runners/shellcheck.test.js +0 -98
- package/clients/dispatch/runners/shellcheck.test.ts +0 -129
- package/clients/dispatch/runners/similarity.js +0 -230
- package/clients/dispatch/runners/spellcheck.js +0 -106
- package/clients/dispatch/runners/spellcheck.test.js +0 -158
- package/clients/dispatch/runners/spellcheck.test.ts +0 -214
- package/clients/dispatch/runners/tree-sitter.js +0 -246
- package/clients/dispatch/runners/ts-lsp.js +0 -125
- package/clients/dispatch/runners/ts-slop.js +0 -113
- package/clients/dispatch/runners/type-safety.js +0 -142
- package/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -134
- package/clients/dispatch/runners/utils/runner-helpers.js +0 -115
- package/clients/dispatch/runners/utils.js +0 -51
- package/clients/dispatch/runners/yaml-rule-parser.js +0 -360
- package/clients/dispatch/types.js +0 -16
- package/clients/dispatch/utils/format-utils.js +0 -44
- package/clients/dogfood.test.js +0 -201
- package/clients/dogfood.test.ts +0 -269
- package/clients/file-kinds.js +0 -177
- package/clients/file-kinds.test.js +0 -169
- package/clients/file-kinds.test.ts +0 -210
- package/clients/file-time.js +0 -152
- package/clients/file-utils.js +0 -40
- package/clients/fix-scanners.js +0 -204
- package/clients/format-service.js +0 -184
- package/clients/formatters.js +0 -488
- package/clients/go-client.js +0 -203
- package/clients/go-client.test.js +0 -127
- package/clients/go-client.test.ts +0 -143
- package/clients/installer/index.js +0 -403
- package/clients/interviewer-templates.js +0 -75
- package/clients/interviewer.js +0 -173
- package/clients/jscpd-client.js +0 -196
- package/clients/jscpd-client.test.js +0 -127
- package/clients/jscpd-client.test.ts +0 -145
- package/clients/knip-client.js +0 -239
- package/clients/knip-client.test.js +0 -112
- package/clients/knip-client.test.ts +0 -128
- package/clients/latency-logger.js +0 -40
- package/clients/lsp/__tests__/client.test.js +0 -310
- package/clients/lsp/__tests__/client.test.ts +0 -412
- package/clients/lsp/__tests__/config.test.js +0 -167
- package/clients/lsp/__tests__/config.test.ts +0 -217
- package/clients/lsp/__tests__/error-recovery.test.js +0 -213
- package/clients/lsp/__tests__/error-recovery.test.ts +0 -279
- package/clients/lsp/__tests__/integration.test.js +0 -127
- package/clients/lsp/__tests__/integration.test.ts +0 -160
- package/clients/lsp/__tests__/launch.test.js +0 -313
- package/clients/lsp/__tests__/launch.test.ts +0 -394
- package/clients/lsp/__tests__/server.test.js +0 -259
- package/clients/lsp/__tests__/server.test.ts +0 -332
- package/clients/lsp/__tests__/service.test.js +0 -438
- package/clients/lsp/__tests__/service.test.ts +0 -530
- package/clients/lsp/client.js +0 -350
- package/clients/lsp/config.js +0 -112
- package/clients/lsp/index.js +0 -318
- package/clients/lsp/installer/index.js +0 -391
- package/clients/lsp/interactive-install.js +0 -221
- package/clients/lsp/language.js +0 -170
- package/clients/lsp/launch.js +0 -329
- package/clients/lsp/lsp/launch.js +0 -116
- package/clients/lsp/lsp/server.js +0 -532
- package/clients/lsp/lsp-index.js +0 -10
- package/clients/lsp/path-utils.js +0 -5
- package/clients/lsp/server.js +0 -725
- package/clients/lsp/test-py-spawn/requirements.txt +0 -1
- package/clients/lsp/test-py-spawn/test.py +0 -3
- package/clients/lsp/test-py-svc/requirements.txt +0 -1
- package/clients/lsp/test-py-svc/test.py +0 -3
- package/clients/lsp/test-python-project/requirements.txt +0 -1
- package/clients/lsp/test-python-project/test.py +0 -5
- package/clients/metrics-client.js +0 -107
- package/clients/metrics-client.test.js +0 -128
- package/clients/metrics-client.test.ts +0 -163
- package/clients/metrics-history.js +0 -367
- package/clients/path-utils.js +0 -142
- package/clients/pipeline.js +0 -272
- package/clients/production-readiness.js +0 -522
- package/clients/project-index.js +0 -255
- package/clients/project-metadata.js +0 -531
- package/clients/ruff-client.js +0 -325
- package/clients/ruff-client.test.js +0 -132
- package/clients/ruff-client.test.ts +0 -153
- package/clients/rules-scanner.js +0 -97
- package/clients/runner-tracker.js +0 -152
- package/clients/rust-client.js +0 -205
- package/clients/rust-client.test.js +0 -108
- package/clients/rust-client.test.ts +0 -130
- package/clients/safe-spawn-async.js +0 -163
- package/clients/safe-spawn.js +0 -241
- package/clients/sanitize.js +0 -291
- package/clients/sanitize.test.js +0 -177
- package/clients/sanitize.test.ts +0 -223
- package/clients/scan-architectural-debt.js +0 -167
- package/clients/scan-utils.js +0 -83
- package/clients/secrets-scanner.js +0 -119
- package/clients/secrets-scanner.test.js +0 -100
- package/clients/secrets-scanner.test.ts +0 -113
- package/clients/sg-runner.js +0 -292
- package/clients/state-matrix.js +0 -160
- package/clients/subprocess-client.js +0 -65
- package/clients/symbol-types.js +0 -5
- package/clients/test-runner-client.js +0 -523
- package/clients/test-runner-client.test.js +0 -192
- package/clients/test-runner-client.test.ts +0 -253
- package/clients/test-utils.js +0 -27
- package/clients/test-utils.ts +0 -36
- package/clients/todo-scanner.js +0 -200
- package/clients/todo-scanner.test.js +0 -301
- package/clients/todo-scanner.test.ts +0 -352
- package/clients/tool-availability.js +0 -207
- package/clients/tree-sitter-client.js +0 -601
- package/clients/tree-sitter-query-loader.js +0 -355
- package/clients/tree-sitter-symbol-extractor.js +0 -289
- package/clients/ts-service.js +0 -129
- package/clients/type-coverage-client.js +0 -127
- package/clients/type-coverage-client.test.js +0 -105
- package/clients/type-coverage-client.test.ts +0 -125
- package/clients/type-safety-client.js +0 -138
- package/clients/types.js +0 -11
- package/clients/typescript-client.codefix.test.js +0 -157
- package/clients/typescript-client.codefix.test.ts +0 -186
- package/clients/typescript-client.js +0 -509
- package/clients/typescript-client.test.js +0 -105
- package/clients/typescript-client.test.ts +0 -126
- package/commands/booboo.js +0 -1007
- package/commands/fix-from-booboo.js +0 -398
- package/commands/fix-simplified.js +0 -618
- package/commands/rate.js +0 -281
- package/commands/rate.test.js +0 -119
- package/commands/rate.test.ts +0 -131
- package/commands/refactor.js +0 -130
package/clients/sanitize.test.js
DELETED
|
@@ -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
|
-
});
|
package/clients/sanitize.test.ts
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for sanitize.ts
|
|
3
|
-
* Tool output sanitization
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { describe, expect, it } from "vitest";
|
|
7
|
-
import {
|
|
8
|
-
extractErrorMessage,
|
|
9
|
-
normalizeWhitespace,
|
|
10
|
-
sanitizeBiomeOutput,
|
|
11
|
-
sanitizeLine,
|
|
12
|
-
sanitizeOutput,
|
|
13
|
-
sanitizeRuffOutput,
|
|
14
|
-
sanitizeToolOutput,
|
|
15
|
-
stripAnsi,
|
|
16
|
-
truncateMessage,
|
|
17
|
-
} from "./sanitize.js";
|
|
18
|
-
|
|
19
|
-
describe("stripAnsi", () => {
|
|
20
|
-
it("should remove ANSI color codes", () => {
|
|
21
|
-
expect(stripAnsi("\x1b[31mError\x1b[0m")).toBe("Error");
|
|
22
|
-
expect(stripAnsi("\x1b[1;32mSuccess\x1b[0m")).toBe("Success");
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("should remove extended ANSI codes", () => {
|
|
26
|
-
expect(stripAnsi("\x1b[1;2;3mText\x1b[0m")).toBe("Text");
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("should return original string if no ANSI codes", () => {
|
|
30
|
-
expect(stripAnsi("Plain text")).toBe("Plain text");
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe("normalizeWhitespace", () => {
|
|
35
|
-
it("should collapse multiple spaces", () => {
|
|
36
|
-
expect(normalizeWhitespace("Hello World")).toBe("Hello World");
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("should trim lines", () => {
|
|
40
|
-
expect(normalizeWhitespace(" Hello \n World ")).toBe("Hello\nWorld");
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("should remove empty lines", () => {
|
|
44
|
-
expect(normalizeWhitespace("Hello\n\n\nWorld")).toBe("Hello\nWorld");
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
describe("sanitizeLine", () => {
|
|
49
|
-
it("should remove common error prefixes", () => {
|
|
50
|
-
expect(sanitizeLine("[error] Something went wrong")).toBe(
|
|
51
|
-
"Something went wrong",
|
|
52
|
-
);
|
|
53
|
-
expect(sanitizeLine("error: Something went wrong")).toBe(
|
|
54
|
-
"Something went wrong",
|
|
55
|
-
);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("should remove check/cross marks", () => {
|
|
59
|
-
expect(sanitizeLine("× Error message")).toBe("Error message");
|
|
60
|
-
expect(sanitizeLine("✓ Success message")).toBe("Success message");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("should normalize whitespace", () => {
|
|
64
|
-
expect(sanitizeLine(" Error message ")).toBe("Error message");
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
describe("sanitizeOutput", () => {
|
|
69
|
-
it("should return empty string for empty input", () => {
|
|
70
|
-
expect(sanitizeOutput("")).toBe("");
|
|
71
|
-
expect(sanitizeOutput(" ")).toBe("");
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("should filter out detail lines", () => {
|
|
75
|
-
const output = "Error message\n at line 10\n at function foo";
|
|
76
|
-
const result = sanitizeOutput(output);
|
|
77
|
-
expect(result).toBe("Error message");
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("should remove ANSI codes and normalize", () => {
|
|
81
|
-
const output = "\x1b[31mError\x1b[0m\n\x1b[32mSuccess\x1b[0m";
|
|
82
|
-
const result = sanitizeOutput(output);
|
|
83
|
-
expect(result).toBe("Error\nSuccess");
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
describe("extractErrorMessage", () => {
|
|
88
|
-
it("should return undefined for empty input", () => {
|
|
89
|
-
expect(extractErrorMessage("")).toBeUndefined();
|
|
90
|
-
expect(extractErrorMessage(" ")).toBeUndefined();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("should return first error line containing error indicator", () => {
|
|
94
|
-
// Note: extractErrorMessage sanitizes lines first, then checks isErrorLine
|
|
95
|
-
// Since "error:" prefix is removed by sanitizeLine, we need to use a keyword
|
|
96
|
-
// that remains in the sanitized line, like "failed" or "syntax"
|
|
97
|
-
const output = "Some info\nOperation failed: disk full\nMore info";
|
|
98
|
-
const result = extractErrorMessage(output);
|
|
99
|
-
expect(result).toBe("Operation failed: disk full");
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("should fall back to first non-empty line", () => {
|
|
103
|
-
const output = "info line\nanother info\nand more";
|
|
104
|
-
expect(extractErrorMessage(output)).toBe("info line");
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("should find error by keyword", () => {
|
|
108
|
-
const result = extractErrorMessage(
|
|
109
|
-
"warning: low severity\nfailed: operation",
|
|
110
|
-
);
|
|
111
|
-
// extractErrorMessage returns the first line with error indicators
|
|
112
|
-
// "failed:" contains "failed" which matches ERROR_INDICATORS
|
|
113
|
-
expect(result).toBe("failed: operation");
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
describe("truncateMessage", () => {
|
|
118
|
-
it("should not truncate short messages", () => {
|
|
119
|
-
const msg = "Short message";
|
|
120
|
-
expect(truncateMessage(msg, 100)).toBe(msg);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("should truncate long messages", () => {
|
|
124
|
-
const msg = "A".repeat(200);
|
|
125
|
-
const result = truncateMessage(msg, 50);
|
|
126
|
-
expect(result).toBe(`${"A".repeat(49)}…`);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it("should use default max length of 140", () => {
|
|
130
|
-
const msg = "A".repeat(200);
|
|
131
|
-
const result = truncateMessage(msg);
|
|
132
|
-
expect(result.length).toBe(140);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
describe("sanitizeToolOutput", () => {
|
|
137
|
-
it("should return empty result for empty input", () => {
|
|
138
|
-
const result = sanitizeToolOutput("");
|
|
139
|
-
expect(result.summary).toBeUndefined();
|
|
140
|
-
expect(result.details).toBeUndefined();
|
|
141
|
-
expect(result.truncated).toBe(false);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it("should extract summary and details", () => {
|
|
145
|
-
const output = "error: something failed\nline 2\nline 3\nline 4";
|
|
146
|
-
const result = sanitizeToolOutput(output);
|
|
147
|
-
// sanitizeLine removes "error:" prefix
|
|
148
|
-
expect(result.summary).toContain("something failed");
|
|
149
|
-
expect(result.details).toBeDefined();
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it("should mark as truncated when too many lines", () => {
|
|
153
|
-
const lines: string[] = [];
|
|
154
|
-
for (let i = 0; i < 30; i++) {
|
|
155
|
-
lines.push(`error line ${i}`);
|
|
156
|
-
}
|
|
157
|
-
const result = sanitizeToolOutput(lines.join("\n"));
|
|
158
|
-
expect(result.truncated).toBe(true);
|
|
159
|
-
expect(result.details).toContain("more lines");
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it("should respect max summary length", () => {
|
|
163
|
-
const output = `error: ${"x".repeat(200)}`;
|
|
164
|
-
const result = sanitizeToolOutput(output, 50);
|
|
165
|
-
expect(result.summary?.length).toBeLessThanOrEqual(50);
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
describe("sanitizeBiomeOutput", () => {
|
|
170
|
-
it("should parse JSON diagnostics", () => {
|
|
171
|
-
const json = JSON.stringify({
|
|
172
|
-
diagnostics: [
|
|
173
|
-
{
|
|
174
|
-
location: {
|
|
175
|
-
path: "file.ts",
|
|
176
|
-
span: { start: { line: 10, column: 0 } },
|
|
177
|
-
},
|
|
178
|
-
message: "Unexpected token",
|
|
179
|
-
},
|
|
180
|
-
],
|
|
181
|
-
});
|
|
182
|
-
const result = sanitizeBiomeOutput(json);
|
|
183
|
-
expect(result).toContain("file.ts");
|
|
184
|
-
expect(result).toContain("11"); // line + 1
|
|
185
|
-
expect(result).toContain("Unexpected token");
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it("should handle text output with errors", () => {
|
|
189
|
-
const output = "error: something went wrong\ninfo: some info";
|
|
190
|
-
const result = sanitizeBiomeOutput(output);
|
|
191
|
-
expect(result).toContain("error");
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it("should return empty for empty input", () => {
|
|
195
|
-
expect(sanitizeBiomeOutput("")).toBe("");
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
describe("sanitizeRuffOutput", () => {
|
|
200
|
-
it("should parse JSON diagnostics", () => {
|
|
201
|
-
const json = JSON.stringify([
|
|
202
|
-
{
|
|
203
|
-
location: { row: 10, column: 5 },
|
|
204
|
-
code: "E501",
|
|
205
|
-
message: "Line too long",
|
|
206
|
-
},
|
|
207
|
-
]);
|
|
208
|
-
const result = sanitizeRuffOutput(json);
|
|
209
|
-
expect(result).toContain("10:5");
|
|
210
|
-
expect(result).toContain("[E501]");
|
|
211
|
-
expect(result).toContain("Line too long");
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it("should handle text output", () => {
|
|
215
|
-
const output = "file.py:10:5 [E501] Line too long";
|
|
216
|
-
const result = sanitizeRuffOutput(output);
|
|
217
|
-
expect(result).toContain("E501");
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
it("should return empty for empty input", () => {
|
|
221
|
-
expect(sanitizeRuffOutput("")).toBe("");
|
|
222
|
-
});
|
|
223
|
-
});
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared architectural debt scanning.
|
|
3
|
-
* Scans ast-grep skip rules + complexity metrics + architect.yaml rules.
|
|
4
|
-
*/
|
|
5
|
-
import * as fs from "node:fs";
|
|
6
|
-
import * as path from "node:path";
|
|
7
|
-
import { safeSpawn } from "./safe-spawn.js";
|
|
8
|
-
import { getSourceFiles, parseAstGrepJson } from "./scan-utils.js";
|
|
9
|
-
/**
|
|
10
|
-
* Scan for skip-category ast-grep violations grouped by absolute file path.
|
|
11
|
-
*/
|
|
12
|
-
export function scanSkipViolations(astGrepClient, configPath, targetPath, isTsProject, skipRules, ruleActions) {
|
|
13
|
-
const skipByFile = new Map();
|
|
14
|
-
if (!astGrepClient.isAvailable())
|
|
15
|
-
return skipByFile;
|
|
16
|
-
const sgResult = safeSpawn("npx", [
|
|
17
|
-
"sg",
|
|
18
|
-
"scan",
|
|
19
|
-
"--config",
|
|
20
|
-
configPath,
|
|
21
|
-
"--json",
|
|
22
|
-
"--globs",
|
|
23
|
-
"!**/*.test.ts",
|
|
24
|
-
"--globs",
|
|
25
|
-
"!**/*.spec.ts",
|
|
26
|
-
"--globs",
|
|
27
|
-
"!**/test-utils.ts",
|
|
28
|
-
"--globs",
|
|
29
|
-
"!**/.pi-lens/**",
|
|
30
|
-
...(isTsProject ? ["--globs", "!**/*.js"] : []),
|
|
31
|
-
targetPath,
|
|
32
|
-
], {
|
|
33
|
-
timeout: 30000,
|
|
34
|
-
});
|
|
35
|
-
const items = parseAstGrepJson(sgResult.stdout?.trim() ?? "");
|
|
36
|
-
for (const item of items) {
|
|
37
|
-
const rule = item.ruleId || item.rule?.title || item.name || "unknown";
|
|
38
|
-
if (!skipRules.has(rule))
|
|
39
|
-
continue;
|
|
40
|
-
const line = (item.labels?.[0]?.range?.start?.line ?? item.range?.start?.line ?? 0) +
|
|
41
|
-
1;
|
|
42
|
-
const absFile = path.resolve(item.file ?? "");
|
|
43
|
-
const list = skipByFile.get(absFile) ?? [];
|
|
44
|
-
list.push({ rule, line, note: ruleActions[rule]?.note ?? "" });
|
|
45
|
-
skipByFile.set(absFile, list);
|
|
46
|
-
}
|
|
47
|
-
return skipByFile;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Scan complexity metrics for all supported files, grouped by absolute file path.
|
|
51
|
-
*/
|
|
52
|
-
export function scanComplexityMetrics(complexityClient, targetPath, isTsProject) {
|
|
53
|
-
const metricsByFile = new Map();
|
|
54
|
-
const files = getSourceFiles(targetPath, isTsProject);
|
|
55
|
-
for (const full of files) {
|
|
56
|
-
if (complexityClient.isSupportedFile(full) &&
|
|
57
|
-
!/\.(test|spec)\.[jt]sx?$/.test(path.basename(full))) {
|
|
58
|
-
const m = complexityClient.analyzeFile(full);
|
|
59
|
-
if (m)
|
|
60
|
-
metricsByFile.set(full, {
|
|
61
|
-
mi: m.maintainabilityIndex,
|
|
62
|
-
cognitive: m.cognitiveComplexity,
|
|
63
|
-
nesting: m.maxNestingDepth,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return metricsByFile;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Scan for architectural rule violations grouped by absolute file path.
|
|
71
|
-
* Returns map of absolute file path → list of violation messages.
|
|
72
|
-
*/
|
|
73
|
-
export function scanArchitectViolations(architectClient, targetPath) {
|
|
74
|
-
const violationsByFile = new Map();
|
|
75
|
-
if (!architectClient.hasConfig())
|
|
76
|
-
return violationsByFile;
|
|
77
|
-
const isTsProject = fs.existsSync(path.join(targetPath, "tsconfig.json"));
|
|
78
|
-
const files = getSourceFiles(targetPath, isTsProject);
|
|
79
|
-
for (const full of files) {
|
|
80
|
-
const relPath = path.relative(targetPath, full).replace(/\\/g, "/");
|
|
81
|
-
const content = fs.readFileSync(full, "utf-8");
|
|
82
|
-
const lineCount = content.split("\n").length;
|
|
83
|
-
const msgs = [];
|
|
84
|
-
// Check pattern violations
|
|
85
|
-
for (const v of architectClient.checkFile(relPath, content)) {
|
|
86
|
-
const lineStr = v.line ? `L${v.line}: ` : "";
|
|
87
|
-
msgs.push(`${lineStr}${v.message}`);
|
|
88
|
-
}
|
|
89
|
-
// Check file size
|
|
90
|
-
const sizeV = architectClient.checkFileSize(relPath, lineCount);
|
|
91
|
-
if (sizeV) {
|
|
92
|
-
msgs.push(sizeV.message);
|
|
93
|
-
}
|
|
94
|
-
if (msgs.length > 0) {
|
|
95
|
-
violationsByFile.set(full, msgs);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return violationsByFile;
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Score each file by combined debt signal. Higher = worse.
|
|
102
|
-
*/
|
|
103
|
-
export function scoreFiles(skipByFile, metricsByFile, architectViolations) {
|
|
104
|
-
const allFiles = new Set([
|
|
105
|
-
...skipByFile.keys(),
|
|
106
|
-
...metricsByFile.keys(),
|
|
107
|
-
...(architectViolations?.keys() ?? []),
|
|
108
|
-
]);
|
|
109
|
-
return [...allFiles]
|
|
110
|
-
.map((file) => {
|
|
111
|
-
let score = 0;
|
|
112
|
-
const m = metricsByFile.get(file);
|
|
113
|
-
if (m) {
|
|
114
|
-
if (m.mi < 20)
|
|
115
|
-
score += 5;
|
|
116
|
-
else if (m.mi < 40)
|
|
117
|
-
score += 3;
|
|
118
|
-
else if (m.mi < 60)
|
|
119
|
-
score += 1;
|
|
120
|
-
if (m.cognitive > 300)
|
|
121
|
-
score += 4;
|
|
122
|
-
else if (m.cognitive > 150)
|
|
123
|
-
score += 2;
|
|
124
|
-
else if (m.cognitive > 80)
|
|
125
|
-
score += 1;
|
|
126
|
-
if (m.nesting > 8)
|
|
127
|
-
score += 2;
|
|
128
|
-
else if (m.nesting > 5)
|
|
129
|
-
score += 1;
|
|
130
|
-
}
|
|
131
|
-
for (const issue of skipByFile.get(file) ?? []) {
|
|
132
|
-
if (issue.rule === "large-class")
|
|
133
|
-
score += 5;
|
|
134
|
-
else if (issue.rule === "no-as-any")
|
|
135
|
-
score += 2;
|
|
136
|
-
else
|
|
137
|
-
score += 1;
|
|
138
|
-
}
|
|
139
|
-
// Architect violations are high-priority signals
|
|
140
|
-
const archMsgs = architectViolations?.get(file);
|
|
141
|
-
if (archMsgs && archMsgs.length > 0) {
|
|
142
|
-
score += archMsgs.length * 3; // Each violation = 3 points
|
|
143
|
-
}
|
|
144
|
-
return { file, score };
|
|
145
|
-
})
|
|
146
|
-
.filter((f) => f.score > 0)
|
|
147
|
-
.sort((a, b) => b.score - a.score);
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Read a code snippet around the first violation line.
|
|
151
|
-
* Returns { snippet, start, end } or null.
|
|
152
|
-
*/
|
|
153
|
-
export function extractCodeSnippet(filePath, firstLine, contextLines = 2, maxLines = 45) {
|
|
154
|
-
try {
|
|
155
|
-
const fileLines = fs.readFileSync(filePath, "utf-8").split("\n");
|
|
156
|
-
const start = Math.max(0, firstLine - 1 - contextLines);
|
|
157
|
-
const end = Math.min(fileLines.length, start + maxLines);
|
|
158
|
-
return {
|
|
159
|
-
snippet: fileLines.slice(start, end).join("\n"),
|
|
160
|
-
start: start + 1,
|
|
161
|
-
end,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
catch {
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
}
|
package/clients/scan-utils.js
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { EXCLUDED_DIRS, isTestFile } from "./file-utils.js";
|
|
4
|
-
/**
|
|
5
|
-
* Common parsing logic for ast-grep JSON output (handles both array and NDJSON).
|
|
6
|
-
*/
|
|
7
|
-
// biome-ignore lint/suspicious/noExplicitAny: ast-grep JSON output is untyped
|
|
8
|
-
export function parseAstGrepJson(raw) {
|
|
9
|
-
if (!raw)
|
|
10
|
-
return [];
|
|
11
|
-
const trimmed = raw.trim();
|
|
12
|
-
if (trimmed.startsWith("[")) {
|
|
13
|
-
try {
|
|
14
|
-
return JSON.parse(trimmed);
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
return [];
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return trimmed.split("\n").flatMap((l) => {
|
|
21
|
-
try {
|
|
22
|
-
return [JSON.parse(l)];
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return [];
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Check if a file should be ignored based on project type and common patterns.
|
|
31
|
-
*/
|
|
32
|
-
export function shouldIgnoreFile(filePath, isTsProject) {
|
|
33
|
-
const relPath = filePath.replace(/\\/g, "/");
|
|
34
|
-
const _basename = path.basename(relPath);
|
|
35
|
-
// Ignore compiled JS in TS projects
|
|
36
|
-
const isJs = relPath.endsWith(".js") ||
|
|
37
|
-
relPath.endsWith(".mjs") ||
|
|
38
|
-
relPath.endsWith(".cjs");
|
|
39
|
-
if (isTsProject && isJs)
|
|
40
|
-
return true;
|
|
41
|
-
// Ignore test scripts and common test patterns
|
|
42
|
-
if (isTestFile(filePath))
|
|
43
|
-
return true;
|
|
44
|
-
// Ignore hidden directories and common build outputs
|
|
45
|
-
if (EXCLUDED_DIRS.some((d) => relPath.includes(`/${d}/`)))
|
|
46
|
-
return true;
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Recursively find source files in a directory, respecting common excludes.
|
|
51
|
-
*/
|
|
52
|
-
export function getSourceFiles(dir, isTsProject) {
|
|
53
|
-
const files = [];
|
|
54
|
-
if (!fs.existsSync(dir))
|
|
55
|
-
return files;
|
|
56
|
-
const scan = (d) => {
|
|
57
|
-
let entries = [];
|
|
58
|
-
try {
|
|
59
|
-
entries = fs.readdirSync(d, { withFileTypes: true });
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
for (const entry of entries) {
|
|
65
|
-
const full = path.join(d, entry.name);
|
|
66
|
-
if (entry.isDirectory()) {
|
|
67
|
-
if (EXCLUDED_DIRS.includes(entry.name))
|
|
68
|
-
continue;
|
|
69
|
-
scan(full);
|
|
70
|
-
}
|
|
71
|
-
else if (/\.(ts|tsx|js|jsx|py|go|rs)$/.test(entry.name)) {
|
|
72
|
-
// Skip compiled JS if it's a TS project
|
|
73
|
-
if (isTsProject &&
|
|
74
|
-
entry.name.endsWith(".js") &&
|
|
75
|
-
fs.existsSync(full.replace(/\.js$/, ".ts")))
|
|
76
|
-
continue;
|
|
77
|
-
files.push(full);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
scan(dir);
|
|
82
|
-
return files;
|
|
83
|
-
}
|